Archived
feat(w05): 重做的意见交回给 AI 重跑那一步 —— 不再只是把派生列重算一遍
用户:「唯一调用 ai 的这一步,使用重做后是否没有将重做原因带回去重新让 ai 重跑这一步?」 确实没有:S3 那一轮只跑三笔纯投影重算,人写的那句话除了留痕之外无人读 —— AI 判过的四态一个字不会变,除非他自己在件上逐格改。 平台:引擎把「人这次打回写了什么」注给配方(`case_redo_note`)。 · 下划线不是 case: 前缀 —— S-FEEL 的名字不许带冒号,而它最要紧的用法就是当 when 条件用; · **没打回也永远绑着空串**:未绑定的名字 `!= ""` 求值为真,不绑就等于那几笔每一轮都跑 (拿真 @sfeel 钉了这条,桩也照真语义写,否则钉不住); · 静态审认这个名字 —— 它当场拦下过一次未登记的引用,这道门做对了事。 W05@2.17.0:S3 多两笔,都钉在 `case_redo_note != ""` 上 —— 带意见重判(入参多带上一轮四态/影响/依据/UNKNOWN 去向 + 他那句话;判法与第一轮同一份 ASSESS_RULES,后接复核纪律:没被点到、也确实没判错的行原样带回,别趁重判把人改过的冲掉) + 判完落回明细(与第一轮同一笔投影,只多一个 when)。判法、出参形状、落明细抽成三个 共享常量,S2 与 S3 共用 —— 各写一份早晚分叉。 验收:e2e w05 5/5 released、被拒 0;案例 0(先点名驳回、再通过)模型调用恰 2 次, 其余四例仍是 1 次 —— 没被打回就不多花一次模型。auto-runner 3 测(含变异验证)、包内 +1。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LKMZDwMbyKzjw6Hembwvet
This commit is contained in:
@@ -406,11 +406,41 @@ describe('S3 / S4 配方', () => {
|
||||
const s4 = stepOf('T_release')?.auto?.do ?? [];
|
||||
const s2 = stepOf('S2_assess')?.auto?.do ?? [];
|
||||
|
||||
it('S3 五笔:差异明细重算 → 关系 → 验证记录 → 卡 → 交代 —— 人改的四态先落地,结论才有真数据可读', () => {
|
||||
expect(s3.map((a) => a.kind)).toEqual(['project', 'project', 'project', 'card', 'say']);
|
||||
expect(s3[0]?.kind === 'project' && s3[0].into).toBe('substitution.difference_item');
|
||||
expect(s3[1]?.kind === 'project' && s3[1].into).toBe('substitution.relation');
|
||||
expect(s3[2]?.kind === 'project' && s3[2].into).toBe('validation.record');
|
||||
it('S3 七笔:复核重判 → 落明细 → 明细重算 → 关系 → 验证记录 → 卡 → 交代', () => {
|
||||
expect(s3.map((a) => a.kind)).toEqual(['analyze', 'project', 'project', 'project', 'project', 'card', 'say']);
|
||||
expect(s3[1]?.kind === 'project' && s3[1].into).toBe('substitution.difference_item');
|
||||
expect(s3[2]?.kind === 'project' && s3[2].into).toBe('substitution.difference_item');
|
||||
expect(s3[3]?.kind === 'project' && s3[3].into).toBe('substitution.relation');
|
||||
expect(s3[4]?.kind === 'project' && s3[4].into).toBe('validation.record');
|
||||
});
|
||||
|
||||
/*
|
||||
* **人打回时,意见要交回给 AI 重判**(2026-09-01 用户定:「重做后是否没有将重做原因带回去
|
||||
* 重新让 ai 重跑这一步?」)—— 在此之前 S3 那一轮只把派生列重算一遍,人写的那句话除了留痕
|
||||
* 之外**无人读**:AI 判过的四态一个字不会变,除非人自己在件上逐格改。
|
||||
*
|
||||
* 只在被打回的那一轮跑:`case_redo_note` 是引擎注给配方的那句话(没打回就是空串)——
|
||||
* 常路(第一次送审)这两笔跳过,整个活动仍然只调一次模型。
|
||||
*/
|
||||
it('复核轮:两笔都钉在「人打回了才跑」上,判法与第一轮同一份规矩,入参多带上一轮结论与他的意见', () => {
|
||||
const redo = s3[0];
|
||||
const land = s3[1];
|
||||
expect(redo?.kind === 'analyze' && redo.when).toBe('case_redo_note != ""');
|
||||
expect(land?.kind === 'project' && land.when).toBe('case_redo_note != ""');
|
||||
/* 判法一处真源:复核指令以第一轮那份规矩打头,后面才接复核纪律 */
|
||||
const first = s2[0];
|
||||
const rules = first?.kind === 'analyze' ? first.instruction : '';
|
||||
expect(redo?.kind === 'analyze' && redo.instruction.startsWith(rules)).toBe(true);
|
||||
expect(redo?.kind === 'analyze' && redo.instruction).toContain('原样带回');
|
||||
/* 入参:上一轮四态 + 他写的意见 —— 少了任何一样,重判要么冲掉人改过的行,要么读不到意见 */
|
||||
const fields = Object.keys(redo?.kind === 'analyze' ? redo.input.fields : {});
|
||||
for (const key of ['last_status', 'last_impact', 'last_evidence', 'last_unknown_disposition', 'reviewer_note']) {
|
||||
expect(fields).toContain(key);
|
||||
}
|
||||
expect(redo?.kind === 'analyze' && redo.input.fields['reviewer_note']).toEqual({ in: 'case_redo_note' });
|
||||
/* 落明细那一笔与第一轮同一份投影(只多一个 when)—— 各写一份早晚分叉 */
|
||||
const s2Land = s2[2];
|
||||
expect(land?.kind === 'project' && land.fields).toBe(s2Land?.kind === 'project' ? s2Land.fields : undefined);
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -420,7 +450,7 @@ describe('S3 / S4 配方', () => {
|
||||
* `s3[0]` 必须是**排在关系 / 验证记录投影之前**的差异明细重算笔。
|
||||
*/
|
||||
it('差异明细重算笔:只列主键与两个派生列,不碰 difference_status 自己、也不碰人能改的叙事列', () => {
|
||||
const recompute = s3[0];
|
||||
const recompute = s3[2];
|
||||
expect(recompute?.kind === 'project' && recompute.from).toEqual({ each: 'mine:substitution.difference_item' });
|
||||
/* `recompute` 经 `s3[0]` 的 tuple 定位精确 narrow 到这一笔自己的字面量类型,它压根没有
|
||||
`prune` 这个键(不是「值为 undefined」,是键不存在)—— `in` 判存在性,不做属性访问 */
|
||||
@@ -438,7 +468,7 @@ describe('S3 / S4 配方', () => {
|
||||
* 展开)——各写一份早晚分叉,而分叉的那一刻屏上会同时摆着两个互相矛盾的规则。
|
||||
*/
|
||||
it('S3 重算笔与 S2 差异明细创建笔共用同一份 judgement_class / risk_level when 链', () => {
|
||||
const s3Fields = s3[0]?.kind === 'project' ? s3[0].fields : undefined;
|
||||
const s3Fields = s3[2]?.kind === 'project' ? s3[2].fields : undefined;
|
||||
const s2Fields = s2[2]?.kind === 'project' ? s2[2].fields : undefined;
|
||||
expect(s2Fields?.['judgement_class']).toBeDefined();
|
||||
expect(s3Fields?.['judgement_class']).toBe(s2Fields?.['judgement_class']);
|
||||
@@ -459,7 +489,7 @@ describe('S3 / S4 配方', () => {
|
||||
* 「人改四态 → 等级跟着变」这条链不能断),且与 S2 那份是**同一个 when 链引用**(不许分叉)。
|
||||
*/
|
||||
it('**S3 的关系笔只重算判定** —— 收口四格(理由/摘要/风险/验证要求)一列都不许出现,否则人改完一保存就被冲掉', () => {
|
||||
const relation = s3[1];
|
||||
const relation = s3[3];
|
||||
expect(relation?.kind).toBe('project');
|
||||
const fieldsOf = (act: (typeof s3)[number] | undefined): Readonly<Record<string, unknown>> =>
|
||||
act?.kind === 'project' ? act.fields : {};
|
||||
@@ -474,7 +504,7 @@ describe('S3 / S4 配方', () => {
|
||||
/* 主键带回:同 PK 覆盖,不是新开一行 */
|
||||
expect(fields['substitution_relation_id']).toBeDefined();
|
||||
/* 验证记录那一笔仍与 S2 同一份(它不涉及人可改的格子) */
|
||||
expect(s3[2]).toBe(s2[4]);
|
||||
expect(s3[4]).toBe(s2[4]);
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -490,7 +520,7 @@ describe('S3 / S4 配方', () => {
|
||||
});
|
||||
|
||||
it('S3 的卡:审核面孔、单一选项、挂本步的件', () => {
|
||||
const card = s3[3];
|
||||
const card = s3[5];
|
||||
expect(card?.kind === 'card' && card.as).toBe('decide');
|
||||
expect(card?.kind === 'card' && card.variant).toBe('review');
|
||||
expect(card?.kind === 'card' ? card.options ?? [] : []).toHaveLength(1);
|
||||
|
||||
Reference in New Issue
Block a user