Archived
手改是**兜 AI 的错**的那一手 —— 这一轮把它从「摆在那儿但落不了地」做成真的: 件上就地改 · 保存**按地址收**,不看当下那张卡:载荷从前借道工单的 `blocks[i]` 取列名, 于是熔断卡(没有 blocks)在屏上时,人补的那一格一个字都送不出去 —— 而他要补的恰恰是把这一单卡住的那个必填格。授权面改由活动清单的台账声明把关。 · **能增删行**:错不止「这一格填错了」,还有「这一行不该在」与「漏了一行」。 删挂记号不当场抹掉(看得见、可后悔),且**删要说出来**(`drops`)—— 「没回传就是删了」会把一次不完整的回传变成一次删表。 · **必填没填按不下提交**:与放行门 L1 同一个数同一套算法;闸上摆出差在哪一行哪一格。 · 只写真改了的那几行 —— 改一格却留「修订 4 行」的痕,那句话是假的。 判据与门 · 裁量判据带谓词时**谓词说了算**:「选填缺口已补齐」基座数得出来, 人填好的目标价它照样判「客户未提供」的毛病由此消掉。 · 验收卡判据**读时现算** —— 人在成果件上补一格,判据跟着变。 · 放行门认「**办完了没有**」不认「有人写过」:从前写个 `false` 就把门推开, 于是一单没打标就进了放行门。三个数全由基座现数。 · 拒回把「活没干完」说成「数或行对不上」的毛病:判据点名的表一行都没写时直说那一步没办。 **指向两扇打不开的门,比不指还糟。** 界面不撒谎 · 日志只有三色:负责人绿 · 智能体蓝 · 系统灰(归侧五种是度量的分法,不是给人看的)。 · 验收收口答两件事:落了什么(按**交付物**点名,一份成果的几张表并成一行)、 往哪儿去(转出哪一口、带过去哪些字段);一个口都没发就直说到此为止。 · 签完到收尾之间那一段有自己的话「字已签下,正在落地」—— 中间态本来就该说出来,只是要说对。 · 消息不再被内部事件刷屏:只有从通道进来的件才谈得上「无人认领」。 · 建客户公司与联系人**一次确认落两行**;联系人编码自增不问人;卡面摆表的人读名。 A1 三案例端到端全 released:全 A 类直通 12 拍 / 客户点名跳一条 10 拍 / 新客户缺必填 14 拍。 五道门禁全绿(971 passed)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JsTDjoHovERXyy6Wmw1ALp
177 lines
7.9 KiB
TypeScript
177 lines
7.9 KiB
TypeScript
/**
|
||
* 收步判据要连着**此刻的读数**一起摆给模型 —— 「差在哪」不能靠猜。
|
||
*
|
||
* 这条是拿一次熔断换来的:A1 的 S2 收步判据是
|
||
* `missing_key_total + missing_minor_total = 0`,而草稿里 missing_key_total 是 1。
|
||
* 模型看不见这个数,以为缺口已经补齐了,就把同一批行再写一遍「确认一下」——
|
||
* 撞上空转检查被拒;拒了它还是不知道差在哪,再写一遍……连拒五拍熔断。
|
||
* 把读数摆出来,这一步就从死胡同变成一句话能办的事。
|
||
*/
|
||
import { describe, expect, it } from 'vitest';
|
||
import { readingsOf } from '../internal/domain/lifecycle.ts';
|
||
|
||
describe('收步判据的读数', () => {
|
||
it('把判据读的那几个值摆出来', () => {
|
||
const out = readingsOf('missing_key_total + missing_minor_total = 0', {
|
||
missing_key_total: 1,
|
||
missing_minor_total: 0,
|
||
});
|
||
expect(out).toContain('missing_key_total=1');
|
||
expect(out).toContain('missing_minor_total=0');
|
||
});
|
||
|
||
it('只摆判据读的,不摆整个任务视图', () => {
|
||
const out = readingsOf('line_total > 0', { line_total: 3, subject: '询价', pn: 'LM358DR' });
|
||
expect(out).toContain('line_total=3');
|
||
expect(out).not.toContain('subject');
|
||
expect(out).not.toContain('pn');
|
||
});
|
||
|
||
it('没写过的名字如实说没写过 —— 它正是这一步推不动的原因', () => {
|
||
expect(readingsOf('benchmark_total > 0', {})).toContain('benchmark_total=(没写过)');
|
||
});
|
||
|
||
it('保留字不当台账名去查', () => {
|
||
const out = readingsOf('need_sourcing = true and line_total > 0', { need_sourcing: false, line_total: 2 });
|
||
expect(out).toContain('need_sourcing=false');
|
||
expect(out).not.toContain('true=');
|
||
expect(out).not.toContain('and=');
|
||
});
|
||
|
||
it('字符串字面量里的词不当台账名', () => {
|
||
expect(readingsOf('disposition = "照原价报"', { disposition: '通过' })).toBe('(现在:disposition=通过)');
|
||
});
|
||
|
||
it('没有名字可摆时给空串 —— 不往提示词里塞空括号', () => {
|
||
expect(readingsOf('1 = 1', {})).toBe('');
|
||
});
|
||
});
|
||
|
||
/**
|
||
* 判据名归一要削掉**引擎自己贴的等级标记**。
|
||
*
|
||
* 提交被拒时门印的是「…没达标 —— 某判据(L3)」,紧接着让模型「照判据名原样写」;
|
||
* 模型照做,把 `(L3)` 一起抄进 criterion —— 裁定的键与清单里的判据名对不上,
|
||
* 裁了等于没裁,再提交再被拒,连拒五拍熔断(实测 A2 死于此)。
|
||
*/
|
||
import { criterionKey } from '../internal/domain/lifecycle.ts';
|
||
|
||
describe('判据名归一', () => {
|
||
it('模型抄进等级标记也要认得出是同一条', () => {
|
||
expect(criterionKey('每项数据来源可溯(以原厂官网规格书为准)(L3)')).toBe(
|
||
criterionKey('每项数据来源可溯(以原厂官网规格书为准)'),
|
||
);
|
||
});
|
||
|
||
it('五个等级都削', () => {
|
||
for (const level of ['L1', 'L2', 'L3', 'L4', 'L5']) {
|
||
expect(criterionKey(`某判据(${level})`)).toBe(criterionKey('某判据'));
|
||
}
|
||
});
|
||
|
||
it('全角括号的等级标记也削 —— 模型爱把半角敲成全角', () => {
|
||
expect(criterionKey('某判据(L3)')).toBe(criterionKey('某判据'));
|
||
});
|
||
|
||
it('只削末尾那一个,不碰判据名里本来就有的括号', () => {
|
||
expect(criterionKey('吃不准降 B(不许臆断 A)')).toBe('吃不准降b(不许臆断a)');
|
||
});
|
||
|
||
it('照旧折全角、去空白 —— 老毛病不许回来', () => {
|
||
expect(criterionKey('吃不准降B,不许臆断A')).toBe(criterionKey('吃不准降 B,不许臆断 A'));
|
||
});
|
||
});
|
||
|
||
/**
|
||
* 同一句交代不进两遍对话区。
|
||
*
|
||
* 实测试跑 2e091d25:「有两颗料客户没写清楚……你看是我起草一封信去问,还是你直接找对方?」
|
||
* 一字不差连着吐了三拍(先 start 那一项、又 start 了一次、才发出决策卡)——
|
||
* 人在对话区看到同一个问题被问三遍,像卡带。
|
||
*
|
||
* 判定放在这儿单测:去空白后逐字比、只比紧邻的上一句。
|
||
* 近义句不拦(那是两次不同的交代),隔了几拍说回同一句也不拦(局面绕回来了)。
|
||
*/
|
||
function isRepeatSay(say: string, trail: ReadonlyArray<{ kind: string; title: string }>): boolean {
|
||
const now = say.trim().replace(/\s+/g, '');
|
||
const last = [...trail].reverse().find((t) => t.kind === 'narration');
|
||
return last !== undefined && last.title.trim().replace(/\s+/g, '') === now;
|
||
}
|
||
|
||
describe('对话区不结巴', () => {
|
||
const n = (title: string) => ({ kind: 'narration', title });
|
||
|
||
it('跟上一句一字不差 —— 压下', () => {
|
||
expect(isRepeatSay('缺两颗料的信息,要我起草封信吗?', [n('缺两颗料的信息,要我起草封信吗?')])).toBe(true);
|
||
});
|
||
|
||
it('只差空白也算同一句 —— 模型抄自己时空格常飘', () => {
|
||
expect(isRepeatSay('缺 两颗料 的信息', [n('缺两颗料的信息')])).toBe(true);
|
||
});
|
||
|
||
it('中间隔着别的痕,比的仍是**上一句叙事**', () => {
|
||
const trail = [n('缺两颗料的信息'), { kind: 'action', title: '写草稿:x 1 行' }, { kind: 'engine-transition', title: 'manualStart:S2' }];
|
||
expect(isRepeatSay('缺两颗料的信息', trail)).toBe(true);
|
||
});
|
||
|
||
it('近义但不同的句子 —— 放行,那是两次不同的交代', () => {
|
||
expect(isRepeatSay('历史价都查到了,接下来打标。', [n('三颗料的历史价都查到了,接下来打标。')])).toBe(false);
|
||
});
|
||
|
||
it('隔了一句别的又说回同一句 —— 放行,局面可能真绕回来了', () => {
|
||
expect(isRepeatSay('缺两颗料的信息', [n('缺两颗料的信息'), n('客户回话了')])).toBe(false);
|
||
});
|
||
|
||
it('一条叙事都还没有 —— 放行', () => {
|
||
expect(isRepeatSay('第一句话', [{ kind: 'action', title: '写草稿' }])).toBe(false);
|
||
});
|
||
});
|
||
|
||
/**
|
||
* **行数对不上时,把是哪几行也摆出来**(2026-08-01,连跑三次熔断逼出来的)。
|
||
*
|
||
* 行数类判据拒回时只给两个数,模型看得见「10 ≠ 9」,却不知道多的是哪一行 ——
|
||
* 它只能猜:改数、重写、再提,三条路都不对,连拒五拍熔断。实测三种形态全是这一类:
|
||
* · 记录写了 10 行而条目数报 9(同一颗料两个 PN 写法,或把客户说跳过的那条也写了);
|
||
* · 分类表 0 行而记录 10 行(它说「打完标了」,其实一行没写);
|
||
* · 替代请求 1 张而 C 类 0 条(给那颗料开了单,却没把它标成 C)。
|
||
* 三种一摆主键就都是一句话能办的事。
|
||
*/
|
||
describe('行数判据拒回:连主键名单一起摆', () => {
|
||
it('谓词读了 _rows,就把那张表本单写下的行摆出来', () => {
|
||
const out = readingsOf('quote_intake_record_rows = line_total', {
|
||
quote_intake_record_rows: 10,
|
||
line_total: 9,
|
||
quote_intake_record_keys: 'LM358DR、TL072CDR、NE555P',
|
||
});
|
||
expect(out).toContain('quote_intake_record_rows=10');
|
||
expect(out).toContain('line_total=9');
|
||
expect(out).toContain('是这几行:LM358DR、TL072CDR、NE555P');
|
||
});
|
||
|
||
it('两张表都读了 → 两份名单都摆,差集自己对得出来', () => {
|
||
const out = readingsOf('quote_intake_type_table_rows = quote_intake_record_rows', {
|
||
quote_intake_type_table_rows: 2,
|
||
quote_intake_record_rows: 3,
|
||
quote_intake_type_table_keys: 'A、B',
|
||
quote_intake_record_keys: 'A、B、C',
|
||
});
|
||
expect(out).toContain('是这几行:A、B;');
|
||
expect(out).toContain('是这几行:A、B、C');
|
||
});
|
||
|
||
it('那张表一行没写就没有名单 —— 不摆一句空的「是这几行:」', () => {
|
||
const out = readingsOf('quote_intake_alt_request_rows = type_c_total', {
|
||
quote_intake_alt_request_rows: 0,
|
||
type_c_total: 0,
|
||
});
|
||
expect(out).toBe('(现在:quote_intake_alt_request_rows=0、type_c_total=0)');
|
||
});
|
||
|
||
it('不读 _rows 的判据照旧只摆读数 —— 名单不是每条判据都要的', () => {
|
||
expect(readingsOf('missing_key_total = 0', { missing_key_total: 1, quote_intake_record_keys: 'X' })).toBe(
|
||
'(现在:missing_key_total=1)',
|
||
);
|
||
});
|
||
});
|