@itookit/dsht 0.3.8 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +30 -11
- package/README.zh.md +30 -11
- package/dist/cli/dsht.js +203 -18
- package/dist/cli/startup.d.ts +40 -0
- package/dist/cli/startup.js +295 -0
- package/dist/cli/trace-summary.d.ts +78 -0
- package/dist/cli/trace-summary.js +241 -0
- package/dist/cli/verifier.d.ts +60 -0
- package/dist/cli/verifier.js +242 -0
- package/dist/contracts.d.ts +344 -0
- package/dist/contracts.js +1 -0
- package/dist/controller/commands.d.ts +47 -0
- package/dist/controller/commands.js +322 -0
- package/dist/controller/connection.d.ts +11 -29
- package/dist/controller/connection.js +26 -60
- package/dist/controller/controller.d.ts +616 -166
- package/dist/controller/controller.js +1395 -146
- package/dist/controller/index.d.ts +8 -1
- package/dist/controller/index.js +5 -0
- package/dist/controller/loop-contract.d.ts +136 -0
- package/dist/controller/loop-contract.js +308 -0
- package/dist/controller/loop-prompts-schema.d.ts +56 -0
- package/dist/controller/loop-prompts-schema.js +144 -0
- package/dist/controller/loop-prompts.d.ts +55 -0
- package/dist/controller/loop-prompts.generated.d.ts +104 -0
- package/dist/controller/loop-prompts.generated.js +185 -0
- package/dist/controller/loop-prompts.js +104 -0
- package/dist/controller/loop-protocols.d.ts +39 -0
- package/dist/controller/loop-protocols.js +115 -0
- package/dist/controller/loop.d.ts +275 -0
- package/dist/controller/loop.js +378 -0
- package/dist/controller/prompts.d.ts +54 -0
- package/dist/controller/prompts.js +162 -0
- package/dist/controller/trace-log.d.ts +45 -0
- package/dist/controller/trace-log.js +144 -0
- package/dist/controller/verifier.d.ts +126 -0
- package/dist/controller/verifier.js +75 -0
- package/dist/cost/index.d.ts +1 -1
- package/dist/cost/index.js +1 -1
- package/dist/cost/ledger.d.ts +0 -1
- package/dist/cost/ledger.js +0 -1
- package/dist/json.d.ts +18 -0
- package/dist/json.js +19 -0
- package/dist/references.d.ts +25 -0
- package/dist/references.js +26 -0
- package/dist/session/connection-view.d.ts +2 -11
- package/dist/session/controller.d.ts +73 -72
- package/dist/session/controller.js +185 -209
- package/dist/session/history.d.ts +6 -18
- package/dist/session/history.js +1 -24
- package/dist/session/index.d.ts +9 -4
- package/dist/session/index.js +7 -3
- package/dist/session/info.d.ts +25 -52
- package/dist/session/info.js +39 -25
- package/dist/session/markdown.js +1 -1
- package/dist/session/math.js +1 -1
- package/dist/session/mutation-gate.d.ts +51 -0
- package/dist/session/mutation-gate.js +73 -0
- package/dist/session/navigation.d.ts +2 -89
- package/dist/session/navigation.js +2 -129
- package/dist/session/peek.d.ts +38 -0
- package/dist/session/peek.js +103 -0
- package/dist/session/references.d.ts +2 -20
- package/dist/session/references.js +1 -26
- package/dist/session/runtime.d.ts +26 -0
- package/dist/session/runtime.js +28 -0
- package/dist/session/telemetry.d.ts +12 -13
- package/dist/session/telemetry.js +27 -58
- package/dist/session/transcript.d.ts +0 -6
- package/dist/session/transcript.js +2 -15
- package/dist/session/types.d.ts +25 -0
- package/dist/session/types.js +0 -1
- package/dist/session-title.d.ts +9 -0
- package/dist/session-title.js +21 -0
- package/dist/shell/controller.d.ts +31 -1
- package/dist/shell/controller.js +34 -2
- package/dist/shell/index.d.ts +3 -3
- package/dist/shell/index.js +2 -2
- package/dist/shell/runner.d.ts +10 -0
- package/dist/shell/runner.js +48 -9
- package/dist/slash/index.d.ts +10 -0
- package/dist/slash/index.js +7 -0
- package/dist/slash/parse.d.ts +166 -0
- package/dist/slash/parse.js +259 -0
- package/dist/slash/pipeline.d.ts +140 -0
- package/dist/slash/pipeline.js +115 -0
- package/dist/slash/registry.d.ts +88 -0
- package/dist/slash/registry.js +177 -0
- package/dist/state.d.ts +14 -4
- package/dist/state.js +3 -2
- package/dist/text.d.ts +28 -0
- package/dist/text.js +55 -0
- package/dist/transport/events.d.ts +104 -0
- package/dist/transport/events.js +149 -0
- package/dist/transport/wire.d.ts +9 -17
- package/dist/transport/wire.js +2 -27
- package/dist/ui/app.js +856 -441
- package/dist/ui/chat/header.js +1 -1
- package/dist/ui/chat/history-view.d.ts +1 -1
- package/dist/ui/chat/loop-status.d.ts +11 -0
- package/dist/ui/chat/loop-status.js +28 -0
- package/dist/ui/chat/navigation-model.d.ts +86 -0
- package/dist/ui/chat/navigation-model.js +107 -0
- package/dist/ui/chat/shell-view.d.ts +15 -2
- package/dist/ui/chat/shell-view.js +37 -3
- package/dist/ui/chat/status.d.ts +47 -3
- package/dist/ui/chat/status.js +65 -50
- package/dist/ui/chat/viewport.d.ts +1 -1
- package/dist/ui/dialogs/cost.d.ts +21 -4
- package/dist/ui/dialogs/cost.js +7 -12
- package/dist/ui/dialogs/index.d.ts +22 -5
- package/dist/ui/dialogs/index.js +19 -3
- package/dist/ui/dialogs/loop.d.ts +43 -0
- package/dist/ui/dialogs/loop.js +224 -0
- package/dist/ui/dialogs/peek.d.ts +25 -0
- package/dist/ui/dialogs/peek.js +35 -0
- package/dist/ui/dialogs/picker.d.ts +2 -0
- package/dist/ui/dialogs/picker.js +4 -2
- package/dist/ui/input/mouse.d.ts +12 -2
- package/dist/ui/input/mouse.js +20 -7
- package/dist/ui/input/references.d.ts +1 -1
- package/dist/ui/status/model.d.ts +7 -0
- package/dist/ui/status/model.js +5 -0
- package/dist/ui/theme/index.d.ts +1 -1
- package/package.json +6 -4
- package/dist/ui/commands/parse.d.ts +0 -104
- package/dist/ui/commands/parse.js +0 -135
- package/dist/ui/commands/registry.d.ts +0 -33
- package/dist/ui/commands/registry.js +0 -73
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/** The shape of loop.yaml and the rules an editor must not break.
|
|
2
|
+
*
|
|
3
|
+
* Kept apart from `loop-prompts.ts` so the build script can validate a YAML file before the
|
|
4
|
+
* generated module exists: this module imports nothing, while the renderer imports the generated
|
|
5
|
+
* data. The validator is the single copy of the schema — the generator and the tests both call it.
|
|
6
|
+
*/
|
|
7
|
+
/** Placeholders every template may use; a record's own `vars` add to these. */
|
|
8
|
+
export const LOOP_PLACEHOLDERS = ['from', 'to', 'score', 'tries', 'step', 'attempt', 'title', 'artifact', 'checks'];
|
|
9
|
+
/** Record names that belong to `/loop` itself, so a protocol cannot shadow a subcommand. */
|
|
10
|
+
export const RESERVED_PROTOCOL_NAMES = ['answer', 'abort', 'stop'];
|
|
11
|
+
/** Check one parsed document against the schema the renderer relies on.
|
|
12
|
+
*
|
|
13
|
+
* Every message names the field at fault, because the only reader is a maintainer editing YAML.
|
|
14
|
+
* @param source - Parsed loop.yaml, or anything else that claims to be one.
|
|
15
|
+
* @returns One message per problem; an empty list means valid.
|
|
16
|
+
*/
|
|
17
|
+
export function validateLoopPrompts(source) {
|
|
18
|
+
const errors = [];
|
|
19
|
+
const document = source;
|
|
20
|
+
if (document?.version !== 1)
|
|
21
|
+
errors.push('version must be 1');
|
|
22
|
+
if (!Number.isFinite(document?.defaults?.score))
|
|
23
|
+
errors.push('defaults.score must be a number');
|
|
24
|
+
if (!Number.isFinite(document?.defaults?.tries))
|
|
25
|
+
errors.push('defaults.tries must be a number');
|
|
26
|
+
const protocols = document?.protocols;
|
|
27
|
+
if (protocols === null || typeof protocols !== 'object' || Object.keys(protocols ?? {}).length === 0) {
|
|
28
|
+
errors.push('protocols must be a non-empty mapping');
|
|
29
|
+
return errors;
|
|
30
|
+
}
|
|
31
|
+
const runtime = new Set(LOOP_PLACEHOLDERS);
|
|
32
|
+
const reserved = new Set(RESERVED_PROTOCOL_NAMES);
|
|
33
|
+
const placeholders = (text) => [...text.matchAll(/\{\{(\w+)\}\}/g)].map(match => match[1]);
|
|
34
|
+
for (const [kind, protocol] of Object.entries(protocols)) {
|
|
35
|
+
const at = `protocols.${kind}`;
|
|
36
|
+
if (reserved.has(kind))
|
|
37
|
+
errors.push(`${at} is a reserved name: ${RESERVED_PROTOCOL_NAMES.join(', ')} belong to /loop itself`);
|
|
38
|
+
// A template may name a runtime value or one of this record's own vars, and nothing else.
|
|
39
|
+
const vars = protocol.vars;
|
|
40
|
+
if (vars !== undefined && (vars === null || typeof vars !== 'object' || Array.isArray(vars))) {
|
|
41
|
+
errors.push(`${at}.vars must be a mapping of names to strings`);
|
|
42
|
+
}
|
|
43
|
+
const varNames = new Set(vars !== undefined && typeof vars === 'object' && !Array.isArray(vars) ? Object.keys(vars) : []);
|
|
44
|
+
if (vars !== undefined && typeof vars === 'object' && !Array.isArray(vars)) {
|
|
45
|
+
for (const [name, value] of Object.entries(vars)) {
|
|
46
|
+
if (typeof value !== 'string')
|
|
47
|
+
errors.push(`${at}.vars.${name} must be a string`);
|
|
48
|
+
if (runtime.has(name))
|
|
49
|
+
errors.push(`${at}.vars.${name} shadows a runtime placeholder`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const allowed = new Set([...runtime, ...varNames]);
|
|
53
|
+
const checkPlaceholders = (text, where) => {
|
|
54
|
+
for (const name of placeholders(text))
|
|
55
|
+
if (!allowed.has(name))
|
|
56
|
+
errors.push(`${where}: unknown placeholder {{${name}}}`);
|
|
57
|
+
};
|
|
58
|
+
checkPlaceholders(typeof protocol.title === 'string' ? protocol.title : '', `${at}.title`);
|
|
59
|
+
if (typeof protocol.title !== 'string' || protocol.title === '')
|
|
60
|
+
errors.push(`${at}.title must be a non-empty string`);
|
|
61
|
+
if (!Number.isInteger(protocol.steps) || (protocol.steps ?? 0) < 1)
|
|
62
|
+
errors.push(`${at}.steps must be a positive integer`);
|
|
63
|
+
if (protocol.artifact !== undefined && (typeof protocol.artifact !== 'string' || protocol.artifact === '')) {
|
|
64
|
+
errors.push(`${at}.artifact must be a non-empty string when present`);
|
|
65
|
+
}
|
|
66
|
+
else if (typeof protocol.artifact === 'string') {
|
|
67
|
+
// The file name may use the record's vars — that is how one run per input gets its own file —
|
|
68
|
+
// but never a runtime placeholder: the artifact must not move between rounds.
|
|
69
|
+
for (const name of placeholders(protocol.artifact)) {
|
|
70
|
+
if (!allowed.has(name) || runtime.has(name)) {
|
|
71
|
+
errors.push(`${at}.artifact: {{${name}}} must be a record var; the artifact may not depend on the round`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (protocol.artifactMarker !== undefined) {
|
|
76
|
+
if (typeof protocol.artifactMarker !== 'string' || protocol.artifactMarker.trim() === '') {
|
|
77
|
+
errors.push(`${at}.artifactMarker must be a non-empty string when present`);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
checkPlaceholders(protocol.artifactMarker, `${at}.artifactMarker`);
|
|
81
|
+
}
|
|
82
|
+
// A marker without a file to look in cannot be checked, so the pair is required together.
|
|
83
|
+
if (typeof protocol.artifact !== 'string' || protocol.artifact === '') {
|
|
84
|
+
errors.push(`${at}.artifactMarker needs ${at}.artifact: there is no file to look in`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (typeof protocol.fallbackLabel !== 'string' || protocol.fallbackLabel === '')
|
|
88
|
+
errors.push(`${at}.fallbackLabel must be a non-empty string`);
|
|
89
|
+
if (protocol.standard !== undefined && (typeof protocol.standard !== 'string' || protocol.standard.trim() === '')) {
|
|
90
|
+
errors.push(`${at}.standard must be a non-empty string when present`);
|
|
91
|
+
}
|
|
92
|
+
if (protocol.starts !== undefined && protocol.starts !== 'verify' && protocol.starts !== 'work') {
|
|
93
|
+
errors.push(`${at}.starts must be 'verify' or 'work'`);
|
|
94
|
+
}
|
|
95
|
+
if (protocol.defaults !== undefined) {
|
|
96
|
+
const { score, tries } = protocol.defaults;
|
|
97
|
+
if (score !== undefined && (!Number.isFinite(score) || score < 0 || score > 10))
|
|
98
|
+
errors.push(`${at}.defaults.score must be a number in 0-10`);
|
|
99
|
+
if (tries !== undefined && (!Number.isInteger(tries) || tries < 1))
|
|
100
|
+
errors.push(`${at}.defaults.tries must be a positive integer`);
|
|
101
|
+
}
|
|
102
|
+
const rounds = protocol.rounds;
|
|
103
|
+
if (!Array.isArray(rounds)) {
|
|
104
|
+
errors.push(`${at}.rounds must be a list`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
if (rounds.length > 0 && rounds.length !== protocol.steps) {
|
|
108
|
+
errors.push(`${at}.rounds has ${rounds.length} entries but steps is ${protocol.steps}`);
|
|
109
|
+
}
|
|
110
|
+
rounds.forEach((round, index) => {
|
|
111
|
+
if (typeof round?.title !== 'string' || round.title === '')
|
|
112
|
+
errors.push(`${at}.rounds[${index}].title must be a non-empty string`);
|
|
113
|
+
if (typeof round?.checks !== 'string' || round.checks === '')
|
|
114
|
+
errors.push(`${at}.rounds[${index}].checks must be a non-empty string`);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
for (const key of ['brief', 'followUp']) {
|
|
118
|
+
const lines = protocol[key];
|
|
119
|
+
if (!Array.isArray(lines) || lines.length === 0) {
|
|
120
|
+
errors.push(`${at}.${key} must be a non-empty list`);
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
lines.forEach((line, index) => {
|
|
124
|
+
if (typeof line !== 'string') {
|
|
125
|
+
errors.push(`${at}.${key}[${index}] must be a string`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
checkPlaceholders(line, `${at}.${key}[${index}]`);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const checksLines = (protocol.brief ?? []).filter(line => typeof line === 'string' && line.trim() === '{{checks}}');
|
|
132
|
+
if ((rounds?.length ?? 0) > 0 && checksLines.length !== 1)
|
|
133
|
+
errors.push(`${at}.brief must contain exactly one line that is just {{checks}}`);
|
|
134
|
+
if ((rounds?.length ?? 0) === 0 && checksLines.length > 0)
|
|
135
|
+
errors.push(`${at}.brief uses {{checks}} but defines no rounds`);
|
|
136
|
+
if (protocol.verifyFocus !== undefined) {
|
|
137
|
+
if (typeof protocol.verifyFocus !== 'string' || protocol.verifyFocus === '')
|
|
138
|
+
errors.push(`${at}.verifyFocus must be a non-empty string when present`);
|
|
139
|
+
else
|
|
140
|
+
checkPlaceholders(protocol.verifyFocus, `${at}.verifyFocus`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return errors;
|
|
144
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type { LoopPromptSource, LoopProtocolText, LoopRoundText } from './loop-prompts-schema.ts';
|
|
2
|
+
/** Values one render call supplies; a record's `vars` are merged in on top of these. */
|
|
3
|
+
export interface LoopPromptValues {
|
|
4
|
+
from: number;
|
|
5
|
+
to: number;
|
|
6
|
+
score: number;
|
|
7
|
+
tries: number;
|
|
8
|
+
step: number;
|
|
9
|
+
attempt: number;
|
|
10
|
+
}
|
|
11
|
+
/** The rendering API one record uses. */
|
|
12
|
+
export interface LoopPromptText {
|
|
13
|
+
readonly kind: string;
|
|
14
|
+
/** Progress label, already rendered with the record's vars. */
|
|
15
|
+
readonly title: string;
|
|
16
|
+
readonly steps: number;
|
|
17
|
+
readonly artifact?: string;
|
|
18
|
+
/** Rendered line the artifact must contain for one step, when the record declares one. */
|
|
19
|
+
artifactMarker(step: number): string | undefined;
|
|
20
|
+
/** Extra requirements folded on top of every round's rubric, when the record declares them. */
|
|
21
|
+
readonly standard?: string;
|
|
22
|
+
/** This record's fixed inputs as rendered, with any per-run override already applied. */
|
|
23
|
+
readonly vars: Readonly<Record<string, string>>;
|
|
24
|
+
readonly defaultScore: number;
|
|
25
|
+
readonly defaultTries: number;
|
|
26
|
+
/** Which phase a step starts in; absent means the protocol asks for work first. */
|
|
27
|
+
readonly starts?: 'verify' | 'work';
|
|
28
|
+
/** Round label; steps outside the table get the fallback, step 0 the empty string. */
|
|
29
|
+
roundTitle(step: number): string;
|
|
30
|
+
/** That round's checklist; steps past the table reuse the last round's, step 0 has none. */
|
|
31
|
+
checks(step: number): string;
|
|
32
|
+
/** What the forked verifier is told this step is about, when the record names it. */
|
|
33
|
+
focus(step: number): string | undefined;
|
|
34
|
+
/** The opening prompt for one attempt, still missing the result contract the caller appends. */
|
|
35
|
+
brief(values: LoopPromptValues): string[];
|
|
36
|
+
/** The shorter prompt for a later attempt on the same step. */
|
|
37
|
+
followUp(values: LoopPromptValues): string[];
|
|
38
|
+
}
|
|
39
|
+
/** Every record's rendered prompts and its static inputs. */
|
|
40
|
+
export interface LoopPrompts {
|
|
41
|
+
/** Record names in file order, which is the order `/loop` lists them in an error. */
|
|
42
|
+
names: readonly string[];
|
|
43
|
+
/** One record, or undefined when no record has that name.
|
|
44
|
+
*
|
|
45
|
+
* @param kind - Record key.
|
|
46
|
+
* @param overrides - Values that replace the record's own `vars` for this rendering only.
|
|
47
|
+
*/
|
|
48
|
+
find(kind: string, overrides?: Readonly<Record<string, string>>): LoopPromptText | undefined;
|
|
49
|
+
/** One record's declared `vars`, unrendered, so a form can offer them before a run starts. */
|
|
50
|
+
vars(kind: string): Readonly<Record<string, string>>;
|
|
51
|
+
}
|
|
52
|
+
/** The records of loop.yaml.
|
|
53
|
+
* @returns Names, lookups and the declared vars of each record, built once.
|
|
54
|
+
*/
|
|
55
|
+
export declare function loopPrompts(): LoopPrompts;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export declare const LOOP_PROMPTS: {
|
|
2
|
+
readonly version: 1;
|
|
3
|
+
readonly defaults: {
|
|
4
|
+
readonly score: 8;
|
|
5
|
+
readonly tries: 10;
|
|
6
|
+
};
|
|
7
|
+
readonly protocols: {
|
|
8
|
+
readonly "design-review": {
|
|
9
|
+
readonly title: "Design review";
|
|
10
|
+
readonly steps: 10;
|
|
11
|
+
readonly starts: "verify";
|
|
12
|
+
readonly artifact: "DESIGN-REVIEW.md";
|
|
13
|
+
readonly artifactMarker: "## 第 {{step}} 轮 · {{title}}";
|
|
14
|
+
readonly defaults: {
|
|
15
|
+
readonly score: 8;
|
|
16
|
+
readonly tries: 10;
|
|
17
|
+
};
|
|
18
|
+
readonly fallbackLabel: "收敛审查";
|
|
19
|
+
readonly verifyFocus: "第 {{step}} 轮 · {{title}}";
|
|
20
|
+
readonly rounds: readonly [{
|
|
21
|
+
readonly title: "职责与归属";
|
|
22
|
+
readonly checks: "逐个模块/类/服务/Store/Controller/组件:它为什么存在?核心职责能否一句话说明?是否只有一个变化原因?是否混合了不同生命周期的职责?是否因\"方便统一管理\"承担了不属于自己的职责?状态与行为真正属于谁?是否有 God Object/God Module 趋势?重点找:表现层状态进入业务层、基础设施细节进入业务模型、一个模块同时承担数据/控制/格式化/网络/持久化、顶层协调器做了本应模块自己做的事、通用模块变成所有功能的中心。";
|
|
23
|
+
}, {
|
|
24
|
+
readonly title: "依赖关系";
|
|
25
|
+
readonly checks: "按真实 import/调用/类型引用画依赖图,不看目录名。逐条检查:不必要依赖?同层横向依赖?隐藏的 type-only 依赖?上层直接依赖底层实现?底层反向理解上层业务?表现层直接依赖业务服务或基础设施?是否用 common/shared/types/barrel/facade/helper 隐藏真实耦合?特别警惕\"只是 import type\"\"只是 getter\"\"只是 facade\"\"只是 barrel\"\"只是公共 helper\"。";
|
|
26
|
+
}, {
|
|
27
|
+
readonly title: "接口审查";
|
|
28
|
+
readonly checks: "逐个检查 public API、参数、返回值、props 与跨模块数据:调用者真的需要整个对象吗?只用到几个字段吗?是否获得了超出需要的能力?是否暴露内部实现类型或 mutable state?能否换成更小、更稳定、更语义化的数据结构?参数表达的是业务意图还是实现方式?优先 plain object、readonly array、判别联合、最小 callback、语义化 DTO/Snapshot/Result;避免跨边界传 Controller/Service 实例、数据库/Client/Connection、内部状态容器、mutable collection、原始协议对象、Json/any/unknown 逃生口、巨大 Context/Actions/Manager。遵循最小能力原则。";
|
|
29
|
+
}, {
|
|
30
|
+
readonly title: "状态审查";
|
|
31
|
+
readonly checks: "对每个状态问:谁拥有、谁修改、谁读取、生命周期是什么、是否需要持久化、是否需要跨模块共享、能否由其他状态计算得到?状态应靠近真正使用者:组件能持有就不上提模块 Store,模块能持有就不上提全局 Store,能派生就不重复存储。重点检查 source state 与 derived state 是否被同时长期保存,避免多个事实源。";
|
|
32
|
+
}, {
|
|
33
|
+
readonly title: "变化传播测试";
|
|
34
|
+
readonly checks: "不要只看\"能工作\",要测变化传播多远。A 外部协议/API/数据格式变化:理想只影响 infrastructure/adapter/mapper,若业务与 UI 大面积修改说明协议泄漏。B UI/表现形式变化(布局、CLI→Web、状态栏字段、颜色/排序/折叠):理想只影响表现层,若业务逻辑随之改动说明表现语义泄漏。C 核心业务规则变化(算法、计费、校验、状态机):理想只影响对应功能模块。D 新增一个功能:需要改多少旧模块?是否必须改巨大中央 Controller 或 shared/common/core?是否要在多处加同一个 case?理想是新增代码多、修改旧代码少。";
|
|
35
|
+
}, {
|
|
36
|
+
readonly title: "删除式审查";
|
|
37
|
+
readonly checks: "假设当前方案\"基本正确\",专门寻找可以删除的东西:哪个目录/interface/abstraction 没有独立语义?哪个 facade 只是原样转发?哪个 barrel 只隐藏真实路径?哪个 Store abstraction 只是重复 get/set/update?哪个 ViewModel/read-model/adapter 是不必要的中间层?哪个 Controller 方法应该消失?哪个 DTO 只为满足架构形式而存在?哪个公共类型只为绕开依赖规则?哪些只为\"看起来更分层\"?原则:若 A→B→C 而 B 只原样转发/返回、无独立策略与稳定语义、不隔离变化,优先 A→C。";
|
|
38
|
+
}, {
|
|
39
|
+
readonly title: "反证当前方案";
|
|
40
|
+
readonly checks: "假设当前方案是错的,主动寻找它未来最可能坏掉的方式:哪个新模块会成为下一代 God Object?哪个 Store 会成为所有状态的垃圾场?哪个 Controller 会重新变成所有功能入口?哪个 shared/common/types 会变成公共垃圾场?哪个 read-model 会成为所有数据的中央聚合器?哪个 UI root 会重新成为巨型状态机?哪个 Snapshot 暴露过多内部信息?哪个\"解耦层\"只是把依赖搬到了别处?是否为了禁止依赖制造大量 DTO/Adapter/Port?是否为了低耦合增加过多中间层?是否为了满足模式提高理解与修改成本?对每个发现继续问:能否通过删除而不是新增架构来解决?";
|
|
41
|
+
}, {
|
|
42
|
+
readonly title: "一致性检查";
|
|
43
|
+
readonly checks: "把设计文档、接口定义、依赖规则与状态归属交叉检查,列出所有矛盾:原则说 A 不能依赖 B 但代码或依赖表允许;声称边界是 plain data 但接口仍暴露内部对象;声称 Query 无副作用但实际写操作;声称某层不知道某模块但通过 types/barrel 间接引用;同一状态在不同章节归属不同;同一概念有多个事实源;架构图与实际 import 方向不一致;命名表达的职责与真实职责不一致。";
|
|
44
|
+
}, {
|
|
45
|
+
readonly title: "过度设计检查";
|
|
46
|
+
readonly checks: "单独检查是否超过实际问题所需复杂度:当前规模真的需要这些层吗?这个 abstraction 今天解决了什么具体问题?删除它真正会失去什么?是否只为未来\"可能\"的需求?能否等第二个真实用例出现再抽象?是否为了测试而制造生产代码复杂度?是否为了依赖倒置创建大量同形接口?是否为了目录整齐增加无语义的层?遵循 Rule of Three:第一次直接实现,第二次允许少量重复,第三次模式稳定后再抽象。";
|
|
47
|
+
}, {
|
|
48
|
+
readonly title: "最终收敛";
|
|
49
|
+
readonly checks: "完成前面所有检查后重新整体审查一次,禁止再引入新的架构模式。只判断:当前设计是否已经足够简单?哪些是 P0 必须修改?哪些是 P1 值得修改?哪些只是理论洁癖应保持现状?哪些抽象应该删除?哪些状态应该移动?哪些接口应该缩小?哪些依赖应该禁止?最终推荐的依赖关系是什么?是否已到\"停止设计、开始实施\"的阶段?";
|
|
50
|
+
}];
|
|
51
|
+
readonly brief: readonly ["你是一名资深软件架构师。请对下面的软件架构、模块设计、代码组织或重构方案进行系统审查。", "", "审查范围:第 {{from}} 轮到第 {{to}} 轮;每轮及格线 {{score}} 分(0–10,允许小数);每轮最多 {{tries}} 次尝试。", "本次只执行第 {{step}} 轮的第 {{attempt}} 次尝试。完成这一轮后立即停止,不要自行进入后续轮次或重复尝试。", "本轮通过只代表本轮达标,不代表整个目标通过;尚未运行的轮次仍然必须运行。", "", "你的目标不是套用 MVC、DDD、Clean Architecture、Hexagonal、CQRS、DI 等架构模式,而是寻找最简单、最稳定、最容易长期维护的边界。", "", "优先目标(冲突时按此顺序决策):", "1 高内聚 > 模式完整;2 低耦合 > 目录漂亮;3 最小接口 > 通用接口;4 明确归属 > 全局统一;", "5 局部状态 > 全局状态;6 单一事实源 > 同步多个副本;7 删除抽象 > 新增抽象;8 真实需求 > 未来假设;", "9 可维护性 > 理论纯洁;10 简单直接 > 架构炫技。", "不要因为模式名词本身而引入复杂度;只有某个模式确实解决已存在的问题时才使用。", "", "本轮主题:第 {{step}} 轮 · {{title}}", "本轮检查要点:", "{{checks}}", "", "输出要求:不要输出冗长的内部思维过程,只输出——", "- 发现的问题", "- 判断依据", "- 修改建议", "- 修改后减少了什么耦合或复杂度", "- 本轮收敛结论", "", "每轮结论必须写入工作区文件 {{artifact}} 的 “## 第 {{step}} 轮 · {{title}}” 小节:不存在则创建,已存在则替换该小节,不要覆盖其它轮次。验证者会直接读这个文件。若工作区不可写,则在正文给出完整内容并在 evidence 中说明。", "若本轮确实没有可改进项,请如实在 status 中给 done 并说明无需改进,不要为了触发重试而压低分数。"];
|
|
52
|
+
readonly followUp: readonly ["现在是第 {{step}} 轮、第 {{attempt}}/{{tries}} 次尝试(及格线 {{score}})。", "阅读上面的结果、意见与建议,按第 {{step}} 轮({{title}})的要求继续改进;", "上一版未解决、未回应的 blocking 问题必须逐条处理,并更新工作区文件 {{artifact}} 中本轮的小节。"];
|
|
53
|
+
};
|
|
54
|
+
readonly "designdoc-review": {
|
|
55
|
+
readonly title: "Designdoc review · {{path}}";
|
|
56
|
+
readonly steps: 10;
|
|
57
|
+
readonly starts: "verify";
|
|
58
|
+
readonly artifact: "{{path}}.review.md";
|
|
59
|
+
readonly artifactMarker: "## 第 {{step}} 轮 · {{title}} · {{path}}";
|
|
60
|
+
readonly vars: {
|
|
61
|
+
readonly path: "tui-design.md";
|
|
62
|
+
};
|
|
63
|
+
readonly defaults: {
|
|
64
|
+
readonly score: 8;
|
|
65
|
+
readonly tries: 10;
|
|
66
|
+
};
|
|
67
|
+
readonly fallbackLabel: "收敛结论";
|
|
68
|
+
readonly verifyFocus: "第 {{step}} 轮 · {{title}}";
|
|
69
|
+
readonly rounds: readonly [{
|
|
70
|
+
readonly title: "定位与范围";
|
|
71
|
+
readonly checks: "这份文档为谁写、承诺记录什么/不记录什么、覆盖哪些模块与边界?是否有明确的边界声明(本文不是规范/不覆盖什么)?与 README、CONTRIBUTING 的分工是否说清?若读者只关心一个子系统,能否从目录直接找到入口?";
|
|
72
|
+
}, {
|
|
73
|
+
readonly title: "结构与导航";
|
|
74
|
+
readonly checks: "章节层级是否可预期、能否只读一节就完成一项任务?术语是否统一并有定义或术语表?交叉引用是否指向存在的锚点/章节?篇幅与信息密度是否匹配,是否有大段可压缩的叙述?";
|
|
75
|
+
}, {
|
|
76
|
+
readonly title: "与代码一致性";
|
|
77
|
+
readonly checks: "把文档里的每条结构性声明与实际代码对照:目录依赖规则与 import 边界、导出符号与文件清单、状态归属与生命周期、命令表与 CLI 选项、宿主端点与帧结构。逐条列出「文档说 A、代码做 B」的差异,并给出代码侧证据(文件:符号)。";
|
|
78
|
+
}, {
|
|
79
|
+
readonly title: "完整性与悬空引用";
|
|
80
|
+
readonly checks: "文档引用的文件、符号、章节、命令、参数是否存在?关键决策是否记录了原因与取舍,而不只是结论?反过来看:代码里重要的机制(谁拥有状态、谁负责回收、失败如何传播)是否在文档中有位置?列出悬空引用与缺失章节。";
|
|
81
|
+
}, {
|
|
82
|
+
readonly title: "准确性";
|
|
83
|
+
readonly checks: "具体数字(行数、文件数、条数、上限、默认值)、路径、命令、参数、时序(谁在何时写、何时释放)是否与实现一致?示例命令能否直接运行?表格里的计数是否与当前 HEAD 相符?逐条给出实测值与文档值。";
|
|
84
|
+
}, {
|
|
85
|
+
readonly title: "单一事实源";
|
|
86
|
+
readonly checks: "同一概念是否在多处重复定义、可能互相漂移(例如同一状态在不同章节归属不同、同一默认值写了两遍、同一路径出现三种写法)?是否有一处权威定义、其它处只引用?指出所有多事实源并建议唯一的归属位置。";
|
|
87
|
+
}, {
|
|
88
|
+
readonly title: "可维护性";
|
|
89
|
+
readonly checks: "文档是否写明「什么变化必须同步更新本文」?易腐内容(行数、文件计数、依赖表、截图)是否有维护约定或生成方式?更新一次的成本是否被控制(能否只改一处)?哪些内容应当改为指向代码/生成物而不是复制?";
|
|
90
|
+
}, {
|
|
91
|
+
readonly title: "可执行性";
|
|
92
|
+
readonly checks: "一个新人能否照着文档跑起来、定位代码、完成一次改动?是否给出验证方式(命令、测试、断言)?是否存在只有作者才懂的隐含前提(环境变量、前置步骤、未写明的约定)?把最小可执行路径写出来并指出缺口。";
|
|
93
|
+
}, {
|
|
94
|
+
readonly title: "删减与过时";
|
|
95
|
+
readonly checks: "与当前 HEAD 漂移的历史章节、已被取代的决策、重复段落、失去意义的例子分别是什么?哪些内容删除后不损失信息?哪些\"未来计划\"只是假设、应当删除或标注状态?给出可删除清单与理由。";
|
|
96
|
+
}, {
|
|
97
|
+
readonly title: "收敛结论";
|
|
98
|
+
readonly checks: "完成前面所有检查后重新整体判断,不要引入新的文档结构:P0 必须修改(会导致错误理解或错误实现)、P1 值得修改、保持现状(继续改属于过度设计)、可以删除的章节/段落/表格。最后回答:这份文档是否已经足够支撑维护,还是仍需要补充设计?";
|
|
99
|
+
}];
|
|
100
|
+
readonly brief: readonly ["你是一名资深软件架构师兼技术文档维护者。请对工作区中的设计文档 `{{path}}` 做系统审查,判断它是否准确、完整、可维护,并与当前代码一致。", "", "审查范围:第 {{from}} 轮到第 {{to}} 轮;每轮及格线 {{score}} 分(0–10,允许小数);每轮最多 {{tries}} 次尝试。", "本次只执行第 {{step}} 轮的第 {{attempt}} 次尝试。完成这一轮后立即停止,不要自行进入后续轮次或重复尝试。", "本轮通过只代表本轮达标,不代表整个目标通过;尚未运行的轮次仍然必须运行。", "", "判断原则(冲突时按此顺序):准确 > 完整 > 简洁;与代码一致 > 文采;单一事实源 > 多处重复;可维护 > 面面俱到;能删除 > 新增章节。不要为了\"看起来更完整\"而增加无人维护的内容。", "", "本轮主题:第 {{step}} 轮 · {{title}}", "本轮检查要点:", "{{checks}}", "", "输出要求:不要输出冗长的内部思维过程,只输出——", "- 发现的问题(逐条,附文档位置与代码/实测证据)", "- 判断依据", "- 修改建议(具体到章节与改法)", "- 修改后减少了什么维护风险", "- 本轮收敛结论", "", "每轮结论必须写入工作区文件 {{artifact}} 的 “## 第 {{step}} 轮 · {{title}} · {{path}}” 小节:不存在则创建,已存在则替换该小节,不要覆盖其它轮次(标题里的 {{path}} 就是本次 run 的被评审文档,换文档审查时不要改别的文档的小节)。验证者会直接读这个文件。若工作区不可写,则在正文给出完整内容并在 evidence 中说明。", "若本轮确实没有可改进项,请如实在 status 中给 done 并说明无需改进,不要为了触发重试而压低分数。"];
|
|
101
|
+
readonly followUp: readonly ["现在是第 {{step}} 轮、第 {{attempt}}/{{tries}} 次尝试(及格线 {{score}})。", "以 {{path}} 为准,按第 {{step}} 轮({{title}})的要求处理上一版未解决的问题,并更新工作区文件 {{artifact}} 中 “## 第 {{step}} 轮 · {{title}} · {{path}}” 小节。"];
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit. Edit loop.yaml and run `npm run build:prompts`.
|
|
2
|
+
// Kept in sync by tests/controller/loop-prompts.test.ts.
|
|
3
|
+
export const LOOP_PROMPTS = {
|
|
4
|
+
"version": 1,
|
|
5
|
+
"defaults": {
|
|
6
|
+
"score": 8,
|
|
7
|
+
"tries": 10
|
|
8
|
+
},
|
|
9
|
+
"protocols": {
|
|
10
|
+
"design-review": {
|
|
11
|
+
"title": "Design review",
|
|
12
|
+
"steps": 10,
|
|
13
|
+
"starts": "verify",
|
|
14
|
+
"artifact": "DESIGN-REVIEW.md",
|
|
15
|
+
"artifactMarker": "## 第 {{step}} 轮 · {{title}}",
|
|
16
|
+
"defaults": {
|
|
17
|
+
"score": 8,
|
|
18
|
+
"tries": 10
|
|
19
|
+
},
|
|
20
|
+
"fallbackLabel": "收敛审查",
|
|
21
|
+
"verifyFocus": "第 {{step}} 轮 · {{title}}",
|
|
22
|
+
"rounds": [
|
|
23
|
+
{
|
|
24
|
+
"title": "职责与归属",
|
|
25
|
+
"checks": "逐个模块/类/服务/Store/Controller/组件:它为什么存在?核心职责能否一句话说明?是否只有一个变化原因?是否混合了不同生命周期的职责?是否因\"方便统一管理\"承担了不属于自己的职责?状态与行为真正属于谁?是否有 God Object/God Module 趋势?重点找:表现层状态进入业务层、基础设施细节进入业务模型、一个模块同时承担数据/控制/格式化/网络/持久化、顶层协调器做了本应模块自己做的事、通用模块变成所有功能的中心。"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"title": "依赖关系",
|
|
29
|
+
"checks": "按真实 import/调用/类型引用画依赖图,不看目录名。逐条检查:不必要依赖?同层横向依赖?隐藏的 type-only 依赖?上层直接依赖底层实现?底层反向理解上层业务?表现层直接依赖业务服务或基础设施?是否用 common/shared/types/barrel/facade/helper 隐藏真实耦合?特别警惕\"只是 import type\"\"只是 getter\"\"只是 facade\"\"只是 barrel\"\"只是公共 helper\"。"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"title": "接口审查",
|
|
33
|
+
"checks": "逐个检查 public API、参数、返回值、props 与跨模块数据:调用者真的需要整个对象吗?只用到几个字段吗?是否获得了超出需要的能力?是否暴露内部实现类型或 mutable state?能否换成更小、更稳定、更语义化的数据结构?参数表达的是业务意图还是实现方式?优先 plain object、readonly array、判别联合、最小 callback、语义化 DTO/Snapshot/Result;避免跨边界传 Controller/Service 实例、数据库/Client/Connection、内部状态容器、mutable collection、原始协议对象、Json/any/unknown 逃生口、巨大 Context/Actions/Manager。遵循最小能力原则。"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"title": "状态审查",
|
|
37
|
+
"checks": "对每个状态问:谁拥有、谁修改、谁读取、生命周期是什么、是否需要持久化、是否需要跨模块共享、能否由其他状态计算得到?状态应靠近真正使用者:组件能持有就不上提模块 Store,模块能持有就不上提全局 Store,能派生就不重复存储。重点检查 source state 与 derived state 是否被同时长期保存,避免多个事实源。"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"title": "变化传播测试",
|
|
41
|
+
"checks": "不要只看\"能工作\",要测变化传播多远。A 外部协议/API/数据格式变化:理想只影响 infrastructure/adapter/mapper,若业务与 UI 大面积修改说明协议泄漏。B UI/表现形式变化(布局、CLI→Web、状态栏字段、颜色/排序/折叠):理想只影响表现层,若业务逻辑随之改动说明表现语义泄漏。C 核心业务规则变化(算法、计费、校验、状态机):理想只影响对应功能模块。D 新增一个功能:需要改多少旧模块?是否必须改巨大中央 Controller 或 shared/common/core?是否要在多处加同一个 case?理想是新增代码多、修改旧代码少。"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"title": "删除式审查",
|
|
45
|
+
"checks": "假设当前方案\"基本正确\",专门寻找可以删除的东西:哪个目录/interface/abstraction 没有独立语义?哪个 facade 只是原样转发?哪个 barrel 只隐藏真实路径?哪个 Store abstraction 只是重复 get/set/update?哪个 ViewModel/read-model/adapter 是不必要的中间层?哪个 Controller 方法应该消失?哪个 DTO 只为满足架构形式而存在?哪个公共类型只为绕开依赖规则?哪些只为\"看起来更分层\"?原则:若 A→B→C 而 B 只原样转发/返回、无独立策略与稳定语义、不隔离变化,优先 A→C。"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"title": "反证当前方案",
|
|
49
|
+
"checks": "假设当前方案是错的,主动寻找它未来最可能坏掉的方式:哪个新模块会成为下一代 God Object?哪个 Store 会成为所有状态的垃圾场?哪个 Controller 会重新变成所有功能入口?哪个 shared/common/types 会变成公共垃圾场?哪个 read-model 会成为所有数据的中央聚合器?哪个 UI root 会重新成为巨型状态机?哪个 Snapshot 暴露过多内部信息?哪个\"解耦层\"只是把依赖搬到了别处?是否为了禁止依赖制造大量 DTO/Adapter/Port?是否为了低耦合增加过多中间层?是否为了满足模式提高理解与修改成本?对每个发现继续问:能否通过删除而不是新增架构来解决?"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"title": "一致性检查",
|
|
53
|
+
"checks": "把设计文档、接口定义、依赖规则与状态归属交叉检查,列出所有矛盾:原则说 A 不能依赖 B 但代码或依赖表允许;声称边界是 plain data 但接口仍暴露内部对象;声称 Query 无副作用但实际写操作;声称某层不知道某模块但通过 types/barrel 间接引用;同一状态在不同章节归属不同;同一概念有多个事实源;架构图与实际 import 方向不一致;命名表达的职责与真实职责不一致。"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"title": "过度设计检查",
|
|
57
|
+
"checks": "单独检查是否超过实际问题所需复杂度:当前规模真的需要这些层吗?这个 abstraction 今天解决了什么具体问题?删除它真正会失去什么?是否只为未来\"可能\"的需求?能否等第二个真实用例出现再抽象?是否为了测试而制造生产代码复杂度?是否为了依赖倒置创建大量同形接口?是否为了目录整齐增加无语义的层?遵循 Rule of Three:第一次直接实现,第二次允许少量重复,第三次模式稳定后再抽象。"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"title": "最终收敛",
|
|
61
|
+
"checks": "完成前面所有检查后重新整体审查一次,禁止再引入新的架构模式。只判断:当前设计是否已经足够简单?哪些是 P0 必须修改?哪些是 P1 值得修改?哪些只是理论洁癖应保持现状?哪些抽象应该删除?哪些状态应该移动?哪些接口应该缩小?哪些依赖应该禁止?最终推荐的依赖关系是什么?是否已到\"停止设计、开始实施\"的阶段?"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"brief": [
|
|
65
|
+
"你是一名资深软件架构师。请对下面的软件架构、模块设计、代码组织或重构方案进行系统审查。",
|
|
66
|
+
"",
|
|
67
|
+
"审查范围:第 {{from}} 轮到第 {{to}} 轮;每轮及格线 {{score}} 分(0–10,允许小数);每轮最多 {{tries}} 次尝试。",
|
|
68
|
+
"本次只执行第 {{step}} 轮的第 {{attempt}} 次尝试。完成这一轮后立即停止,不要自行进入后续轮次或重复尝试。",
|
|
69
|
+
"本轮通过只代表本轮达标,不代表整个目标通过;尚未运行的轮次仍然必须运行。",
|
|
70
|
+
"",
|
|
71
|
+
"你的目标不是套用 MVC、DDD、Clean Architecture、Hexagonal、CQRS、DI 等架构模式,而是寻找最简单、最稳定、最容易长期维护的边界。",
|
|
72
|
+
"",
|
|
73
|
+
"优先目标(冲突时按此顺序决策):",
|
|
74
|
+
"1 高内聚 > 模式完整;2 低耦合 > 目录漂亮;3 最小接口 > 通用接口;4 明确归属 > 全局统一;",
|
|
75
|
+
"5 局部状态 > 全局状态;6 单一事实源 > 同步多个副本;7 删除抽象 > 新增抽象;8 真实需求 > 未来假设;",
|
|
76
|
+
"9 可维护性 > 理论纯洁;10 简单直接 > 架构炫技。",
|
|
77
|
+
"不要因为模式名词本身而引入复杂度;只有某个模式确实解决已存在的问题时才使用。",
|
|
78
|
+
"",
|
|
79
|
+
"本轮主题:第 {{step}} 轮 · {{title}}",
|
|
80
|
+
"本轮检查要点:",
|
|
81
|
+
"{{checks}}",
|
|
82
|
+
"",
|
|
83
|
+
"输出要求:不要输出冗长的内部思维过程,只输出——",
|
|
84
|
+
"- 发现的问题",
|
|
85
|
+
"- 判断依据",
|
|
86
|
+
"- 修改建议",
|
|
87
|
+
"- 修改后减少了什么耦合或复杂度",
|
|
88
|
+
"- 本轮收敛结论",
|
|
89
|
+
"",
|
|
90
|
+
"每轮结论必须写入工作区文件 {{artifact}} 的 “## 第 {{step}} 轮 · {{title}}” 小节:不存在则创建,已存在则替换该小节,不要覆盖其它轮次。验证者会直接读这个文件。若工作区不可写,则在正文给出完整内容并在 evidence 中说明。",
|
|
91
|
+
"若本轮确实没有可改进项,请如实在 status 中给 done 并说明无需改进,不要为了触发重试而压低分数。"
|
|
92
|
+
],
|
|
93
|
+
"followUp": [
|
|
94
|
+
"现在是第 {{step}} 轮、第 {{attempt}}/{{tries}} 次尝试(及格线 {{score}})。",
|
|
95
|
+
"阅读上面的结果、意见与建议,按第 {{step}} 轮({{title}})的要求继续改进;",
|
|
96
|
+
"上一版未解决、未回应的 blocking 问题必须逐条处理,并更新工作区文件 {{artifact}} 中本轮的小节。"
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
"designdoc-review": {
|
|
100
|
+
"title": "Designdoc review · {{path}}",
|
|
101
|
+
"steps": 10,
|
|
102
|
+
"starts": "verify",
|
|
103
|
+
"artifact": "{{path}}.review.md",
|
|
104
|
+
"artifactMarker": "## 第 {{step}} 轮 · {{title}} · {{path}}",
|
|
105
|
+
"vars": {
|
|
106
|
+
"path": "tui-design.md"
|
|
107
|
+
},
|
|
108
|
+
"defaults": {
|
|
109
|
+
"score": 8,
|
|
110
|
+
"tries": 10
|
|
111
|
+
},
|
|
112
|
+
"fallbackLabel": "收敛结论",
|
|
113
|
+
"verifyFocus": "第 {{step}} 轮 · {{title}}",
|
|
114
|
+
"rounds": [
|
|
115
|
+
{
|
|
116
|
+
"title": "定位与范围",
|
|
117
|
+
"checks": "这份文档为谁写、承诺记录什么/不记录什么、覆盖哪些模块与边界?是否有明确的边界声明(本文不是规范/不覆盖什么)?与 README、CONTRIBUTING 的分工是否说清?若读者只关心一个子系统,能否从目录直接找到入口?"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"title": "结构与导航",
|
|
121
|
+
"checks": "章节层级是否可预期、能否只读一节就完成一项任务?术语是否统一并有定义或术语表?交叉引用是否指向存在的锚点/章节?篇幅与信息密度是否匹配,是否有大段可压缩的叙述?"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"title": "与代码一致性",
|
|
125
|
+
"checks": "把文档里的每条结构性声明与实际代码对照:目录依赖规则与 import 边界、导出符号与文件清单、状态归属与生命周期、命令表与 CLI 选项、宿主端点与帧结构。逐条列出「文档说 A、代码做 B」的差异,并给出代码侧证据(文件:符号)。"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"title": "完整性与悬空引用",
|
|
129
|
+
"checks": "文档引用的文件、符号、章节、命令、参数是否存在?关键决策是否记录了原因与取舍,而不只是结论?反过来看:代码里重要的机制(谁拥有状态、谁负责回收、失败如何传播)是否在文档中有位置?列出悬空引用与缺失章节。"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"title": "准确性",
|
|
133
|
+
"checks": "具体数字(行数、文件数、条数、上限、默认值)、路径、命令、参数、时序(谁在何时写、何时释放)是否与实现一致?示例命令能否直接运行?表格里的计数是否与当前 HEAD 相符?逐条给出实测值与文档值。"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"title": "单一事实源",
|
|
137
|
+
"checks": "同一概念是否在多处重复定义、可能互相漂移(例如同一状态在不同章节归属不同、同一默认值写了两遍、同一路径出现三种写法)?是否有一处权威定义、其它处只引用?指出所有多事实源并建议唯一的归属位置。"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"title": "可维护性",
|
|
141
|
+
"checks": "文档是否写明「什么变化必须同步更新本文」?易腐内容(行数、文件计数、依赖表、截图)是否有维护约定或生成方式?更新一次的成本是否被控制(能否只改一处)?哪些内容应当改为指向代码/生成物而不是复制?"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"title": "可执行性",
|
|
145
|
+
"checks": "一个新人能否照着文档跑起来、定位代码、完成一次改动?是否给出验证方式(命令、测试、断言)?是否存在只有作者才懂的隐含前提(环境变量、前置步骤、未写明的约定)?把最小可执行路径写出来并指出缺口。"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"title": "删减与过时",
|
|
149
|
+
"checks": "与当前 HEAD 漂移的历史章节、已被取代的决策、重复段落、失去意义的例子分别是什么?哪些内容删除后不损失信息?哪些\"未来计划\"只是假设、应当删除或标注状态?给出可删除清单与理由。"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"title": "收敛结论",
|
|
153
|
+
"checks": "完成前面所有检查后重新整体判断,不要引入新的文档结构:P0 必须修改(会导致错误理解或错误实现)、P1 值得修改、保持现状(继续改属于过度设计)、可以删除的章节/段落/表格。最后回答:这份文档是否已经足够支撑维护,还是仍需要补充设计?"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
"brief": [
|
|
157
|
+
"你是一名资深软件架构师兼技术文档维护者。请对工作区中的设计文档 `{{path}}` 做系统审查,判断它是否准确、完整、可维护,并与当前代码一致。",
|
|
158
|
+
"",
|
|
159
|
+
"审查范围:第 {{from}} 轮到第 {{to}} 轮;每轮及格线 {{score}} 分(0–10,允许小数);每轮最多 {{tries}} 次尝试。",
|
|
160
|
+
"本次只执行第 {{step}} 轮的第 {{attempt}} 次尝试。完成这一轮后立即停止,不要自行进入后续轮次或重复尝试。",
|
|
161
|
+
"本轮通过只代表本轮达标,不代表整个目标通过;尚未运行的轮次仍然必须运行。",
|
|
162
|
+
"",
|
|
163
|
+
"判断原则(冲突时按此顺序):准确 > 完整 > 简洁;与代码一致 > 文采;单一事实源 > 多处重复;可维护 > 面面俱到;能删除 > 新增章节。不要为了\"看起来更完整\"而增加无人维护的内容。",
|
|
164
|
+
"",
|
|
165
|
+
"本轮主题:第 {{step}} 轮 · {{title}}",
|
|
166
|
+
"本轮检查要点:",
|
|
167
|
+
"{{checks}}",
|
|
168
|
+
"",
|
|
169
|
+
"输出要求:不要输出冗长的内部思维过程,只输出——",
|
|
170
|
+
"- 发现的问题(逐条,附文档位置与代码/实测证据)",
|
|
171
|
+
"- 判断依据",
|
|
172
|
+
"- 修改建议(具体到章节与改法)",
|
|
173
|
+
"- 修改后减少了什么维护风险",
|
|
174
|
+
"- 本轮收敛结论",
|
|
175
|
+
"",
|
|
176
|
+
"每轮结论必须写入工作区文件 {{artifact}} 的 “## 第 {{step}} 轮 · {{title}} · {{path}}” 小节:不存在则创建,已存在则替换该小节,不要覆盖其它轮次(标题里的 {{path}} 就是本次 run 的被评审文档,换文档审查时不要改别的文档的小节)。验证者会直接读这个文件。若工作区不可写,则在正文给出完整内容并在 evidence 中说明。",
|
|
177
|
+
"若本轮确实没有可改进项,请如实在 status 中给 done 并说明无需改进,不要为了触发重试而压低分数。"
|
|
178
|
+
],
|
|
179
|
+
"followUp": [
|
|
180
|
+
"现在是第 {{step}} 轮、第 {{attempt}}/{{tries}} 次尝试(及格线 {{score}})。",
|
|
181
|
+
"以 {{path}} 为准,按第 {{step}} 轮({{title}})的要求处理上一版未解决的问题,并更新工作区文件 {{artifact}} 中 “## 第 {{step}} 轮 · {{title}} · {{path}}” 小节。"
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/** Typed access to the loop prompts and the placeholder renderer.
|
|
2
|
+
*
|
|
3
|
+
* `loop.yaml` is the editable source; `loop-prompts.generated.ts` is its inlined form and
|
|
4
|
+
* `loop-prompts-schema.ts` holds the shape and the rules. This module turns one record into the
|
|
5
|
+
* strings a protocol needs, so the dynamic parts (this round's title, its checklist, the record's
|
|
6
|
+
* own vars) are filled here and nowhere else. A template may only use placeholders the caller can
|
|
7
|
+
* supply; anything else throws rather than putting a literal `{{name}}` into a prompt.
|
|
8
|
+
*/
|
|
9
|
+
import { LOOP_PROMPTS } from "./loop-prompts.generated.js";
|
|
10
|
+
const PLACEHOLDER = /\{\{(\w+)\}\}/g;
|
|
11
|
+
const SOURCE = LOOP_PROMPTS;
|
|
12
|
+
/** Replace every `{{name}}` in one template list.
|
|
13
|
+
* @param lines - Template lines.
|
|
14
|
+
* @param values - Values keyed by placeholder name.
|
|
15
|
+
* @param kind - Record the template came from, for the error message.
|
|
16
|
+
* @returns The rendered lines.
|
|
17
|
+
*/
|
|
18
|
+
function fill(lines, values, kind) {
|
|
19
|
+
return lines.map(line => line.replace(PLACEHOLDER, (_match, name) => {
|
|
20
|
+
const value = values[name];
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
throw new Error(`loop.yaml(${kind}): no value for {{${name}}}`);
|
|
23
|
+
return String(value);
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
/** Wrap one record in the renderer.
|
|
27
|
+
* @param kind - Record key in loop.yaml, also the loop's `kind`.
|
|
28
|
+
* @param protocol - The record itself.
|
|
29
|
+
* @param overrides - Values that replace the record's own `vars` for this rendering.
|
|
30
|
+
* @returns Its prompt text and renderer.
|
|
31
|
+
*/
|
|
32
|
+
function render(kind, protocol, overrides) {
|
|
33
|
+
const round = (step) => (step < 1 ? undefined : protocol.rounds[step - 1]);
|
|
34
|
+
const roundTitle = (step) => {
|
|
35
|
+
if (step < 1)
|
|
36
|
+
return '';
|
|
37
|
+
return round(step)?.title ?? protocol.fallbackLabel;
|
|
38
|
+
};
|
|
39
|
+
const checks = (step) => {
|
|
40
|
+
if (step < 1)
|
|
41
|
+
return '';
|
|
42
|
+
return round(step)?.checks ?? protocol.rounds[protocol.rounds.length - 1]?.checks ?? '';
|
|
43
|
+
};
|
|
44
|
+
// A per-run override wins over the record's declared value, so retargeting a record never edits it.
|
|
45
|
+
const vars = { ...(protocol.vars ?? {}), ...(overrides ?? {}) };
|
|
46
|
+
// The artifact is a template like the title: a record whose runs target different inputs names one
|
|
47
|
+
// file per input (`{{path}}.review.md`), so a run never reads another input's conclusions.
|
|
48
|
+
const artifact = protocol.artifact === undefined ? undefined : fill([protocol.artifact], { ...vars }, kind)[0];
|
|
49
|
+
const values = (runtime) => ({
|
|
50
|
+
...vars,
|
|
51
|
+
...runtime,
|
|
52
|
+
title: roundTitle(runtime.step),
|
|
53
|
+
artifact,
|
|
54
|
+
checks: checks(runtime.step),
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
kind,
|
|
58
|
+
// The title is static per run: it may use vars, never the step.
|
|
59
|
+
title: fill([protocol.title], { ...vars }, kind)[0],
|
|
60
|
+
steps: protocol.steps,
|
|
61
|
+
...(artifact === undefined ? {} : { artifact }),
|
|
62
|
+
...(protocol.standard === undefined ? {} : { standard: protocol.standard }),
|
|
63
|
+
vars,
|
|
64
|
+
defaultScore: protocol.defaults?.score ?? SOURCE.defaults.score,
|
|
65
|
+
defaultTries: protocol.defaults?.tries ?? SOURCE.defaults.tries,
|
|
66
|
+
...(protocol.starts === undefined ? {} : { starts: protocol.starts }),
|
|
67
|
+
roundTitle,
|
|
68
|
+
checks,
|
|
69
|
+
artifactMarker: protocol.artifactMarker === undefined ? () => undefined
|
|
70
|
+
: step => fill([protocol.artifactMarker], { ...vars, step, title: roundTitle(step), artifact }, kind)[0],
|
|
71
|
+
focus: step => protocol.verifyFocus === undefined
|
|
72
|
+
? undefined
|
|
73
|
+
: fill([protocol.verifyFocus], { ...vars, step, title: roundTitle(step) }, kind)[0],
|
|
74
|
+
brief: runtime => fill(protocol.brief, values(runtime), kind),
|
|
75
|
+
followUp: runtime => fill(protocol.followUp, values(runtime), kind),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/** All records, rendered once at module load: the config cannot change under a running client. */
|
|
79
|
+
let cache;
|
|
80
|
+
/** The records of loop.yaml.
|
|
81
|
+
* @returns Names, lookups and the declared vars of each record, built once.
|
|
82
|
+
*/
|
|
83
|
+
export function loopPrompts() {
|
|
84
|
+
if (cache !== undefined)
|
|
85
|
+
return cache;
|
|
86
|
+
const rendered = new Map();
|
|
87
|
+
for (const [kind, protocol] of Object.entries(SOURCE.protocols))
|
|
88
|
+
rendered.set(kind, render(kind, protocol));
|
|
89
|
+
const source = SOURCE.protocols;
|
|
90
|
+
cache = {
|
|
91
|
+
names: [...rendered.keys()],
|
|
92
|
+
find: (kind, overrides) => {
|
|
93
|
+
const protocol = source[kind];
|
|
94
|
+
if (protocol === undefined)
|
|
95
|
+
return undefined;
|
|
96
|
+
// The default rendering is cached because the record list reads it on every frame; overrides are
|
|
97
|
+
// rare, so only a run that retargets a record pays for re-rendering.
|
|
98
|
+
return overrides === undefined || Object.keys(overrides).length === 0
|
|
99
|
+
? rendered.get(kind) : render(kind, protocol, overrides);
|
|
100
|
+
},
|
|
101
|
+
vars: kind => source[kind]?.vars ?? {},
|
|
102
|
+
};
|
|
103
|
+
return cache;
|
|
104
|
+
}
|