intentdna 1.5.4 → 1.5.6

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.
@@ -9,7 +9,7 @@
9
9
  {
10
10
  "name": "intentdna",
11
11
  "description": "DNA template compilation + runtime enforcement",
12
- "version": "1.5.4",
12
+ "version": "1.5.6",
13
13
  "source": "./"
14
14
  }
15
15
  ]
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Declarative policy layer for AI agent governance"
5
5
  }
@@ -42,7 +42,7 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
42
42
  lines.push("</Purpose>");
43
43
  lines.push("");
44
44
  }
45
- // Steps
45
+ // Steps — each step MUST be executed via Agent() tool call
46
46
  const usedRoles = new Set(plan.steps.map((s) => s.role));
47
47
  lines.push("<Steps>");
48
48
  let stepNumber = 0;
@@ -51,24 +51,44 @@ export function compileWorkflowToSkill(plan, roles, ir, variables) {
51
51
  for (const stepId of group.step_ids) {
52
52
  stepNumber++;
53
53
  const step = plan.steps.find((s) => s.id === stepId);
54
- const agent = `@dna-${toKebabCase(step.role)}`;
55
- const runIf = step.run_if ? ` (${step.run_if})` : "";
54
+ const agentType = `dna-${toKebabCase(step.role)}`;
55
+ const runIf = step.run_if ? `\n **Condition**: ${step.run_if}` : "";
56
56
  const optional = step.optional ? " (optional)" : "";
57
- lines.push(`${stepNumber}. **${step.id}** ${agent}${runIf}${optional}`);
58
- lines.push(` ${step.description}`);
59
- if (step.prompt) {
60
- lines.push(` Prompt: ${step.prompt}`);
57
+ // Build agent prompt: anti-recursion + role identity + task
58
+ const promptParts = [
59
+ "Do NOT spawn sub-agents.",
60
+ `You are the ${agentType} agent.`,
61
+ step.prompt || step.description,
62
+ ];
63
+ const agentPrompt = escapePrompt(promptParts.join(" "));
64
+ lines.push(`${stepNumber}. **${step.id}**${optional}`);
65
+ lines.push(` ${step.description}${runIf}`);
66
+ lines.push("");
67
+ lines.push(` Execute with Agent tool — DO NOT perform this work yourself:`);
68
+ lines.push(` \`\`\``);
69
+ lines.push(` Agent(`);
70
+ lines.push(` subagent_type="${agentType}",`);
71
+ lines.push(` prompt="${agentPrompt}"`);
72
+ lines.push(` )`);
73
+ lines.push(` \`\`\``);
74
+ lines.push(` Wait for agent to complete. Read the output before proceeding.`);
75
+ if (step.handoff?.produces && step.handoff.produces.length > 0) {
76
+ const artifacts = step.handoff.produces.map(p => p.description).join(", ");
77
+ lines.push(` Verify produced artifacts: ${artifacts}`);
61
78
  }
79
+ lines.push("");
62
80
  }
63
81
  }
64
82
  lines.push("</Steps>");
65
83
  lines.push("");
66
- // Execution Policy — force-complete standard
84
+ // Execution Policy — force Agent dispatch
67
85
  lines.push("<Execution_Policy>");
86
+ lines.push("- **CRITICAL**: Each step MUST be executed by spawning an Agent using the Agent tool with the specified subagent_type. DO NOT perform any step's work directly in the main session.");
68
87
  lines.push("- Use TodoWrite to track each step as pending/in_progress/completed.");
69
88
  lines.push("- After completing each step, immediately proceed to the next — do not stop, summarize, or wait for confirmation.");
70
89
  lines.push("- If a step fails, mark it as failed in TodoWrite, log the error, and continue to the next non-dependent step.");
71
90
  lines.push("- Do not ask the user for permission between steps — the workflow is pre-approved.");
91
+ lines.push("- Wait for each Agent to complete and read its output before proceeding to the next step.");
72
92
  lines.push("</Execution_Policy>");
73
93
  lines.push("");
74
94
  // Tool_Usage — only if roles have permissions or scope
@@ -232,3 +252,11 @@ export async function removeSkillFiles(outputDir) {
232
252
  function escapeYaml(s) {
233
253
  return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
234
254
  }
255
+ /** Escape prompt text for Agent() call template inside markdown code block. */
256
+ function escapePrompt(s) {
257
+ return s
258
+ .replace(/\\/g, "\\\\")
259
+ .replace(/"/g, '\\"')
260
+ .replace(/`{3,}/g, "` ` `")
261
+ .replace(/\n/g, "\\n");
262
+ }
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Parses the YAML subset used by DNA templates and dna import output.
5
5
  * Supports: scalars, arrays (inline and block), objects (indentation),
6
- * comments, quoted strings. Does NOT support: anchors, aliases, tags,
7
- * multi-line scalars (|, >), complex keys, merge keys.
6
+ * comments, quoted strings, block scalars (|, >).
7
+ * Does NOT support: anchors, aliases, tags, complex keys, merge keys.
8
8
  *
9
9
  * Zero external dependencies.
10
10
  */
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * Parses the YAML subset used by DNA templates and dna import output.
5
5
  * Supports: scalars, arrays (inline and block), objects (indentation),
6
- * comments, quoted strings. Does NOT support: anchors, aliases, tags,
7
- * multi-line scalars (|, >), complex keys, merge keys.
6
+ * comments, quoted strings, block scalars (|, >).
7
+ * Does NOT support: anchors, aliases, tags, complex keys, merge keys.
8
8
  *
9
9
  * Zero external dependencies.
10
10
  */
@@ -49,6 +49,34 @@ function parseBlock(lines, startLine, baseIndent) {
49
49
  const key = content.slice(0, colonIdx).trim();
50
50
  const valueStr = content.slice(colonIdx + 1).trim();
51
51
  if (valueStr === "" || valueStr === "|" || valueStr === ">") {
52
+ // Block scalar: collect indented lines as a single string
53
+ if (valueStr === "|" || valueStr === ">") {
54
+ const blockLines = [];
55
+ let j = i + 1;
56
+ const blockIndent = j < lines.length ? getIndent(lines[j]) : indent + 2;
57
+ while (j < lines.length) {
58
+ const bLine = lines[j];
59
+ const bStripped = bLine.trimEnd();
60
+ if (bStripped === "") {
61
+ blockLines.push("");
62
+ j++;
63
+ continue;
64
+ }
65
+ const bIndent = getIndent(bLine);
66
+ if (bIndent < blockIndent)
67
+ break;
68
+ blockLines.push(bLine.slice(blockIndent));
69
+ j++;
70
+ }
71
+ // Trim trailing empty lines
72
+ while (blockLines.length > 0 && blockLines[blockLines.length - 1] === "") {
73
+ blockLines.pop();
74
+ }
75
+ const sep = valueStr === "|" ? "\n" : " ";
76
+ obj[key] = blockLines.join(sep) + "\n";
77
+ i = j;
78
+ continue;
79
+ }
52
80
  // Value is a nested block — check next line's indent
53
81
  const nextNonEmpty = findNextNonEmptyLine(lines, i + 1);
54
82
  if (nextNonEmpty < lines.length) {
@@ -98,6 +98,25 @@ genes:
98
98
  signal: uncertain_information
99
99
  response: escalate_to_human
100
100
 
101
+ minimal_change:
102
+ description: Each fix should be the smallest possible change
103
+ codons:
104
+ - type: attract
105
+ target: single_file_change
106
+ - type: repel
107
+ target: unnecessary_refactoring
108
+ - type: threshold
109
+ condition: "files_changed_per_round <= 5"
110
+ action: escalate
111
+
112
+ evidence_based:
113
+ description: All completion claims require fresh test evidence
114
+ codons:
115
+ - type: attract
116
+ target: run_tests_before_claiming_done
117
+ - type: repel
118
+ target: trust_without_evidence
119
+
101
120
  contexts: {}
102
121
 
103
122
 
@@ -147,6 +166,9 @@ roles:
147
166
  - Find where v2 diverges
148
167
  - Report with exact file paths and line numbers
149
168
  - Never suggest code changes
169
+ - "If round > 1: review previous round's git diff first, judge if direction is correct"
170
+ - "If previous fix produced 0 red→green transitions: warn 'no progress'"
171
+ - "Categorize by severity: CRITICAL (compile errors) > HIGH (logic failures) > LOW (widget/style)"
150
172
 
151
173
  surgeon:
152
174
  description: Fixes breakpoints and builds missing layers by understanding v1 intent and rewriting in v2 style.
@@ -165,6 +187,12 @@ roles:
165
187
  - After each change, run tests
166
188
  - If fix doesn't work, revert and re-analyze
167
189
  - Never "improve" code — preserve exact behavior
190
+ - "Before each fix: run `git diff` to confirm previous round's change scope"
191
+ - "After each file change: run `flutter analyze` to verify compilation"
192
+ - "Maximum 5 files per round — if more needed, the scope is too large, split it"
193
+ - "Same issue failed 3 times → STOP and report as blocked, do not retry"
194
+ - "Commit message must include: what changed, why, which test it targets"
195
+ - Do not introduce new abstractions or refactor unrelated code
168
196
 
169
197
  # ── Core align 角色 ──
170
198
  core_aligner:
@@ -188,18 +216,46 @@ workflows:
188
216
  steps:
189
217
  - id: scan
190
218
  role: scanner
191
- description: "Scan v1 module in {{v1_path}}/. Output behavior doc to {{behavior_docs}}/$ARGUMENTS.md. List all user actions with call chains."
192
- prompt: "Scan module '$ARGUMENTS' in {{v1_path}}/. For each page, list: action → function() → return value. Output to {{behavior_docs}}/$ARGUMENTS.md. Then immediately proceed to write_tests."
219
+ description: "Detect scenario (first-time vs re-run), scan v1 module accordingly."
220
+ prompt: |
221
+ Scenario detection:
222
+ - Check if {{test_path}}/$ARGUMENTS/ has test files AND {{behavior_docs}}/$ARGUMENTS.md exists
223
+ - Both missing → Scenario 1 (first-time): full scan, output complete behavior doc
224
+ - At least one exists → Scenario 2 (re-run/incremental): read existing behavior doc, compare against v1 source changes, output incremental diff only (do NOT rewrite the entire doc)
225
+
226
+ Scenario 1: Scan module '$ARGUMENTS' in {{v1_path}}/. For each page, list: action → function() → return value. Output to {{behavior_docs}}/$ARGUMENTS.md.
227
+ Scenario 2: Read {{behavior_docs}}/$ARGUMENTS.md. Compare against current v1 source in {{v1_path}}/. Output only the DIFF (added/removed/changed behaviors). Append changes to existing doc, do not rewrite.
193
228
  handoff:
194
229
  produces:
195
230
  - type: file
196
231
  path: "{{behavior_docs}}/$ARGUMENTS.md"
197
- description: "Behavior document for module"
232
+ description: "Behavior document for module (full or incremental)"
198
233
  - id: write_tests
199
234
  role: test_writer
200
235
  depends_on: [scan]
201
- description: "Read {{behavior_docs}}/$ARGUMENTS.md, write tests in {{test_path}}/$ARGUMENTS/, run baseline, commit."
202
- prompt: "Read {{behavior_docs}}/$ARGUMENTS.md. Write tests in {{test_path}}/$ARGUMENTS/. Test ALL layers: logic, widget, navigation. Run tests, record red/green baseline. Append baseline to behavior doc. Git commit: behavior-lock($ARGUMENTS): X tests (Y red, Z skipped)"
236
+ description: "Write or update tests based on scenario, verify compilation."
237
+ prompt: |
238
+ Scenario detection (same as scan step):
239
+ - {{test_path}}/$ARGUMENTS/ has NO test files → Scenario 1 (first-time)
240
+ - {{test_path}}/$ARGUMENTS/ has existing tests → Scenario 2 (re-run)
241
+
242
+ Scenario 1 (first-time):
243
+ - Read {{behavior_docs}}/$ARGUMENTS.md
244
+ - Write tests in {{test_path}}/$ARGUMENTS/. Test ALL layers: logic, widget, navigation
245
+ - Run `flutter analyze` to verify compilation — do NOT run `flutter test` (no implementation yet)
246
+ - Git commit: "behavior-lock($ARGUMENTS): N tests (fresh, not yet runnable)"
247
+
248
+ Scenario 2 (re-run/incremental):
249
+ - Read the incremental diff from behavior doc
250
+ - Update existing tests incrementally — do NOT rewrite all tests (preserves rescue progress)
251
+ - Run `flutter test {{test_path}}/$ARGUMENTS/` with NO timeout — record baseline
252
+ - Append baseline to behavior doc
253
+ - Git commit: "behavior-lock($ARGUMENTS): N tests (X green, Y red from behavior change)"
254
+
255
+ BANNED patterns:
256
+ - Do NOT use `sleep N && check` polling — run commands in foreground
257
+ - Do NOT use `timeout Nm flutter test` — let tests run to completion
258
+ - Do NOT rewrite existing test files from scratch — update incrementally
203
259
  handoff:
204
260
  consumes:
205
261
  - type: file
@@ -214,12 +270,25 @@ workflows:
214
270
 
215
271
  rescue:
216
272
  name: Rescue
217
- description: "Fix v2 module $ARGUMENTS — logic layer first, then widget layer, commit when done"
273
+ description: "Fix v2 module $ARGUMENTS — investigate, fix, review, verify, report. Max 10 rounds with convergence protection."
274
+ max_rounds: 10
275
+ convergence_rule: "2 consecutive rounds with 0 test progress (green count not increasing) → STOP. Output blocked items + analysis."
218
276
  steps:
219
277
  - id: investigate
220
278
  role: investigator
221
- description: "Run tests, assess current state, pick next targets."
222
- prompt: "Run tests in {{test_path}}/$ARGUMENTS/. Categorize all non-passing tests: 1) RED (failing) — highest priority, fix first. 2) SKIPPED-logic — state/notifier/service tests, fix second. 3) SKIPPED-widget — widget/UI/navigation tests, fix after logic is done. For the highest priority category, pick a batch of related tests. Trace: what does v1 do vs what does v2 do? Find the breakpoints. Report findings and the plan for this round."
279
+ description: "Run tests, assess current state, pick next targets by severity."
280
+ prompt: |
281
+ Round context:
282
+ - If round > 1: review previous round's git diff first
283
+ - If previous round had 0 test progress (no red→green or skip→green): warn "no progress" and consider changing approach
284
+
285
+ Run tests in {{test_path}}/$ARGUMENTS/. Categorize all non-passing tests by severity:
286
+ CRITICAL: compile errors, import failures — fix first
287
+ HIGH: logic test failures (state/notifier/service) — fix second
288
+ LOW: widget test failures (rendering/navigation) — fix after logic is done
289
+
290
+ Pick highest severity batch. Trace: what does v1 do vs what does v2 do? Find the breakpoints.
291
+ Report findings and the plan for this round.
223
292
  handoff:
224
293
  produces:
225
294
  - type: summary
@@ -230,11 +299,20 @@ workflows:
230
299
  - id: fix
231
300
  role: surgeon
232
301
  depends_on: [investigate]
233
- description: "Fix the identified issues. Handle both logic and widget layers."
302
+ description: "Fix identified issues. Max 5 files per round."
234
303
  checkpoints:
235
304
  - assert: clean_working_tree
236
- message: "Commit all changes before proceeding to report step"
237
- prompt: "Fix the identified breakpoints. Read v1 in {{v1_path}}/. Rewrite in v2 style in {{v2_path}}/. Logic tests: implement notifier/state/service code. Widget tests: copy widget from v1, change bindings (Obx→Consumer, Get.to→context.go), set up widget test infra (ProviderScope, mock providers, GoRouter) if needed. Run tests after each fix. Red→green or Skipped→green = done. Still failing = revert and re-analyze. Maximize test coverage per round. When done, commit all changes: rescue($ARGUMENTS): round N — X passed (+Y)"
305
+ message: "Commit all changes before proceeding"
306
+ prompt: |
307
+ Fix the identified breakpoints. Rules:
308
+ - Read v1 intent in {{v1_path}}/, rewrite in v2 style in {{v2_path}}/ (not copy v1 verbatim)
309
+ - Maximum 5 files per round — if more needed, split the scope
310
+ - Run `flutter analyze` after each file change
311
+ - Same issue failed 3 times → STOP, report as blocked, do not retry
312
+ - Logic tests: implement notifier/state/service code
313
+ - Widget tests: copy widget from v1, change bindings (Obx→Consumer, Get.to→context.go), set up infra (ProviderScope, mock providers, GoRouter) if needed
314
+ - Run tests after each fix. Red→green or Skip→green = progress. Still failing = revert and re-analyze
315
+ - Commit: "rescue($ARGUMENTS): round N — what changed, why, which tests targeted"
238
316
  handoff:
239
317
  consumes:
240
318
  - type: summary
@@ -243,11 +321,19 @@ workflows:
243
321
  produces:
244
322
  - type: git_commit
245
323
  description: "Rescue round commit"
246
- - id: report
324
+ - id: review
247
325
  role: investigator
248
326
  depends_on: [fix]
249
- description: "Summarize round, guide next steps."
250
- prompt: "Run full test suite in {{test_path}}/$ARGUMENTS/. Report: 1) passed/skipped/failed delta vs last round. 2) List remaining skipped tests by category (logic vs widget vs platform). 3) If skipped tests remain, end with: Run /rescue $ARGUMENTS to continue. 4) If all tests pass, end with: Module $ARGUMENTS rescue complete."
327
+ description: "Read-only review of surgeon's changes."
328
+ prompt: |
329
+ Review surgeon's git diff (read-only, do NOT modify any files):
330
+ 1. Are changes minimal? No unnecessary files touched?
331
+ 2. Does the code match v2 patterns (Riverpod, GoRouter)?
332
+ 3. Are mocks correct (not faked just to make tests pass)?
333
+ 4. Any new issues introduced?
334
+
335
+ Verdict: APPROVE → proceed to verify
336
+ Verdict: REQUEST_CHANGES → describe specific problems. Next round's investigate step will include this feedback.
251
337
  handoff:
252
338
  consumes:
253
339
  - type: git_commit
@@ -255,7 +341,50 @@ workflows:
255
341
  description: "Committed fix from surgeon"
256
342
  produces:
257
343
  - type: summary
258
- description: "Round summary with test delta"
344
+ description: "Review verdict (APPROVE or REQUEST_CHANGES)"
345
+ - id: verify
346
+ role: investigator
347
+ depends_on: [review]
348
+ description: "Independent test verification + regression check."
349
+ prompt: |
350
+ Run tests independently (do not trust surgeon's reported results):
351
+ 1. `flutter test {{test_path}}/$ARGUMENTS/` (no timeout)
352
+ 2. `flutter analyze` (compilation check)
353
+ 3. Check for regressions in core module tests if applicable
354
+
355
+ Record test delta vs previous round.
356
+ Each acceptance criterion: VERIFIED / PARTIAL / MISSING
357
+ Verdict: PASS or FAIL
358
+ handoff:
359
+ consumes:
360
+ - type: summary
361
+ from: review
362
+ description: "Review verdict"
363
+ produces:
364
+ - type: test_result
365
+ path: "{{test_path}}/$ARGUMENTS/"
366
+ description: "Verified test results"
367
+ - id: report
368
+ role: investigator
369
+ depends_on: [verify]
370
+ description: "Round summary with convergence judgment."
371
+ prompt: |
372
+ Summary:
373
+ 1. Test delta: +N green, -M red, ±K skipped vs last round
374
+ 2. Remaining tests by category (logic vs widget vs platform)
375
+ 3. Convergence check:
376
+ - If 2 consecutive rounds with no test progress → STOP, output blocked items + analysis
377
+ - If all green → "Module $ARGUMENTS rescue complete"
378
+ - Otherwise → "Run /rescue $ARGUMENTS to continue (round N+1 of max 10)"
379
+ 4. If review verdict was REQUEST_CHANGES: include the specific feedback for next round
380
+ handoff:
381
+ consumes:
382
+ - type: test_result
383
+ from: verify
384
+ description: "Verified test results from verify step"
385
+ produces:
386
+ - type: summary
387
+ description: "Round summary with test delta and convergence status"
259
388
 
260
389
  core-align:
261
390
  name: Core Align
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "intentdna",
3
- "version": "1.5.4",
3
+ "version": "1.5.6",
4
4
  "description": "Intent DNA — Declarative policy layer for AI agent behavior",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,116 @@
1
+ # Spec: F5 Agent 调度漏洞修复
2
+
3
+ ## 问题(架构级)
4
+
5
+ DNA 的 skill-adapter.ts 生成的 SKILL.md 用文字提示调度 agent:
6
+
7
+ ```
8
+ 1. **scan** → @dna-frw-scanner
9
+ Prompt: ...
10
+ ```
11
+
12
+ LLM 把这当成建议,选择自己做而非 spawn agent。导致:
13
+
14
+ - agent_type 为空 → role scope 不生效
15
+ - tool_permissions deny 不生效(scanner 能写文件)
16
+ - review/verify 步骤在主 session 做 → 自己审自己
17
+ - handoff chain 无意义(没有跨 agent artifact 传递)
18
+ - agent MD 文件从未被加载
19
+ - 所有 role-based 治理形同虚设
20
+
21
+ ## 影响范围
22
+
23
+ **以下交付物全部不生效:**
24
+
25
+ | 功能 | 依赖 | 当前状态 |
26
+ |------|------|---------|
27
+ | Role scope (read/write 范围) | agent_type 匹配 | 不生效 |
28
+ | tool_permissions deny | agent_type 匹配 | 不生效 |
29
+ | review 步骤只读约束 | investigator agent spawn | 不生效 |
30
+ | verify 步骤独立验证 | 独立 agent context | 不生效 |
31
+ | merge-time scope gate | worktree + agent 隔离 | 不生效 |
32
+ | handoff consumes/produces | 跨 agent artifact | 不生效 |
33
+ | step enforce rules (G4) | workflow state + agent_type | 不生效 |
34
+ | agent MD 文件加载 | agent spawn 时 CC 自动加载 | 从未加载 |
35
+
36
+ ## 根因
37
+
38
+ OMC 用 Claude Code 原生 `Task(subagent_type=...)` API 强制 spawn agent:
39
+
40
+ ```
41
+ Task(subagent_type="oh-my-claudecode:executor", prompt="implement caching")
42
+ ```
43
+
44
+ 这是 API 级调用,LLM 无法绕过。spawn 的 agent 有独立 context、独立 agent_type,hooks 能识别和 enforce。
45
+
46
+ DNA 的 SKILL.md 只是文字提示,不是 API 调用。
47
+
48
+ ## 修复方案
49
+
50
+ ### skill-adapter.ts 改动
51
+
52
+ 生成的 SKILL.md `<Steps>` 部分从:
53
+
54
+ ```markdown
55
+ 1. **scan** → @dna-frw-scanner
56
+ Prompt: Scan module...
57
+ ```
58
+
59
+ 改为:
60
+
61
+ ```markdown
62
+ 1. **scan**
63
+ Execute with Agent tool — DO NOT perform this work in the main session:
64
+ ```
65
+ Agent(subagent_type="dna-frw-scanner", prompt="Scan module '$ARGUMENTS' in common/lib/. ...")
66
+ ```
67
+ Wait for agent to complete. Read the agent's output before proceeding.
68
+ ```
69
+
70
+ ### 关键设计要点
71
+
72
+ 1. **每步必须是 Agent() 调用**,不是文字描述
73
+ 2. **明确禁止主 session 做 agent 的工作**:"DO NOT perform this work in the main session"
74
+ 3. **Agent 输出要读取**:Agent 完成后读取其 output,作为下一步的 handoff
75
+ 4. **Agent 工具参数完整**:subagent_type 用 DNA 编译的 agent 名称,prompt 用编译的步骤 prompt
76
+ 5. **参考 OMC 的 preamble 模式**:spawn 的 agent 不应再 spawn 子 agent("Do NOT spawn sub-agents")
77
+
78
+ ### 生成模板
79
+
80
+ ```typescript
81
+ // skill-adapter.ts — 每个 workflow step 生成:
82
+ function generateStepExecution(step, roleName, prompt) {
83
+ return `
84
+ ### Step: ${step.id}
85
+
86
+ Execute with Agent tool — DO NOT perform this work yourself:
87
+
88
+ \`\`\`
89
+ Agent(
90
+ subagent_type="dna-${toKebabCase(roleName)}",
91
+ prompt="${escapePrompt(prompt)}"
92
+ )
93
+ \`\`\`
94
+
95
+ Wait for agent to complete. Read the output.
96
+ ${step.handoff?.produces ? `Verify produced artifacts: ${step.handoff.produces.map(p => p.description).join(', ')}` : ''}
97
+ `;
98
+ }
99
+ ```
100
+
101
+ ### 验证方式
102
+
103
+ 修复后跑 behavior-lock(home),检查 trace:
104
+ - agent_type 应该有值(dna-frw-scanner, dna-frw-test-writer)
105
+ - 不应该全是 "-"
106
+
107
+ ## Acceptance Criteria
108
+
109
+ - [ ] skill-adapter.ts 生成强制 Agent() 调用格式
110
+ - [ ] 每步明确禁止主 session 直接执行
111
+ - [ ] Agent subagent_type 使用编译后的 role 名称
112
+ - [ ] Agent prompt 包含完整步骤 prompt + variables 替换
113
+ - [ ] spawn 的 agent 包含 "Do NOT spawn sub-agents" 约束
114
+ - [ ] handoff produces 在 Agent 完成后验证
115
+ - [ ] 测试:生成的 SKILL.md 包含 Agent() 调用模板
116
+ - [ ] 实战验证:trace 中 agent_type 有值
@@ -0,0 +1,270 @@
1
+ # Spec: flutter-rewrite 模板优化
2
+
3
+ ## 问题
4
+
5
+ flutter-rewrite 模板的 behavior-lock 和 rescue 工作流都有设计缺陷,导致:
6
+ 1. behavior-lock 不区分首次和重跑,首次跑测试浪费时间
7
+ 2. rescue 没有 code review 和 verify 步骤,surgeon 自己写自己审
8
+ 3. 没有回归检查、收敛保护、diff 审查
9
+
10
+ 参考 OMC agent 设计(code-reviewer/verifier/executor/test-engineer)改进。
11
+
12
+ ---
13
+
14
+ ## 一、behavior-lock 优化
15
+
16
+ ### 场景检测
17
+
18
+ ```
19
+ 检查 {{test_path}}/$ARGUMENTS/ 是否已有测试文件
20
+ 检查 {{behavior_docs}}/$ARGUMENTS.md 是否已存在
21
+
22
+ 两者都不存在 → 场景 1(首次)
23
+ 至少一个存在 → 场景 2(重跑/增量)
24
+ ```
25
+
26
+ ### 场景 1: 首次
27
+
28
+ ```
29
+ scan → 全量扫描 v1,输出完整行为文档
30
+ write → 写测试文件 → `flutter analyze` 验证编译 → 不跑 flutter test
31
+ commit: "behavior-lock($MODULE): N tests (fresh, not yet runnable)"
32
+ ```
33
+
34
+ ### 场景 2: 重跑/增量
35
+
36
+ ```
37
+ scan → 读已有行为文档,对比 v1 变化,输出增量 diff
38
+ write → 增量更新测试(不全量重写)→ `flutter test` 无超时 → 记录 baseline
39
+ commit: "behavior-lock($MODULE): N tests (X green, Y red from behavior change)"
40
+ ```
41
+
42
+ ### scanner 增量
43
+
44
+ 场景 2 时 scanner 不重写行为文档,而是:
45
+ 1. 读已有 behavior doc
46
+ 2. 对比 v1 源码变化
47
+ 3. 增量更新
48
+ 4. 输出 diff 摘要
49
+
50
+ ### 禁止的模式
51
+
52
+ | 禁止 | 原因 | 替代 |
53
+ |------|------|------|
54
+ | `sleep N && check` 轮询 | 浪费时间 | 前台跑 |
55
+ | `timeout Nm flutter test` | 测试可能需要很久 | 不设超时 |
56
+ | 全量重写已有测试 | 丢失 rescue 成果 | 增量更新 |
57
+ | 首次跑 `flutter test` | 没实现,编译不过 | `flutter analyze` |
58
+
59
+ ---
60
+
61
+ ## 二、rescue 优化
62
+
63
+ ### 当前流程(缺陷)
64
+
65
+ ```
66
+ investigate → fix → report → 重复
67
+ ↑ 无人审查
68
+ ↑ 无回归检查
69
+ ↑ 无收敛保护
70
+ ```
71
+
72
+ ### 优化后流程
73
+
74
+ ```
75
+ investigate → fix → review → verify → report
76
+ ↑ 只读审查 diff ↑ 自己跑测试+回归
77
+ ```
78
+
79
+ ### 新增: review 步骤(参考 OMC code-reviewer)
80
+
81
+ ```yaml
82
+ - id: review
83
+ role: investigator # read-only,不能改代码
84
+ depends_on: [fix]
85
+ prompt: |
86
+ Review surgeon's changes (git diff):
87
+ 1. 改动是否最小化?有没有改不该改的文件?
88
+ 2. 是否匹配 v2 代码风格(Riverpod, GoRouter)?
89
+ 3. mock 是否正确(不是为了让测试过而写假 mock)?
90
+ 4. 有没有引入新问题?
91
+
92
+ Verdict: APPROVE / REQUEST_CHANGES
93
+ 如果 REQUEST_CHANGES → 说明具体问题,下轮 investigate 带上 review 意见
94
+ ```
95
+
96
+ ### 新增: verify 步骤(参考 OMC verifier)
97
+
98
+ ```yaml
99
+ - id: verify
100
+ role: investigator # read-only
101
+ depends_on: [review] # review APPROVE 后才 verify
102
+ prompt: |
103
+ 自己跑测试验证(不信任 surgeon 的报告):
104
+ 1. flutter test {{test_path}}/$ARGUMENTS/ (当前模块,无超时)
105
+ 2. flutter test 核心模块(回归检查)
106
+ 3. flutter analyze(编译检查)
107
+
108
+ 每个 acceptance criterion: VERIFIED / PARTIAL / MISSING
109
+ Verdict: PASS / FAIL
110
+ ```
111
+
112
+ ### surgeon 加约束(参考 OMC executor)
113
+
114
+ ```yaml
115
+ surgeon:
116
+ instructions:
117
+ # 现有的保留,新增:
118
+ - 每次 fix 前先 `git diff` 确认上轮改动范围
119
+ - 每个文件改动后跑 `flutter analyze`
120
+ - 单轮最多改 5 个文件,超过说明范围太大需要拆分
121
+ - 同一问题失败 3 次 → 停止,报告卡点,不要无限重试
122
+ - commit message 必须包含:改了什么、为什么、对应哪个测试
123
+ - 不引入新抽象,不重构不相关代码
124
+ ```
125
+
126
+ ### investigator 加约束
127
+
128
+ ```yaml
129
+ investigator:
130
+ instructions:
131
+ # 现有的保留,新增:
132
+ - 非首轮:先 review 上轮 git diff,判断方向是否正确
133
+ - 如果上轮 fix 没有让任何测试从 red→green,警告"无进展"
134
+ - 分类时标注严重度:CRITICAL(编译不过)> HIGH(逻辑错误)> LOW(widget 样式)
135
+ ```
136
+
137
+ ### 收敛保护
138
+
139
+ ```yaml
140
+ rescue:
141
+ max_rounds: 10
142
+ convergence_rule: |
143
+ 连续 2 轮无测试进展(green 数没增加)→ 停止
144
+ 输出卡点分析 + 剩余问题清单
145
+ round_budget: |
146
+ 每轮最多改 5 个文件
147
+ 每轮必须让至少 1 个测试从 red/skip → green,否则视为无进展
148
+ ```
149
+
150
+ ### 完整 rescue workflow
151
+
152
+ ```yaml
153
+ rescue:
154
+ name: Rescue
155
+ max_rounds: 10
156
+ steps:
157
+ - id: investigate
158
+ role: investigator
159
+ prompt: |
160
+ Round context:
161
+ - If round > 1: review previous round's git diff first
162
+ - If previous round had 0 progress: warn and consider changing approach
163
+
164
+ Run tests, categorize by severity:
165
+ CRITICAL: compile errors, import failures
166
+ HIGH: logic test failures (state/notifier)
167
+ LOW: widget test failures (rendering/navigation)
168
+
169
+ Pick highest severity batch. Trace v1 vs v2. Report breakpoints.
170
+
171
+ - id: fix
172
+ role: surgeon
173
+ depends_on: [investigate]
174
+ checkpoints:
175
+ - assert: clean_working_tree
176
+ prompt: |
177
+ Fix identified breakpoints. Rules:
178
+ - Read v1 intent, rewrite in v2 style (not copy v1 verbatim)
179
+ - Maximum 5 files per round
180
+ - flutter analyze after each file change
181
+ - Same issue failed 3 times → STOP, report as blocked
182
+ - Commit: rescue($ARGUMENTS): round N — what changed and why
183
+
184
+ - id: review
185
+ role: investigator
186
+ depends_on: [fix]
187
+ prompt: |
188
+ Review surgeon's git diff (read-only):
189
+ 1. Changes minimal? No unnecessary files touched?
190
+ 2. Matches v2 patterns (Riverpod, GoRouter)?
191
+ 3. Mocks correct (not faked to pass)?
192
+ 4. No new issues introduced?
193
+ Verdict: APPROVE → proceed to verify
194
+ Verdict: REQUEST_CHANGES → next round investigate includes review feedback
195
+
196
+ - id: verify
197
+ role: investigator
198
+ depends_on: [review]
199
+ prompt: |
200
+ Run independently (don't trust surgeon's results):
201
+ 1. flutter test {{test_path}}/$ARGUMENTS/ (no timeout)
202
+ 2. flutter analyze
203
+ 3. Check: any regression in core tests?
204
+ Record delta vs last round. PASS or FAIL.
205
+
206
+ - id: report
207
+ role: investigator
208
+ depends_on: [verify]
209
+ prompt: |
210
+ Summary:
211
+ 1. Test delta: +N green, -M red, ±K skipped
212
+ 2. Remaining by category (logic vs widget vs platform)
213
+ 3. Convergence: are we making progress?
214
+ 4. If 2 rounds no progress → STOP, output blocked items
215
+ 5. If all green → "Module $ARGUMENTS rescue complete"
216
+ 6. Otherwise → "Run /rescue $ARGUMENTS to continue"
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 三、全局规则(两个工作流共享)
222
+
223
+ ### DNA gene 层面
224
+
225
+ ```yaml
226
+ genes:
227
+ # 现有 preserve_behavior 等保留,新增:
228
+ minimal_change:
229
+ description: Each fix should be the smallest possible change
230
+ codons:
231
+ - type: attract
232
+ target: single_file_change
233
+ - type: repel
234
+ target: unnecessary_refactoring
235
+ - type: threshold
236
+ condition: "files_changed_per_round <= 5"
237
+ action: escalate
238
+
239
+ evidence_based:
240
+ description: All completion claims require fresh test evidence
241
+ codons:
242
+ - type: attract
243
+ target: run_tests_before_claiming_done
244
+ - type: repel
245
+ target: trust_without_evidence
246
+ ```
247
+
248
+ ---
249
+
250
+ ## Acceptance Criteria
251
+
252
+ ### behavior-lock
253
+ - [ ] 场景检测正确(首次 vs 重跑)
254
+ - [ ] 首次: 只 analyze,不 test
255
+ - [ ] 重跑: 增量更新测试,不全量重写
256
+ - [ ] 重跑: 跑测试无超时
257
+ - [ ] scanner 增量: 输出 diff 而非全量重写
258
+ - [ ] 禁止 sleep 轮询模式
259
+
260
+ ### rescue
261
+ - [ ] review 步骤: investigator 审查 surgeon 的 git diff
262
+ - [ ] verify 步骤: 独立跑测试 + 回归检查
263
+ - [ ] surgeon 约束: 最多 5 文件/轮,3 次失败停止
264
+ - [ ] 收敛保护: 2 轮无进展 → 停止
265
+ - [ ] 轮次上限: 最多 10 轮
266
+ - [ ] report 包含收敛判断
267
+
268
+ ### 全局
269
+ - [ ] minimal_change gene 编译到 IR
270
+ - [ ] evidence_based gene 编译到 IR