@tea-agent/loop-agent 0.12.0 → 0.13.0-beta.0

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.
Files changed (284) hide show
  1. package/AGENTS.md +155 -153
  2. package/CHANGELOG.md +338 -265
  3. package/README.md +345 -298
  4. package/bin/agent-worker.js +22 -22
  5. package/bin/loop-agent.js +21 -21
  6. package/dist/application/dag/generate-task-dag.js +28 -28
  7. package/dist/application/evaluation/candidate-hash.js +75 -0
  8. package/dist/application/evaluation/candidate.js +52 -0
  9. package/dist/application/evaluation/replay.js +289 -0
  10. package/dist/application/evaluation/types.js +130 -0
  11. package/dist/cli/command-definitions.js +27 -7
  12. package/dist/cli/program.js +8 -4
  13. package/dist/commands/cursor-prompt.js +6 -6
  14. package/dist/commands/eval.js +235 -0
  15. package/dist/commands/init.js +544 -506
  16. package/dist/commands/knowledge.js +129 -31
  17. package/dist/commands/loop-benchmark.js +11 -11
  18. package/dist/commands/pi-reuse-benchmark.js +16 -16
  19. package/dist/executors/pi-sdk-executor.js +38 -24
  20. package/dist/executors/shell-executor.js +34 -2
  21. package/dist/executors/shell-presets.js +20 -0
  22. package/dist/executors/shell-verification.js +7 -0
  23. package/dist/governance/manifest-types.js +4 -0
  24. package/dist/infrastructure/evaluation/candidate-store.js +435 -0
  25. package/dist/infrastructure/evaluation/store.js +40 -0
  26. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  27. package/dist/task/config-types.js +28 -1
  28. package/dist/task/runtime.js +27 -27
  29. package/dist/worker/cli.js +96 -1
  30. package/dist/worker/delivery/package.js +3 -3
  31. package/dist/worker/feature/decision-loader.js +37 -6
  32. package/dist/worker/feature/next-action.js +10 -2
  33. package/dist/worker/feature/ready-plan-projection.js +81 -0
  34. package/dist/worker/feature/reducer.js +2 -1
  35. package/dist/worker/feature/review.js +19 -2
  36. package/dist/worker/feature/run.js +27 -2
  37. package/dist/worker/follow-up/approve.js +5 -2
  38. package/dist/worker/follow-up/factory.js +1 -1
  39. package/dist/worker/observability/read-model.js +246 -41
  40. package/dist/worker/observe/routes.js +173 -15
  41. package/dist/worker/observe/spec-evidence.js +281 -0
  42. package/dist/worker/observe/static/api.js +46 -27
  43. package/dist/worker/observe/static/app.js +150 -150
  44. package/dist/worker/observe/static/constants.js +148 -148
  45. package/dist/worker/observe/static/copy.js +67 -67
  46. package/dist/worker/observe/static/dag-helpers.js +172 -172
  47. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  48. package/dist/worker/observe/static/dag-layout.js +83 -83
  49. package/dist/worker/observe/static/dag-model.js +72 -72
  50. package/dist/worker/observe/static/dom.js +61 -61
  51. package/dist/worker/observe/static/format-pool.js +67 -67
  52. package/dist/worker/observe/static/format.js +292 -292
  53. package/dist/worker/observe/static/index.html +308 -308
  54. package/dist/worker/observe/static/kpi.js +94 -94
  55. package/dist/worker/observe/static/relations.js +133 -128
  56. package/dist/worker/observe/static/router.js +93 -85
  57. package/dist/worker/observe/static/run-processing.js +148 -148
  58. package/dist/worker/observe/static/shell-chrome.js +68 -68
  59. package/dist/worker/observe/static/state.js +253 -253
  60. package/dist/worker/observe/static/styles.css +1902 -1890
  61. package/dist/worker/observe/static/views/batch.js +227 -226
  62. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  63. package/dist/worker/observe/static/views/dag-inspector.js +607 -477
  64. package/dist/worker/observe/static/views/dag.js +362 -362
  65. package/dist/worker/observe/static/views/dashboard.js +445 -442
  66. package/dist/worker/observe/static/views/failures.js +143 -143
  67. package/dist/worker/observe/static/views/feature.js +492 -453
  68. package/dist/worker/observe/static/views/pool.js +350 -347
  69. package/dist/worker/observe/static/views/run.js +453 -453
  70. package/dist/worker/observe/static/views/session-timeline.js +205 -205
  71. package/dist/worker/observe/static/views/shell.js +7 -7
  72. package/dist/worker/observe/static/views/task.js +314 -260
  73. package/dist/worker/observe/static/views/timeline.js +163 -163
  74. package/dist/worker/pool/doctor.js +165 -0
  75. package/dist/worker/pool/migrate-state.js +303 -0
  76. package/dist/worker/pool/run-store.js +205 -17
  77. package/dist/worker/pool/types.js +17 -1
  78. package/dist/worker/pool/validation.js +100 -15
  79. package/dist/worker/report/morning-report.js +12 -2
  80. package/dist/worker/runner/run-ready.js +41 -26
  81. package/dist/worker/task-graph/ready-planner.js +136 -0
  82. package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
  83. package/dist/workflows/dag/canvas-observer.js +275 -275
  84. package/dist/workflows/dag/convergence/controller.js +16 -8
  85. package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
  86. package/dist/workflows/dag/failure-routing.js +12 -1
  87. package/dist/workflows/dag/init-hybrid.js +2404 -360
  88. package/dist/workflows/dag/node-execution.js +9 -0
  89. package/dist/workflows/dag/prompt.js +9 -0
  90. package/dist/workflows/dag/report.js +35 -1
  91. package/dist/workflows/dag/runner.js +28 -2
  92. package/dist/workflows/dag/task-demand-routing.js +383 -0
  93. package/dist/workflows/dag/types.js +51 -13
  94. package/dist/workflows/dag/upstream-artifacts.js +1 -0
  95. package/dist/workflows/dag/validate.js +59 -1
  96. package/docs/README.md +106 -104
  97. package/docs/agent-dag-recovery-playbook.md +195 -184
  98. package/docs/agent-dag-runner.md +67 -67
  99. package/docs/architecture/README.md +26 -26
  100. package/docs/architecture/dag-execution.md +140 -140
  101. package/docs/architecture/evolution.md +54 -53
  102. package/docs/architecture/facts-and-state.md +71 -58
  103. package/docs/architecture/runtime-boundaries.md +191 -191
  104. package/docs/architecture/system-overview.md +93 -93
  105. package/docs/architecture/worker-and-feature.md +85 -81
  106. package/docs/cursor-prompt-sidecar.md +36 -36
  107. package/docs/decisions/README.md +18 -15
  108. package/docs/design/README.md +167 -77
  109. package/docs/development-principles.md +73 -73
  110. package/docs/exec-plans/README.md +6 -6
  111. package/docs/exec-plans/active/README.md +15 -9
  112. package/docs/exec-plans/completed/README.md +85 -73
  113. package/docs/feature-workflow.md +389 -261
  114. package/docs/harness-methodology-debugging.md +153 -153
  115. package/docs/harness-methodology-tdd.md +130 -130
  116. package/docs/harness-methodology-verification.md +27 -27
  117. package/docs/init-surface.manifest.json +289 -280
  118. package/docs/loop-agent-harness.md +142 -130
  119. package/docs/production-readiness.md +96 -96
  120. package/docs/progress/README.md +64 -54
  121. package/docs/reports/README.md +117 -94
  122. package/docs/skills/README.md +7 -7
  123. package/docs/skills/vetted-skill-registry.md +29 -27
  124. package/docs/templates/adr.md +60 -60
  125. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  126. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  127. package/docs/templates/agent-dag-decision-gate-dogfood-report.md +117 -117
  128. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  129. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  130. package/docs/templates/agent-dag-report.schema.json +473 -473
  131. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  132. package/docs/templates/agent-dag.base.json +190 -190
  133. package/docs/templates/agent-dag.final-verification.json +185 -185
  134. package/docs/templates/agent-dag.schema.json +411 -383
  135. package/docs/templates/agent-dag.supervised-implementation.json +501 -501
  136. package/docs/templates/backend-test-analysis.schema.json +44 -0
  137. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +202 -139
  138. package/docs/templates/backend-test-dag.json +311 -276
  139. package/docs/templates/backend-test-dag.retrospect.prompt.md +125 -125
  140. package/docs/templates/backend-test-dag.review-cases.prompt.md +81 -81
  141. package/docs/templates/exec-plan.md +64 -64
  142. package/docs/templates/feature-spec.md +53 -53
  143. package/docs/templates/frontend-design-contract.md +42 -33
  144. package/docs/templates/frontend-task-constraints.md +35 -25
  145. package/docs/templates/frontend-task-requirement.md +70 -61
  146. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
  147. package/docs/templates/frontend-test-dag.json +23 -0
  148. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
  149. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
  150. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
  151. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  152. package/docs/templates/harness.schema.json +221 -221
  153. package/docs/templates/hybrid-dag.json +188 -188
  154. package/docs/templates/init-evolution-review.md +35 -35
  155. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  156. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -0
  157. package/docs/templates/knowledge-sync-dag.json +178 -0
  158. package/docs/templates/knowledge-sync-draft.schema.json +71 -0
  159. package/docs/templates/product-line/AGENTS.md +8 -8
  160. package/docs/templates/product-line/README.md +9 -9
  161. package/docs/templates/product-line/acceptance.yaml +14 -14
  162. package/docs/templates/product-line/closeout.yaml +9 -9
  163. package/docs/templates/product-line/design.md +13 -13
  164. package/docs/templates/product-line/links.md +10 -10
  165. package/docs/templates/product-line/requirement.md +17 -17
  166. package/docs/templates/product-line/task-graph.yaml +15 -15
  167. package/docs/templates/product-line/task.yaml +64 -64
  168. package/docs/templates/product-line/test-plan.md +7 -7
  169. package/docs/templates/production-readiness-checklist.md +57 -57
  170. package/docs/templates/progress-log.md +17 -17
  171. package/docs/templates/project-start-checklist.md +9 -9
  172. package/docs/templates/qa-report.md +48 -48
  173. package/docs/templates/sprint-contract.md +29 -29
  174. package/docs/templates/worker-dogfood-evidence.md +80 -80
  175. package/docs/templates/worker-dogfood-setup.md +68 -68
  176. package/docs/verification-matrix.md +70 -66
  177. package/examples/decision-gate-agent-dag.json +177 -177
  178. package/examples/example-dag.json +46 -46
  179. package/examples/hybrid-loop-agent-dag.json +189 -189
  180. package/harness.json +66 -66
  181. package/package.json +88 -46
  182. package/scripts/check-product-line-docs.sh +29 -29
  183. package/scripts/check-task-pool-root.sh +32 -32
  184. package/scripts/kb-bootstrap-init-skeleton.sh +240 -0
  185. package/scripts/kb-graph-incremental-prepare.mjs +386 -0
  186. package/scripts/kb-graph-incremental-prepare.sh +5 -0
  187. package/scripts/kb-graph-materialize.mjs +105 -0
  188. package/scripts/kb-graph-materialize.sh +4 -0
  189. package/scripts/kb-graph-promote.mjs +164 -0
  190. package/scripts/kb-graph-promote.sh +4 -0
  191. package/scripts/kb-query.mjs +554 -0
  192. package/scripts/kb-query.sh +5 -0
  193. package/skills/agent-worker/SKILL.md +39 -37
  194. package/skills/agent-worker/references/agent-worker-operator.md +60 -43
  195. package/skills/ai-engineering-context/SKILL.md +48 -48
  196. package/skills/analyze-product-dependencies/SKILL.md +67 -0
  197. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
  198. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
  199. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
  200. package/skills/analyze-product-dependencies/references/example.md +76 -0
  201. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
  202. package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
  203. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
  204. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
  205. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
  206. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
  207. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
  208. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
  209. package/skills/analyze-product-requirements/SKILL.md +90 -0
  210. package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
  211. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
  212. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
  213. package/skills/analyze-product-requirements/references/example.md +86 -0
  214. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
  215. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
  216. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
  217. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
  218. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
  219. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
  220. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
  221. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
  222. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
  223. package/skills/code-review-core/SKILL.md +20 -20
  224. package/skills/codebase-scout/SKILL.md +19 -19
  225. package/skills/frontend-design-review/SKILL.md +66 -59
  226. package/skills/frontend-design-review/references/review-checklist.md +58 -37
  227. package/skills/frontend-implementation/SKILL.md +47 -51
  228. package/skills/frontend-implementation/references/code-standards.md +32 -34
  229. package/skills/frontend-implementation/references/design-spec.md +46 -46
  230. package/skills/frontend-implementation/references/node-contracts.md +76 -32
  231. package/skills/frontend-review/SKILL.md +59 -53
  232. package/skills/frontend-review/references/review-findings.md +47 -42
  233. package/skills/frontend-verification/SKILL.md +53 -40
  234. package/skills/frontend-verification/references/verification-checklist.md +68 -56
  235. package/skills/grill-me/SKILL.md +10 -10
  236. package/skills/grill-with-docs/SKILL.md +88 -88
  237. package/skills/grill-with-docs/adr-format.md +47 -47
  238. package/skills/grill-with-docs/context-format.md +60 -60
  239. package/skills/init-capability-evolution/SKILL.md +70 -70
  240. package/skills/loop-agent/SKILL.md +151 -151
  241. package/skills/loop-agent/references/README.md +67 -67
  242. package/skills/loop-agent/references/command-reference.md +505 -452
  243. package/skills/loop-agent/references/docs-converge.md +126 -126
  244. package/skills/loop-agent/references/harness-policy.md +263 -263
  245. package/skills/loop-agent/references/hybrid-dag.md +238 -233
  246. package/skills/loop-agent/references/learned/README.md +21 -21
  247. package/skills/loop-agent/references/long-running-loop.md +57 -57
  248. package/skills/loop-agent/references/model-routing.md +36 -36
  249. package/skills/loop-agent/references/multi-worktree.md +54 -54
  250. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  251. package/skills/loop-agent/references/orchestrator-and-interventions.md +169 -169
  252. package/skills/loop-agent/references/pi-prompt.md +23 -23
  253. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  254. package/skills/loop-agent/references/post-implementation-and-patterns.md +44 -44
  255. package/skills/loop-agent/references/task-workflow.md +89 -89
  256. package/skills/loop-agent/references/verification-and-failure-handling.md +139 -139
  257. package/skills/playwright-cli/SKILL.md +420 -0
  258. package/skills/playwright-cli/references/element-attributes.md +23 -0
  259. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  260. package/skills/playwright-cli/references/request-mocking.md +87 -0
  261. package/skills/playwright-cli/references/running-code.md +241 -0
  262. package/skills/playwright-cli/references/session-management.md +225 -0
  263. package/skills/playwright-cli/references/storage-state.md +275 -0
  264. package/skills/playwright-cli/references/test-generation.md +433 -0
  265. package/skills/playwright-cli/references/tracing.md +139 -0
  266. package/skills/playwright-cli/references/video-recording.md +143 -0
  267. package/skills/playwright-cli-case-generator/SKILL.md +74 -0
  268. package/skills/requesting-code-review/SKILL.md +101 -101
  269. package/skills/requesting-code-review/code-reviewer.md +168 -168
  270. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  271. package/skills/systematic-debugging/SKILL.md +296 -296
  272. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  273. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  274. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  275. package/skills/systematic-debugging/find-polluter.sh +63 -63
  276. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  277. package/skills/systematic-debugging/test-academic.md +14 -14
  278. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  279. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  280. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  281. package/skills/test-driven-development/SKILL.md +20 -20
  282. package/skills/using-git-worktrees/SKILL.md +215 -215
  283. package/skills/verification-before-completion/SKILL.md +154 -154
  284. package/skills/webapp-testing/SKILL.md +19 -19
@@ -0,0 +1,435 @@
1
+ import { access, lstat, mkdir, mkdtemp, readdir, readFile, realpath, rename, rm, writeFile, } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { computeBundleHash, eventHashHex, formatContentSha, LIFECYCLE_GENESIS_HASH, normalizeContentSha, sha256Hex, } from "../../application/evaluation/candidate-hash.js";
4
+ import { candidateManifestInputSchema, candidateManifestSchema, lifecycleEventSchema, } from "../../application/evaluation/types.js";
5
+ import { appendJsonlLineAtomic, writeJsonAtomic, } from "../harness/atomic-write.js";
6
+ import { EVALUATION_ROOT } from "./store.js";
7
+ const SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
8
+ const MANIFEST_INTEGRITY_FILE = "manifest.sha256";
9
+ const LIFECYCLE_LOCK_DIR = ".lifecycle.lock";
10
+ const LIFECYCLE_LOCK_RETRIES = 100;
11
+ const LIFECYCLE_LOCK_DELAY_MS = 10;
12
+ const FORBIDDEN_PREFIXES = [
13
+ "src/application/evaluation/",
14
+ "src/workflows/dag/",
15
+ "src/executors/",
16
+ "src/worker/",
17
+ "src/infrastructure/harness/completed-facts-guard.ts",
18
+ ".harness/dag-runs/",
19
+ ".harness/runs/",
20
+ ".harness/evaluation/",
21
+ ];
22
+ const FORBIDDEN_SEGMENTS = [
23
+ "private-verifier",
24
+ "private_verifier",
25
+ "held-out-evaluator",
26
+ "held_out_evaluator",
27
+ "held-out",
28
+ "completed-facts",
29
+ ];
30
+ export function assertCandidateId(value) {
31
+ if (!SAFE_ID.test(value)) {
32
+ throw new Error(`candidate-id must contain only letters, numbers, dot, underscore, or hyphen: ${value}`);
33
+ }
34
+ }
35
+ export function candidateDir(repoRoot, candidateId) {
36
+ assertCandidateId(candidateId);
37
+ return path.join(repoRoot, EVALUATION_ROOT, "candidates", candidateId);
38
+ }
39
+ export function candidateManifestPath(repoRoot, candidateId) {
40
+ return path.join(candidateDir(repoRoot, candidateId), "manifest.json");
41
+ }
42
+ export function candidateLifecyclePath(repoRoot, candidateId) {
43
+ return path.join(candidateDir(repoRoot, candidateId), "lifecycle.jsonl");
44
+ }
45
+ export async function resolveRepoRelativeSafe(repoRoot, repoRelativePath) {
46
+ if (path.isAbsolute(repoRelativePath) ||
47
+ repoRelativePath.startsWith("/") ||
48
+ /^[A-Za-z]:[\\/]/.test(repoRelativePath)) {
49
+ throw new Error(`content ref path must be repo-relative: ${repoRelativePath}`);
50
+ }
51
+ const normalized = repoRelativePath.replace(/\\/g, "/");
52
+ if (normalized.split("/").includes("..") ||
53
+ normalized.startsWith("./../") ||
54
+ normalized.includes("/../")) {
55
+ throw new Error(`content ref path escapes repo root: ${repoRelativePath}`);
56
+ }
57
+ const canonicalRoot = await realpath(repoRoot);
58
+ const lexical = path.resolve(repoRoot, normalized);
59
+ let absolute;
60
+ try {
61
+ absolute = await realpath(lexical);
62
+ }
63
+ catch {
64
+ // File may not exist yet for some flows; still check lexical containment.
65
+ const relativeLexical = path.relative(canonicalRoot, lexical);
66
+ if (!relativeLexical ||
67
+ relativeLexical.startsWith("..") ||
68
+ path.isAbsolute(relativeLexical)) {
69
+ throw new Error(`content ref path escapes repo root: ${repoRelativePath}`);
70
+ }
71
+ throw new Error(`content ref missing: ${normalized}`);
72
+ }
73
+ const relative = path.relative(canonicalRoot, absolute).replace(/\\/g, "/");
74
+ if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
75
+ throw new Error(`content ref path escapes repo root: ${repoRelativePath}`);
76
+ }
77
+ const st = await lstat(absolute).catch(() => null);
78
+ if (st?.isSymbolicLink()) {
79
+ // realpath already resolved; re-check containment after resolve (done above)
80
+ }
81
+ return { absolute, relative };
82
+ }
83
+ export function assertAllowedContentRefPath(relativePosix) {
84
+ const rel = relativePosix.replace(/\\/g, "/");
85
+ if (rel.startsWith("..") || path.isAbsolute(rel)) {
86
+ throw new Error(`forbidden content ref path: ${rel}`);
87
+ }
88
+ for (const prefix of FORBIDDEN_PREFIXES) {
89
+ if (rel === prefix.replace(/\/$/, "") || rel.startsWith(prefix)) {
90
+ throw new Error(`content ref enters forbidden candidate surface: ${rel}`);
91
+ }
92
+ }
93
+ const lower = rel.toLowerCase();
94
+ for (const segment of FORBIDDEN_SEGMENTS) {
95
+ if (lower.includes(`/${segment}/`) ||
96
+ lower.endsWith(`/${segment}`) ||
97
+ lower.startsWith(`${segment}/`) ||
98
+ lower === segment) {
99
+ throw new Error(`content ref enters forbidden candidate surface: ${rel}`);
100
+ }
101
+ }
102
+ }
103
+ async function hashExistingContent(repoRoot, repoRelativePath) {
104
+ const { absolute, relative } = await resolveRepoRelativeSafe(repoRoot, repoRelativePath);
105
+ assertAllowedContentRefPath(relative);
106
+ const content = await readFile(absolute);
107
+ return { relative, sha256: sha256Hex(content) };
108
+ }
109
+ function lifecycleTransitionAllowed(from, to) {
110
+ if (from === null && to === "proposed")
111
+ return true;
112
+ const edges = {
113
+ proposed: ["eligible", "invalid"],
114
+ eligible: ["experimenting", "invalid"],
115
+ experimenting: ["accepted", "rejected", "invalid"],
116
+ accepted: ["retired"],
117
+ rejected: ["retired"],
118
+ invalid: ["retired"],
119
+ retired: [],
120
+ };
121
+ if (from === null)
122
+ return false;
123
+ return edges[from]?.includes(to) ?? false;
124
+ }
125
+ function computeEventHash(event) {
126
+ return eventHashHex({
127
+ schemaVersion: event.schemaVersion,
128
+ seq: event.seq,
129
+ from: event.from,
130
+ to: event.to,
131
+ reason: event.reason,
132
+ at: event.at,
133
+ previousEventHash: event.previousEventHash,
134
+ });
135
+ }
136
+ export async function materializeManifest(repoRoot, raw) {
137
+ const input = candidateManifestInputSchema.parse(raw);
138
+ const verifiedRefs = [];
139
+ for (const ref of input.contentRefs) {
140
+ const expected = normalizeContentSha(ref.sha256);
141
+ const actual = await hashExistingContent(repoRoot, ref.path);
142
+ assertAllowedContentRefPath(actual.relative);
143
+ if (actual.sha256 !== expected) {
144
+ throw new Error(`content ref hash mismatch for ${actual.relative}: expected ${expected}, got ${actual.sha256}`);
145
+ }
146
+ // Persist repo-relative posix path as resolved relative (no abs paths).
147
+ verifiedRefs.push({
148
+ path: actual.relative,
149
+ sha256: formatContentSha(actual.sha256),
150
+ });
151
+ }
152
+ const withoutHash = {
153
+ schemaVersion: 1,
154
+ candidateId: input.candidateId,
155
+ parentCandidateId: input.parentCandidateId ?? null,
156
+ candidateKind: input.candidateKind,
157
+ createdAt: input.createdAt,
158
+ ...(input.description !== undefined
159
+ ? { description: input.description }
160
+ : {}),
161
+ contentRefs: verifiedRefs,
162
+ };
163
+ const bundleHash = computeBundleHash(withoutHash);
164
+ if (input.bundleHash) {
165
+ const provided = formatContentSha(normalizeContentSha(input.bundleHash));
166
+ if (provided !== bundleHash) {
167
+ throw new Error(`bundleHash mismatch: expected ${bundleHash}, got ${provided}`);
168
+ }
169
+ }
170
+ const manifest = {
171
+ ...withoutHash,
172
+ bundleHash,
173
+ };
174
+ return candidateManifestSchema.parse(manifest);
175
+ }
176
+ function serializeManifest(manifest) {
177
+ return `${JSON.stringify(manifest, null, 2)}\n`;
178
+ }
179
+ function manifestIntegrityPath(repoRoot, candidateId) {
180
+ return path.join(candidateDir(repoRoot, candidateId), MANIFEST_INTEGRITY_FILE);
181
+ }
182
+ async function assertManifestIntegrity(repoRoot, candidateId, rawManifest) {
183
+ const expected = (await readFile(manifestIntegrityPath(repoRoot, candidateId), "utf-8")).trim();
184
+ const actual = sha256Hex(rawManifest);
185
+ if (expected !== actual) {
186
+ throw new Error(`candidate manifest integrity mismatch for ${candidateId}: expected ${expected}, got ${actual}`);
187
+ }
188
+ }
189
+ async function acquireLifecycleLock(repoRoot, candidateId) {
190
+ const lockPath = path.join(candidateDir(repoRoot, candidateId), LIFECYCLE_LOCK_DIR);
191
+ for (let attempt = 0; attempt < LIFECYCLE_LOCK_RETRIES; attempt += 1) {
192
+ try {
193
+ await mkdir(lockPath);
194
+ return async () => {
195
+ await rm(lockPath, { recursive: true, force: true });
196
+ };
197
+ }
198
+ catch (error) {
199
+ if (error.code !== "EEXIST")
200
+ throw error;
201
+ await new Promise((resolve) => setTimeout(resolve, LIFECYCLE_LOCK_DELAY_MS));
202
+ }
203
+ }
204
+ throw new Error(`timed out acquiring lifecycle lock for ${candidateId}`);
205
+ }
206
+ async function pathExists(filePath) {
207
+ try {
208
+ await access(filePath);
209
+ return true;
210
+ }
211
+ catch {
212
+ return false;
213
+ }
214
+ }
215
+ function manifestsEqual(existing, manifest) {
216
+ return (existing.candidateId === manifest.candidateId &&
217
+ existing.bundleHash === manifest.bundleHash &&
218
+ existing.candidateKind === manifest.candidateKind &&
219
+ existing.createdAt === manifest.createdAt &&
220
+ (existing.description ?? "") === (manifest.description ?? "") &&
221
+ (existing.parentCandidateId ?? null) ===
222
+ (manifest.parentCandidateId ?? null) &&
223
+ JSON.stringify(existing.contentRefs) ===
224
+ JSON.stringify(manifest.contentRefs));
225
+ }
226
+ async function readExistingRegistration(repoRoot, manifest, manifestPath, lifecyclePath) {
227
+ const existingRaw = await readFile(manifestPath, "utf-8");
228
+ await assertManifestIntegrity(repoRoot, manifest.candidateId, existingRaw);
229
+ const existing = candidateManifestSchema.parse(JSON.parse(existingRaw));
230
+ if (!manifestsEqual(existing, manifest)) {
231
+ throw new Error(`candidate already exists with different content: ${manifest.candidateId}`);
232
+ }
233
+ const record = await readCandidateRecord(repoRoot, manifest.candidateId);
234
+ return { record, idempotent: true, manifestPath, lifecyclePath };
235
+ }
236
+ export async function registerCandidateManifest(input) {
237
+ const { repoRoot, manifest } = input;
238
+ assertCandidateId(manifest.candidateId);
239
+ const manifestPath = candidateManifestPath(repoRoot, manifest.candidateId);
240
+ const lifecyclePath = candidateLifecyclePath(repoRoot, manifest.candidateId);
241
+ if (await pathExists(manifestPath)) {
242
+ return readExistingRegistration(repoRoot, manifest, manifestPath, lifecyclePath);
243
+ }
244
+ const at = input.now ?? new Date().toISOString();
245
+ const baseEvent = {
246
+ schemaVersion: 1,
247
+ seq: 1,
248
+ from: null,
249
+ to: "proposed",
250
+ reason: "registered",
251
+ at,
252
+ previousEventHash: LIFECYCLE_GENESIS_HASH,
253
+ };
254
+ const event = {
255
+ ...baseEvent,
256
+ eventHash: computeEventHash(baseEvent),
257
+ };
258
+ lifecycleEventSchema.parse(event);
259
+ const candidatesRoot = path.dirname(candidateDir(repoRoot, manifest.candidateId));
260
+ await mkdir(candidatesRoot, { recursive: true });
261
+ const stagingDir = await mkdtemp(path.join(candidatesRoot, `.${manifest.candidateId}.register-`));
262
+ try {
263
+ await writeJsonAtomic(path.join(stagingDir, "manifest.json"), manifest, {
264
+ repoRoot,
265
+ });
266
+ await writeFile(path.join(stagingDir, MANIFEST_INTEGRITY_FILE), `${sha256Hex(serializeManifest(manifest))}\n`, "utf-8");
267
+ await appendJsonlLineAtomic(path.join(stagingDir, "lifecycle.jsonl"), event, {
268
+ repoRoot,
269
+ });
270
+ try {
271
+ await rename(stagingDir, candidateDir(repoRoot, manifest.candidateId));
272
+ }
273
+ catch (error) {
274
+ const code = error.code;
275
+ if (code !== "EEXIST" && code !== "ENOTEMPTY")
276
+ throw error;
277
+ await rm(stagingDir, { recursive: true, force: true });
278
+ return readExistingRegistration(repoRoot, manifest, manifestPath, lifecyclePath);
279
+ }
280
+ }
281
+ catch (error) {
282
+ await rm(stagingDir, { recursive: true, force: true });
283
+ throw error;
284
+ }
285
+ return {
286
+ record: {
287
+ manifest,
288
+ status: "proposed",
289
+ events: [event],
290
+ promotionApplied: false,
291
+ },
292
+ idempotent: false,
293
+ manifestPath,
294
+ lifecyclePath,
295
+ };
296
+ }
297
+ async function readLifecycleEvents(repoRoot, candidateId) {
298
+ const lifecyclePath = candidateLifecyclePath(repoRoot, candidateId);
299
+ const text = await readFile(lifecyclePath, "utf-8");
300
+ const lines = text
301
+ .split("\n")
302
+ .map((line) => line.trim())
303
+ .filter(Boolean);
304
+ const events = [];
305
+ let previous = LIFECYCLE_GENESIS_HASH;
306
+ for (let i = 0; i < lines.length; i += 1) {
307
+ const parsed = lifecycleEventSchema.parse(JSON.parse(lines[i]));
308
+ if (parsed.seq !== i + 1) {
309
+ throw new Error(`lifecycle seq gap for ${candidateId}: expected ${i + 1}, got ${parsed.seq}`);
310
+ }
311
+ if (parsed.previousEventHash !== previous) {
312
+ throw new Error(`lifecycle chain break for ${candidateId} at seq ${parsed.seq}`);
313
+ }
314
+ const expectedHash = computeEventHash({
315
+ schemaVersion: parsed.schemaVersion,
316
+ seq: parsed.seq,
317
+ from: parsed.from,
318
+ to: parsed.to,
319
+ reason: parsed.reason,
320
+ at: parsed.at,
321
+ previousEventHash: parsed.previousEventHash,
322
+ });
323
+ if (parsed.eventHash !== expectedHash) {
324
+ throw new Error(`lifecycle event hash mismatch for ${candidateId} at seq ${parsed.seq}`);
325
+ }
326
+ if (i === 0) {
327
+ if (parsed.from !== null || parsed.to !== "proposed") {
328
+ throw new Error(`lifecycle genesis must be null→proposed for ${candidateId}`);
329
+ }
330
+ }
331
+ else {
332
+ const prev = events[i - 1];
333
+ if (parsed.from !== prev.to) {
334
+ throw new Error(`lifecycle from-state mismatch for ${candidateId} at seq ${parsed.seq}`);
335
+ }
336
+ if (!lifecycleTransitionAllowed(parsed.from, parsed.to)) {
337
+ throw new Error(`illegal lifecycle transition recorded for ${candidateId}: ${parsed.from}→${parsed.to}`);
338
+ }
339
+ }
340
+ events.push(parsed);
341
+ previous = parsed.eventHash;
342
+ }
343
+ if (events.length === 0) {
344
+ throw new Error(`empty lifecycle for candidate ${candidateId}`);
345
+ }
346
+ return events;
347
+ }
348
+ export async function readCandidateRecord(repoRoot, candidateId) {
349
+ assertCandidateId(candidateId);
350
+ const manifestPath = candidateManifestPath(repoRoot, candidateId);
351
+ const rawText = await readFile(manifestPath, "utf-8");
352
+ await assertManifestIntegrity(repoRoot, candidateId, rawText);
353
+ const raw = JSON.parse(rawText);
354
+ const stored = candidateManifestSchema.parse(raw);
355
+ if (stored.candidateId !== candidateId) {
356
+ throw new Error(`candidate manifest identity mismatch: directory=${candidateId}, manifest=${stored.candidateId}`);
357
+ }
358
+ // Re-verify each content ref against workspace bytes.
359
+ for (const ref of stored.contentRefs) {
360
+ const expected = normalizeContentSha(ref.sha256);
361
+ const actual = await hashExistingContent(repoRoot, ref.path);
362
+ if (actual.sha256 !== expected) {
363
+ throw new Error(`content ref hash mismatch for ${actual.relative}: expected ${expected}, got ${actual.sha256}`);
364
+ }
365
+ }
366
+ const recomputed = computeBundleHash(stored);
367
+ if (recomputed !== stored.bundleHash) {
368
+ throw new Error(`bundleHash mismatch for ${candidateId}: expected ${recomputed}, got ${stored.bundleHash}`);
369
+ }
370
+ const events = await readLifecycleEvents(repoRoot, candidateId);
371
+ const status = events[events.length - 1].to;
372
+ return {
373
+ manifest: stored,
374
+ status,
375
+ events,
376
+ promotionApplied: false,
377
+ };
378
+ }
379
+ export async function listCandidateIds(repoRoot) {
380
+ const root = path.join(repoRoot, EVALUATION_ROOT, "candidates");
381
+ try {
382
+ const entries = await readdir(root, { withFileTypes: true });
383
+ return entries
384
+ .filter((entry) => entry.isDirectory() && SAFE_ID.test(entry.name))
385
+ .map((entry) => entry.name)
386
+ .sort();
387
+ }
388
+ catch (error) {
389
+ const code = error.code;
390
+ if (code === "ENOENT")
391
+ return [];
392
+ throw error;
393
+ }
394
+ }
395
+ export async function transitionCandidateLifecycle(input) {
396
+ const reason = input.reason.trim();
397
+ if (!reason) {
398
+ throw new Error("lifecycle transition requires non-empty --reason");
399
+ }
400
+ const releaseLock = await acquireLifecycleLock(input.repoRoot, input.candidateId);
401
+ try {
402
+ const record = await readCandidateRecord(input.repoRoot, input.candidateId);
403
+ const from = record.status;
404
+ if (!lifecycleTransitionAllowed(from, input.to)) {
405
+ throw new Error(`illegal lifecycle transition: ${from} → ${input.to}`);
406
+ }
407
+ const previous = record.events[record.events.length - 1];
408
+ const base = {
409
+ schemaVersion: 1,
410
+ seq: previous.seq + 1,
411
+ from,
412
+ to: input.to,
413
+ reason,
414
+ at: input.now ?? new Date().toISOString(),
415
+ previousEventHash: previous.eventHash,
416
+ };
417
+ const event = {
418
+ ...base,
419
+ eventHash: computeEventHash(base),
420
+ };
421
+ lifecycleEventSchema.parse(event);
422
+ await appendJsonlLineAtomic(candidateLifecyclePath(input.repoRoot, input.candidateId), event, { repoRoot: input.repoRoot });
423
+ return readCandidateRecord(input.repoRoot, input.candidateId);
424
+ }
425
+ finally {
426
+ await releaseLock();
427
+ }
428
+ }
429
+ export async function loadManifestInputFromPath(repoRoot, manifestPath) {
430
+ const resolved = path.isAbsolute(manifestPath)
431
+ ? manifestPath
432
+ : path.resolve(repoRoot, manifestPath);
433
+ const raw = JSON.parse(await readFile(resolved, "utf-8"));
434
+ return candidateManifestInputSchema.parse(raw);
435
+ }
@@ -0,0 +1,40 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { replaySpecSchema, } from "../../application/evaluation/types.js";
4
+ import { writeJsonAtomic, writeTextAtomic } from "../harness/atomic-write.js";
5
+ export const EVALUATION_ROOT = path.join(".harness", "evaluation");
6
+ export function assertReplayId(value) {
7
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value)) {
8
+ throw new Error(`replay-id must contain only letters, numbers, dot, underscore, or hyphen: ${value}`);
9
+ }
10
+ }
11
+ export async function readReplaySpec(repoRoot, specPath) {
12
+ const resolved = path.resolve(repoRoot, specPath);
13
+ const raw = JSON.parse(await readFile(resolved, "utf-8"));
14
+ return replaySpecSchema.parse(raw);
15
+ }
16
+ export function replayDir(repoRoot, replayId) {
17
+ assertReplayId(replayId);
18
+ return path.join(repoRoot, EVALUATION_ROOT, "replays", replayId);
19
+ }
20
+ export function replayScorecardPath(repoRoot, replayId) {
21
+ return path.join(replayDir(repoRoot, replayId), "scorecard.json");
22
+ }
23
+ export function replayMarkdownPath(repoRoot, replayId) {
24
+ return path.join(replayDir(repoRoot, replayId), "report.md");
25
+ }
26
+ export async function writeReplayArtifacts(input) {
27
+ const scorecardPath = replayScorecardPath(input.repoRoot, input.replayId);
28
+ const markdownPath = replayMarkdownPath(input.repoRoot, input.replayId);
29
+ await writeJsonAtomic(scorecardPath, input.scorecard, {
30
+ repoRoot: input.repoRoot,
31
+ });
32
+ await writeTextAtomic(markdownPath, input.markdown, {
33
+ repoRoot: input.repoRoot,
34
+ });
35
+ return { scorecardPath, markdownPath };
36
+ }
37
+ export async function readReplayScorecard(repoRoot, replayId) {
38
+ const raw = await readFile(replayScorecardPath(repoRoot, replayId), "utf-8");
39
+ return JSON.parse(raw);
40
+ }
@@ -29,7 +29,7 @@ export function resolveArtifactWriteDir(options) {
29
29
  }
30
30
  export function buildArtifactPathPrompt(writeDir) {
31
31
  if (!writeDir)
32
- return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
32
+ return `After changes, write artifacts/修改记录.md and artifacts/验证结果.md with verification evidence.
33
33
  ${ARTIFACT_INSTRUCTIONS}`;
34
34
  return [
35
35
  `After changes, write the following files:`,
@@ -11,7 +11,10 @@ export const taskKindSchema = z.enum([
11
11
  "standard",
12
12
  "feature-study",
13
13
  "frontend-implementation",
14
+ "frontend-test",
14
15
  "backend-test",
16
+ "knowledge-sync",
17
+ "knowledge-graph-bootstrap",
15
18
  ]);
16
19
  export const referenceRepoConfigSchema = z.object({
17
20
  name: z.string().min(1),
@@ -51,6 +54,24 @@ export const loopAutoExecutionPolicySchema = z.enum([
51
54
  ]);
52
55
  export const LOOP_AUTO_WRITE_POLICY_REMOVED_ERROR = "task field loopAutoWritePolicy is no longer supported; use loopAutoExecutionPolicy (off | approval-required | enabled)";
53
56
  export const CURSOR_TASK_FIELD_REMOVED_ERROR = 'task fields "executor" and "cursorModel" are no longer supported; governed runtime is Pi-only';
57
+ export const frontendMockPolicySchema = z.enum(["auto", "required", "disabled"]);
58
+ export const frontendMockVerifyCommandSchema = z.object({
59
+ label: z.string().min(1),
60
+ command: z.string().min(1),
61
+ timeoutMs: z.number().int().positive().optional(),
62
+ });
63
+ export const frontendMockConfigSchema = z.object({
64
+ policy: frontendMockPolicySchema.optional().default("auto"),
65
+ serviceRoot: z.string().min(1).optional(),
66
+ verifyCommands: z.array(frontendMockVerifyCommandSchema).optional().default([]),
67
+ });
68
+ /** Batch limits for the browser-driven frontend test DAG. These are post-case
69
+ * stop thresholds, not model-provider hard token caps. */
70
+ export const frontendTestConfigSchema = z.object({
71
+ maxCasesPerBatch: z.number().int().min(1).max(50).optional().default(20),
72
+ maxTokensPerCase: z.number().int().positive().optional(),
73
+ maxTotalTokens: z.number().int().positive().optional(),
74
+ });
54
75
  export const convergenceConfigSchema = z.object({
55
76
  enabled: z.boolean().optional().default(false),
56
77
  maxPasses: z.number().int().positive().optional().default(3),
@@ -62,8 +83,10 @@ const taskConfigObjectSchema = z.object({
62
83
  taskId: z.string(),
63
84
  title: z.string(),
64
85
  sourceFiles: z.array(z.string()),
65
- /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 使用前端实现 DAG 模板 */
86
+ /** standard: 本仓库需求实现;feature-study: 参考外部代码特性并在目标仓库落地;frontend-implementation: 前端实现 DAG;backend-test: 后端测试 DAG;knowledge-sync: 最终验证后回写测试知识库 DAG;knowledge-graph-bootstrap: AI 辅助业务知识图谱初始化 */
66
87
  taskKind: taskKindSchema.optional().default("standard"),
88
+ /** Feature 目录 id(如 F-2026-004)。knowledge-sync 必填(也可从 hardConstraints/需求正文/taskId 解析);用于收窄 writeSet */
89
+ featureId: z.string().min(1).optional(),
67
90
  referenceRepos: z.array(referenceRepoConfigSchema).optional().default([]),
68
91
  referenceDocs: z.array(referenceDocConfigSchema).optional().default([]),
69
92
  referenceMaxFilesPerRepo: z.number().int().positive().optional(),
@@ -107,6 +130,10 @@ const taskConfigObjectSchema = z.object({
107
130
  maxGoalContinuationsPerRun: z.number().int().positive().optional().default(5),
108
131
  /** Pi subagent assisted mode: 'off' (default), 'analyze-plan', or 'full' */
109
132
  piSubagentMode: piSubagentModeSchema.optional().default("off"),
133
+ /** Frontend mock data workflow policy, service root hint, and deterministic mock verification commands. */
134
+ frontendMock: frontendMockConfigSchema.optional(),
135
+ /** Frontend browser-test batch and post-case token-stop configuration. */
136
+ frontendTest: frontendTestConfigSchema.optional(),
110
137
  notes: z.string().optional().default(""),
111
138
  });
112
139
  export const taskConfigSchema = z.preprocess((raw) => {
@@ -236,35 +236,35 @@ export function shouldInjectSubagentGuidance(step, mode) {
236
236
  return false;
237
237
  }
238
238
  /** Advisory guidance for `analyze-plan` mode. */
239
- export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
240
- You have access to the \`subagent\` tool for lightweight delegation within this step.
241
- Use it only for read-only tasks:
242
- - Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
243
- - Chain: scout -> planner (one subagent scouts, another plans based on findings)
244
- - Reviewer: have a subagent review your analysis/plan before finalizing
245
- Do NOT use subagent for writing, editing, or executing commands.
246
- Subagent output is advisory only; always verify and incorporate findings into your own output.
247
- Do NOT treat subagent results as authoritative state or artifact sources.
239
+ export const SUBAGENT_GUIDANCE_STANDARD = `<subagent_guidance>
240
+ You have access to the \`subagent\` tool for lightweight delegation within this step.
241
+ Use it only for read-only tasks:
242
+ - Parallel scout: dispatch multiple subagents to search/read different areas simultaneously
243
+ - Chain: scout -> planner (one subagent scouts, another plans based on findings)
244
+ - Reviewer: have a subagent review your analysis/plan before finalizing
245
+ Do NOT use subagent for writing, editing, or executing commands.
246
+ Subagent output is advisory only; always verify and incorporate findings into your own output.
247
+ Do NOT treat subagent results as authoritative state or artifact sources.
248
248
  </subagent_guidance>`;
249
249
  /** Strong guidance for `full` mode — prescriptive when to delegate. */
250
- export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
251
- You have access to the \`subagent\` tool for lightweight delegation within this step.
252
-
253
- You SHOULD delegate to subagent scouts when:
254
- - The task requires scanning 3+ directories or comparing implementations across modules
255
- - You would otherwise need 5+ sequential read/grep calls to gather context
256
- - A reviewer subagent can independently catch scope drift before you finalize your output
257
-
258
- Delegation saves context tokens and produces better results.
259
-
260
- Allowed patterns:
261
- - Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
262
- - Chain: scout -> planner (one subagent scouts, another plans based on findings)
263
- - Reviewer: have a subagent review your analysis/plan before finalizing
264
-
265
- Do NOT use subagent for writing, editing, or executing commands.
266
- Subagent output is advisory only; always verify and incorporate findings into your own output.
267
- Do NOT treat subagent results as authoritative state or artifact sources.
250
+ export const SUBAGENT_GUIDANCE_STRONG = `<subagent_guidance>
251
+ You have access to the \`subagent\` tool for lightweight delegation within this step.
252
+
253
+ You SHOULD delegate to subagent scouts when:
254
+ - The task requires scanning 3+ directories or comparing implementations across modules
255
+ - You would otherwise need 5+ sequential read/grep calls to gather context
256
+ - A reviewer subagent can independently catch scope drift before you finalize your output
257
+
258
+ Delegation saves context tokens and produces better results.
259
+
260
+ Allowed patterns:
261
+ - Parallel scout: dispatch 2-3 subagents simultaneously to cover different file trees
262
+ - Chain: scout -> planner (one subagent scouts, another plans based on findings)
263
+ - Reviewer: have a subagent review your analysis/plan before finalizing
264
+
265
+ Do NOT use subagent for writing, editing, or executing commands.
266
+ Subagent output is advisory only; always verify and incorporate findings into your own output.
267
+ Do NOT treat subagent results as authoritative state or artifact sources.
268
268
  </subagent_guidance>`;
269
269
  /** Preserved for backward compatibility (alias of STANDARD). */
270
270
  export const SUBAGENT_GUIDANCE = SUBAGENT_GUIDANCE_STANDARD;