@tea-agent/loop-agent 0.13.0-alpha.0 → 0.13.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 (199) hide show
  1. package/AGENTS.md +4 -0
  2. package/CHANGELOG.md +57 -53
  3. package/README.md +12 -3
  4. package/dist/application/dag/generate-task-dag.js +28 -58
  5. package/dist/application/evaluation/candidate-hash.js +75 -0
  6. package/dist/application/evaluation/candidate.js +52 -0
  7. package/dist/application/evaluation/replay.js +289 -0
  8. package/dist/application/evaluation/types.js +130 -0
  9. package/dist/cli/command-definitions.js +17 -4
  10. package/dist/cli/program.js +8 -4
  11. package/dist/commands/eval.js +235 -0
  12. package/dist/commands/init.js +131 -24
  13. package/dist/executors/pi-sdk-executor.js +38 -24
  14. package/dist/executors/shell-executor.js +226 -15
  15. package/dist/executors/shell-presets.js +20 -0
  16. package/dist/executors/shell-verification.js +7 -0
  17. package/dist/governance/manifest-types.js +1 -0
  18. package/dist/infrastructure/evaluation/candidate-store.js +439 -0
  19. package/dist/infrastructure/evaluation/store.js +40 -0
  20. package/dist/task/config-types.js +23 -0
  21. package/dist/worker/observe/routes.js +18 -3
  22. package/dist/worker/observe/spec-evidence.js +1 -1
  23. package/dist/worker/observe/static/dom.js +160 -1
  24. package/dist/worker/observe/static/state.js +14 -0
  25. package/dist/worker/observe/static/views/dag-inspector.js +35 -4
  26. package/dist/worker/observe/static/views/dag.js +9 -0
  27. package/dist/worker/observe/static/views/dashboard.js +702 -445
  28. package/dist/worker/observe/static/views/session-timeline.js +15 -1
  29. package/dist/workflows/dag/backend-test-analysis-contract.js +120 -0
  30. package/dist/workflows/dag/backend-test-case-manifest.js +503 -0
  31. package/dist/workflows/dag/backend-test-execution-contract.js +353 -0
  32. package/dist/workflows/dag/backend-test-result-contract.js +568 -0
  33. package/dist/workflows/dag/decision-envelope.js +57 -2
  34. package/dist/workflows/dag/dynamic-runtime/map.js +90 -2
  35. package/dist/workflows/dag/frontend-implementation-contract.js +240 -0
  36. package/dist/workflows/dag/frontend-project-capability.js +309 -0
  37. package/dist/workflows/dag/frontend-repair.js +341 -0
  38. package/dist/workflows/dag/frontend-risk.js +161 -0
  39. package/dist/workflows/dag/frontend-verification-trace.js +190 -0
  40. package/dist/workflows/dag/init-hybrid.js +2407 -297
  41. package/dist/workflows/dag/node-execution.js +9 -0
  42. package/dist/workflows/dag/prompt.js +9 -0
  43. package/dist/workflows/dag/repair-artifact.js +43 -3
  44. package/dist/workflows/dag/report.js +35 -1
  45. package/dist/workflows/dag/runner.js +28 -2
  46. package/dist/workflows/dag/skill-instructions.js +4 -2
  47. package/dist/workflows/dag/task-demand-routing.js +383 -0
  48. package/dist/workflows/dag/types.js +71 -13
  49. package/dist/workflows/dag/upstream-artifacts.js +1 -0
  50. package/dist/workflows/dag/validate.js +59 -1
  51. package/docs/README.md +6 -3
  52. package/docs/agent-dag-recovery-playbook.md +5 -3
  53. package/docs/agent-dag-runner.md +3 -3
  54. package/docs/architecture/README.md +3 -3
  55. package/docs/architecture/dag-execution.md +1 -1
  56. package/docs/architecture/evolution.md +13 -13
  57. package/docs/architecture/facts-and-state.md +1 -1
  58. package/docs/architecture/runtime-boundaries.md +7 -7
  59. package/docs/architecture/system-overview.md +3 -3
  60. package/docs/architecture/worker-and-feature.md +3 -3
  61. package/docs/design/README.md +124 -42
  62. package/docs/development-principles.md +4 -4
  63. package/docs/exec-plans/active/README.md +12 -11
  64. package/docs/exec-plans/completed/README.md +33 -0
  65. package/docs/feature-workflow.md +114 -39
  66. package/docs/init-surface.manifest.json +30 -3
  67. package/docs/loop-agent-harness.md +9 -8
  68. package/docs/production-readiness.md +1 -1
  69. package/docs/progress/README.md +23 -1
  70. package/docs/reports/README.md +65 -6
  71. package/docs/skills/vetted-skill-registry.md +2 -0
  72. package/docs/templates/agent-dag.schema.json +29 -1
  73. package/docs/templates/agent-dag.supervised-implementation.json +127 -8
  74. package/docs/templates/backend-test-analysis.schema.json +44 -0
  75. package/docs/templates/backend-test-case-manifest.schema.json +190 -0
  76. package/docs/templates/backend-test-dag.classify.prompt.md +75 -0
  77. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +81 -16
  78. package/docs/templates/backend-test-dag.json +311 -40
  79. package/docs/templates/backend-test-dag.retrospect.prompt.md +44 -30
  80. package/docs/templates/backend-test-dag.review-cases.prompt.md +10 -8
  81. package/docs/templates/backend-test-execution.schema.json +133 -0
  82. package/docs/templates/backend-test-result.schema.json +99 -0
  83. package/docs/templates/branch-merge-report.md +93 -0
  84. package/docs/templates/frontend-design-contract.md +9 -0
  85. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -0
  86. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -0
  87. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -0
  88. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -0
  89. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -0
  90. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -0
  91. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -0
  92. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -0
  93. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -0
  94. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -0
  95. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -0
  96. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -0
  97. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -0
  98. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -0
  99. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -0
  100. package/docs/templates/frontend-eval/metrics.md +138 -0
  101. package/docs/templates/frontend-eval/smoke-targets.md +53 -0
  102. package/docs/templates/frontend-implementation-contract.schema.json +27 -0
  103. package/docs/templates/frontend-task-constraints.md +10 -0
  104. package/docs/templates/frontend-task-requirement.md +9 -0
  105. package/docs/templates/frontend-test-dag.generate-cases.prompt.md +5 -0
  106. package/docs/templates/frontend-test-dag.json +23 -0
  107. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +3 -0
  108. package/docs/templates/frontend-test-dag.retrospect.prompt.md +3 -0
  109. package/docs/templates/frontend-test-dag.review-cases.prompt.md +3 -0
  110. package/docs/templates/frontend-test-dag.review-execution.prompt.md +3 -0
  111. package/docs/templates/knowledge-graph-bootstrap-dag.json +1 -1
  112. package/docs/templates/knowledge-sync-dag.json +1 -0
  113. package/docs/verification-matrix.md +4 -1
  114. package/examples/decision-gate-agent-dag.json +4 -4
  115. package/examples/hybrid-loop-agent-dag.json +1 -1
  116. package/package.json +2 -2
  117. package/scripts/kb-bootstrap-init-skeleton.sh +2 -1
  118. package/scripts/kb-graph-incremental-prepare.mjs +19 -5
  119. package/scripts/kb-graph-promote.mjs +12 -1
  120. package/skills/ai-engineering-context/SKILL.md +2 -2
  121. package/skills/analyze-product-dependencies/SKILL.md +67 -0
  122. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -0
  123. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -0
  124. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -0
  125. package/skills/analyze-product-dependencies/references/example.md +76 -0
  126. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -0
  127. package/skills/analyze-product-dependencies/references/input-contract.md +11 -0
  128. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -0
  129. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -0
  130. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -0
  131. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -0
  132. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -0
  133. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -0
  134. package/skills/analyze-product-requirements/SKILL.md +90 -0
  135. package/skills/analyze-product-requirements/agents/openai.yaml +4 -0
  136. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -0
  137. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -0
  138. package/skills/analyze-product-requirements/references/example.md +86 -0
  139. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -0
  140. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -0
  141. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -0
  142. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -0
  143. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -0
  144. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -0
  145. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -0
  146. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -0
  147. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -0
  148. package/skills/browser-tools/SKILL.md +196 -0
  149. package/skills/browser-tools/browser-content.js +103 -0
  150. package/skills/browser-tools/browser-cookies.js +35 -0
  151. package/skills/browser-tools/browser-eval.js +53 -0
  152. package/skills/browser-tools/browser-hn-scraper.js +108 -0
  153. package/skills/browser-tools/browser-nav.js +44 -0
  154. package/skills/browser-tools/browser-pick.js +162 -0
  155. package/skills/browser-tools/browser-screenshot.js +34 -0
  156. package/skills/browser-tools/browser-start.js +86 -0
  157. package/skills/browser-tools/package-lock.json +2556 -0
  158. package/skills/browser-tools/package.json +19 -0
  159. package/skills/frontend-design-review/SKILL.md +6 -1
  160. package/skills/frontend-design-review/references/review-checklist.md +25 -4
  161. package/skills/frontend-implementation/SKILL.md +25 -30
  162. package/skills/frontend-implementation/references/code-standards.md +20 -22
  163. package/skills/frontend-implementation/references/node-contracts.md +17 -53
  164. package/skills/frontend-review/SKILL.md +10 -4
  165. package/skills/frontend-review/references/review-findings.md +8 -3
  166. package/skills/frontend-verification/SKILL.md +22 -9
  167. package/skills/frontend-verification/references/verification-checklist.md +17 -5
  168. package/skills/grill-with-docs/SKILL.md +5 -5
  169. package/skills/grill-with-docs/adr-format.md +3 -3
  170. package/skills/init-capability-evolution/SKILL.md +5 -5
  171. package/skills/loop-agent/SKILL.md +5 -5
  172. package/skills/loop-agent/references/README.md +3 -3
  173. package/skills/loop-agent/references/command-reference.md +98 -24
  174. package/skills/loop-agent/references/docs-converge.md +15 -15
  175. package/skills/loop-agent/references/harness-policy.md +2 -2
  176. package/skills/loop-agent/references/hybrid-dag.md +32 -22
  177. package/skills/loop-agent/references/multi-worktree.md +1 -1
  178. package/skills/loop-agent/references/orchestrator-and-interventions.md +8 -8
  179. package/skills/loop-agent/references/task-workflow.md +1 -1
  180. package/skills/loop-agent/references/verification-and-failure-handling.md +6 -4
  181. package/skills/playwright-cli/SKILL.md +420 -0
  182. package/skills/playwright-cli/references/element-attributes.md +23 -0
  183. package/skills/playwright-cli/references/playwright-tests.md +39 -0
  184. package/skills/playwright-cli/references/request-mocking.md +87 -0
  185. package/skills/playwright-cli/references/running-code.md +241 -0
  186. package/skills/playwright-cli/references/session-management.md +225 -0
  187. package/skills/playwright-cli/references/storage-state.md +275 -0
  188. package/skills/playwright-cli/references/test-generation.md +433 -0
  189. package/skills/playwright-cli/references/tracing.md +139 -0
  190. package/skills/playwright-cli/references/video-recording.md +143 -0
  191. package/skills/playwright-cli-case-generator/SKILL.md +74 -0
  192. package/skills/requesting-code-review/SKILL.md +1 -1
  193. package/skills/systematic-debugging/CREATION-LOG.md +3 -3
  194. package/skills/systematic-debugging/SKILL.md +1 -1
  195. package/skills/systematic-debugging/test-academic.md +1 -1
  196. package/skills/systematic-debugging/test-pressure-1.md +1 -1
  197. package/skills/systematic-debugging/test-pressure-2.md +1 -1
  198. package/skills/systematic-debugging/test-pressure-3.md +1 -1
  199. package/skills/verification-before-completion/SKILL.md +1 -1
@@ -0,0 +1,235 @@
1
+ import path from "node:path";
2
+ import { writeTextAtomic } from "../infrastructure/harness/atomic-write.js";
3
+ import { readReplayScorecard } from "../infrastructure/evaluation/store.js";
4
+ import { formatReplayMarkdown, replayEvaluation, } from "../application/evaluation/replay.js";
5
+ import { formatCandidateMarkdown, listCandidates, registerCandidate, showCandidate, transitionCandidate, } from "../application/evaluation/candidate.js";
6
+ import { lifecycleStateSchema, } from "../application/evaluation/types.js";
7
+ const USAGE = "usage: eval <replay|report|candidate> ...; candidate <register|show|list|transition> [--json|--markdown]";
8
+ function parseFormatFlags(args) {
9
+ const json = args.includes("--json");
10
+ const markdown = args.includes("--markdown");
11
+ if (json && markdown) {
12
+ throw new Error("eval accepts only one of --json or --markdown");
13
+ }
14
+ return { json: json || !markdown, markdown };
15
+ }
16
+ function flagValue(args, flag) {
17
+ const index = args.indexOf(flag);
18
+ if (index >= 0) {
19
+ const value = args[index + 1];
20
+ if (!value || value.startsWith("-")) {
21
+ throw new Error(`${flag} requires a value`);
22
+ }
23
+ return value;
24
+ }
25
+ const prefix = `${flag}=`;
26
+ return args.find((arg) => arg.startsWith(prefix))?.slice(prefix.length);
27
+ }
28
+ function assertKnownFlags(args, allowed) {
29
+ for (let i = 0; i < args.length; i += 1) {
30
+ const arg = args[i];
31
+ if (!arg.startsWith("-"))
32
+ continue;
33
+ const key = arg.split("=", 1)[0];
34
+ if (!allowed.includes(key)) {
35
+ throw new Error(`unknown eval argument: ${arg}`);
36
+ }
37
+ if ([
38
+ "--spec",
39
+ "--output",
40
+ "--replay-id",
41
+ "--manifest",
42
+ "--candidate-id",
43
+ "--to",
44
+ "--reason",
45
+ ].includes(key) &&
46
+ !arg.includes("=")) {
47
+ i += 1;
48
+ }
49
+ }
50
+ }
51
+ function parseCandidateArgs(rest) {
52
+ const [action, ...tail] = rest;
53
+ if (action === "register") {
54
+ assertKnownFlags(tail, ["--manifest", "--json", "--markdown"]);
55
+ const manifestPath = flagValue(tail, "--manifest");
56
+ if (!manifestPath) {
57
+ throw new Error("eval candidate register requires --manifest <path>");
58
+ }
59
+ return {
60
+ command: "candidate",
61
+ action: "register",
62
+ manifestPath,
63
+ ...parseFormatFlags(tail),
64
+ };
65
+ }
66
+ if (action === "show") {
67
+ assertKnownFlags(tail, ["--candidate-id", "--json", "--markdown"]);
68
+ const candidateId = flagValue(tail, "--candidate-id");
69
+ if (!candidateId) {
70
+ throw new Error("eval candidate show requires --candidate-id <id>");
71
+ }
72
+ return {
73
+ command: "candidate",
74
+ action: "show",
75
+ candidateId,
76
+ ...parseFormatFlags(tail),
77
+ };
78
+ }
79
+ if (action === "list") {
80
+ assertKnownFlags(tail, ["--json", "--markdown"]);
81
+ return {
82
+ command: "candidate",
83
+ action: "list",
84
+ ...parseFormatFlags(tail),
85
+ };
86
+ }
87
+ if (action === "transition") {
88
+ assertKnownFlags(tail, [
89
+ "--candidate-id",
90
+ "--to",
91
+ "--reason",
92
+ "--json",
93
+ "--markdown",
94
+ ]);
95
+ const candidateId = flagValue(tail, "--candidate-id");
96
+ const toRaw = flagValue(tail, "--to");
97
+ const reason = flagValue(tail, "--reason");
98
+ if (!candidateId) {
99
+ throw new Error("eval candidate transition requires --candidate-id <id>");
100
+ }
101
+ if (!toRaw) {
102
+ throw new Error("eval candidate transition requires --to <state>");
103
+ }
104
+ if (!reason) {
105
+ throw new Error("eval candidate transition requires --reason <text>");
106
+ }
107
+ const to = lifecycleStateSchema.parse(toRaw);
108
+ return {
109
+ command: "candidate",
110
+ action: "transition",
111
+ candidateId,
112
+ to,
113
+ reason,
114
+ ...parseFormatFlags(tail),
115
+ };
116
+ }
117
+ throw new Error("usage: eval candidate <register|show|list|transition> ...");
118
+ }
119
+ export function parseEvalArgs(args) {
120
+ const [command, ...rest] = args;
121
+ if (command === "replay") {
122
+ assertKnownFlags(rest, ["--spec", "--output", "--json", "--markdown"]);
123
+ const specPath = flagValue(rest, "--spec");
124
+ if (!specPath)
125
+ throw new Error("eval replay requires --spec <path>");
126
+ return {
127
+ command,
128
+ specPath,
129
+ outputPath: flagValue(rest, "--output"),
130
+ ...parseFormatFlags(rest),
131
+ };
132
+ }
133
+ if (command === "report") {
134
+ assertKnownFlags(rest, ["--replay-id", "--json", "--markdown"]);
135
+ const replayId = flagValue(rest, "--replay-id");
136
+ if (!replayId)
137
+ throw new Error("eval report requires --replay-id <id>");
138
+ return { command, replayId, ...parseFormatFlags(rest) };
139
+ }
140
+ if (command === "candidate") {
141
+ return parseCandidateArgs(rest);
142
+ }
143
+ throw new Error(USAGE);
144
+ }
145
+ function printScorecard(input) {
146
+ if (input.json) {
147
+ console.log(JSON.stringify(input.scorecard, null, 2));
148
+ return;
149
+ }
150
+ process.stdout.write(input.markdown);
151
+ }
152
+ function printCandidate(input) {
153
+ if (input.json) {
154
+ console.log(JSON.stringify(input.record, null, 2));
155
+ return;
156
+ }
157
+ process.stdout.write(formatCandidateMarkdown(input.record));
158
+ }
159
+ export async function runEval(repoRoot, args) {
160
+ const parsed = parseEvalArgs(args);
161
+ if (parsed.command === "replay") {
162
+ const result = await replayEvaluation({
163
+ repoRoot,
164
+ specPath: parsed.specPath,
165
+ });
166
+ if (parsed.outputPath) {
167
+ const outputPath = path.resolve(repoRoot, parsed.outputPath);
168
+ await writeTextAtomic(outputPath, parsed.markdown
169
+ ? result.markdown
170
+ : `${JSON.stringify(result.scorecard, null, 2)}\n`, { repoRoot });
171
+ }
172
+ printScorecard({
173
+ scorecard: result.scorecard,
174
+ markdown: result.markdown,
175
+ json: parsed.json,
176
+ });
177
+ return;
178
+ }
179
+ if (parsed.command === "report") {
180
+ const scorecard = (await readReplayScorecard(repoRoot, parsed.replayId));
181
+ const markdown = formatReplayMarkdown(scorecard);
182
+ printScorecard({ scorecard, markdown, json: parsed.json });
183
+ return;
184
+ }
185
+ // candidate subcommands
186
+ if (parsed.action === "register") {
187
+ const result = await registerCandidate({
188
+ repoRoot,
189
+ manifestPath: parsed.manifestPath,
190
+ });
191
+ if (parsed.json) {
192
+ console.log(JSON.stringify({
193
+ idempotent: result.idempotent,
194
+ manifestPath: result.manifestPath,
195
+ lifecyclePath: result.lifecyclePath,
196
+ record: result.record,
197
+ }, null, 2));
198
+ return;
199
+ }
200
+ process.stdout.write(`${result.idempotent ? "idempotent " : ""}registered ${result.record.manifest.candidateId}\n${formatCandidateMarkdown(result.record)}`);
201
+ return;
202
+ }
203
+ if (parsed.action === "show") {
204
+ const record = await showCandidate({
205
+ repoRoot,
206
+ candidateId: parsed.candidateId,
207
+ });
208
+ printCandidate({ record, json: parsed.json });
209
+ return;
210
+ }
211
+ if (parsed.action === "list") {
212
+ const rows = await listCandidates({ repoRoot });
213
+ if (parsed.json) {
214
+ console.log(JSON.stringify(rows, null, 2));
215
+ return;
216
+ }
217
+ const lines = [
218
+ "# Candidates",
219
+ "",
220
+ ...rows.map((row) => `- \`${row.candidateId}\` status=\`${row.status}\` hash=\`${row.bundleHash}\` promotionApplied=false`),
221
+ "",
222
+ ];
223
+ process.stdout.write(`${lines.join("\n")}\n`);
224
+ return;
225
+ }
226
+ if (parsed.action === "transition") {
227
+ const record = await transitionCandidate({
228
+ repoRoot,
229
+ candidateId: parsed.candidateId,
230
+ to: parsed.to,
231
+ reason: parsed.reason,
232
+ });
233
+ printCandidate({ record, json: parsed.json });
234
+ }
235
+ }
@@ -679,9 +679,34 @@ function buildManagedAgentsBlock(input) {
679
679
  "- 保留无关的用户改动,不要回退自己没有做的修改。",
680
680
  "- 委托模型写入前,必须把写入边界写成结构化 `task.json.allowedPaths` / `task.json.forbiddenPaths`,再审查生成 DAG 的 writer `writeSet`;不要只依赖 `source/执行约束.md` 的自然语言约束。",
681
681
  "",
682
+ "### 自然语言入口路由",
683
+ "",
684
+ "用户用中文表达意图时,按以下三类入口路由;只读与写入必须严格区分。",
685
+ "",
686
+ "| 用户表达 | 入口 | 执行动作 |",
687
+ "|---|---|---|",
688
+ "| loop-agent 初始化 / loop agent 初始化 / loop agent初始化 / 初始化 loop-agent | 初始化 | 完成确定性初始化闭环 |",
689
+ "| 初始化更新校验 / 检查初始化更新 / loop-agent 初始化更新校验 / loop agent初始化更新校验 | 更新校验 | 只读报告,不写入 |",
690
+ "| 初始化安全更新 / 应用初始化更新 / loop-agent 初始化安全更新 / loop agent初始化安全更新 | 安全更新 | check-update 后只执行确定性安全动作 |",
691
+ "",
692
+ "**更新校验(只读)**:用户说“初始化更新校验”“检查初始化更新”时,只读执行下面命令,汇报 deterministic actions、model merge、human decisions 和 next steps。**这一步不得自动执行 `apply-safe` 或模型合并。**",
693
+ "",
694
+ "```bash",
695
+ "loop-agent init check-update --repo-root . --markdown",
696
+ "```",
697
+ "",
698
+ "**安全更新(写入型)**:用户明确说“初始化安全更新”“应用初始化更新”时,先 check-update,再执行确定性安全动作;surface 缺失时先 bootstrap,存在 human decisions 时停下等用户决策。model merge 任务必须带 allowed/forbidden paths,且不得自动决策。安全更新完成后必须复查 `loop-agent init doctor --repo-root .`、`loop-agent inspect --repo-root .`、`loop-agent docs audit --repo-root .` 与目标项目治理检查(如 `bash scripts/check-repo.sh`)。",
699
+ "",
700
+ "```bash",
701
+ "loop-agent init check-update --repo-root . --markdown",
702
+ "loop-agent init update --repo-root . --apply-safe",
703
+ "```",
704
+ "",
705
+ "仅当 check-update 报告 surface 缺失时才执行 `loop-agent init update --repo-root . --bootstrap-surface`。",
706
+ "",
682
707
  "### 初始化闭环",
683
708
  "",
684
- `当用户要求在本仓库初始化 loop-agent,或发现 \`README.md\` / \`${input.governanceRoot}/verification-matrix.md\` 仍停留在通用初始化状态时,模型应在同一轮工作中完成初始化闭环,不要把补全文档和验证矩阵留给用户交互。`,
709
+ `当用户要求初始化 loop-agent(“初始化 loop-agent”“loop agent 初始化”“loop agent初始化”“loop-agent 初始化”),或发现 \`README.md\` / \`${input.governanceRoot}/verification-matrix.md\` 仍停留在通用初始化状态时,模型应在同一轮工作中完成初始化闭环,不要把补全文档和验证矩阵留给用户交互。`,
685
710
  "",
686
711
  "标准流程:",
687
712
  "",
@@ -731,6 +756,22 @@ function buildManagedAgentsBlock(input) {
731
756
  "",
732
757
  "执行 DAG 前必须审查 profile routing、governance profile、writer 的 `writeSet`、`allowedPaths`、`forbiddenPaths`、shell verification 和 decision gate mode。不要执行占位或过宽的写入范围。",
733
758
  "",
759
+ "### 任务类型路由(taskKind)",
760
+ "",
761
+ "- 用户明确提出后端测试、接口/API 测试、pytest,或语境明确为后端的自动化测试时,必须把 `.harness/tasks/<task-id>/task.json` 的 `taskKind` 设置为 `\"backend-test\"`,不得保留默认 `standard`。",
762
+ "- `backend-test` 是 `taskKind`,不是 `--profile` 的可选值;运行 `dag run-task` 时继续使用 `--profile auto`,也可按需显式选择 `minimal` / `standard` / `reviewed` / `supervised`,不要把业务模板名当作 profile。",
763
+ "- 仅出现“自动化测试”且无法判断前后端时,先阅读任务源与目标项目技术栈再决定,禁止无条件路由到 `backend-test`。",
764
+ "",
765
+ "### 运行看板(只读)",
766
+ "",
767
+ "启动本地只读运行看板:",
768
+ "",
769
+ "```bash",
770
+ "agent-worker observe serve --repo . --port 8787",
771
+ "```",
772
+ "",
773
+ "浏览器打开 `http://127.0.0.1:8787/`。看板只读,默认只绑定本机 `127.0.0.1`;不要直接暴露到公开网络。",
774
+ "",
734
775
  "### DAG 诊断与收口",
735
776
  "",
736
777
  "执行后优先使用 `loop-agent dag report --run-id <run-id> --markdown` 读取 run facts;失败或 paused run 使用 `loop-agent dag doctor --run-id <run-id> --markdown` 诊断。失败 DAG run 不应写成成功 closeout,应使用 `loop-agent dag closeout-draft --run-id <run-id>` 生成 failure handoff,保留 what failed、evidence、classification、recommended follow-up、safe retry conditions 和 human decision needed。",
@@ -805,6 +846,12 @@ export function buildManagedGitignoreBlock() {
805
846
  "# one-shot executor runs",
806
847
  ".harness/runs/*",
807
848
  "!.harness/runs/.gitkeep",
849
+ "!.harness/runs/active/",
850
+ "!.harness/runs/active/.gitkeep",
851
+ "!.harness/runs/completed/",
852
+ "!.harness/runs/completed/.gitkeep",
853
+ "!.harness/runs/failed/",
854
+ "!.harness/runs/failed/.gitkeep",
808
855
  "",
809
856
  "# session / cache / logs / recomputable surface state",
810
857
  ".harness/live/",
@@ -816,6 +863,9 @@ export function buildManagedGitignoreBlock() {
816
863
  ".harness/task-pool/*",
817
864
  ".task-pool/",
818
865
  "",
866
+ "# repo-local skill dependencies installed inside projected skills",
867
+ ".agents/skills/*/node_modules/",
868
+ "",
819
869
  "# multi-worktree parallel mode",
820
870
  ".worktrees/",
821
871
  GITIGNORE_BLOCK_END,
@@ -980,6 +1030,26 @@ async function ensureHarnessDirs(repoRoot, written) {
980
1030
  await mkdir(path.join(repoRoot, dir), { recursive: true });
981
1031
  written.push(dir);
982
1032
  }
1033
+ const placeholders = [
1034
+ ".harness/tasks/.gitkeep",
1035
+ ".harness/dag-runs/active/.gitkeep",
1036
+ ".harness/dag-runs/completed/.gitkeep",
1037
+ ".harness/dag-runs/paused/.gitkeep",
1038
+ ".harness/runs/.gitkeep",
1039
+ ".harness/runs/active/.gitkeep",
1040
+ ".harness/runs/completed/.gitkeep",
1041
+ ".harness/runs/failed/.gitkeep",
1042
+ ];
1043
+ for (const relativePath of placeholders) {
1044
+ const target = path.join(repoRoot, relativePath);
1045
+ try {
1046
+ await access(target);
1047
+ }
1048
+ catch {
1049
+ await writeFile(target, "", "utf8");
1050
+ written.push(relativePath);
1051
+ }
1052
+ }
983
1053
  }
984
1054
  async function writeCompatPrompts(input) {
985
1055
  for (const [name, content] of Object.entries(COMPAT_PROMPTS)) {
@@ -1216,7 +1286,7 @@ async function buildCurrentSurfaceState(input) {
1216
1286
  repoRoot: input.repoRoot,
1217
1287
  projectName: input.projectName,
1218
1288
  governanceRoot: input.governanceRoot,
1219
- entry,
1289
+ entry: manifestEntry,
1220
1290
  });
1221
1291
  const sourceSha256 = desired.content === undefined ? undefined : sha256Text(desired.content);
1222
1292
  const base = {
@@ -1292,6 +1362,19 @@ async function buildCurrentSurfaceState(input) {
1292
1362
  }
1293
1363
  async function writeInitSurfaceState(input) {
1294
1364
  const state = await buildCurrentSurfaceState(input);
1365
+ if (input.stateKind === "recorded" && input.preserveOwnershipFrom) {
1366
+ for (const [pathName, file] of Object.entries(state.files)) {
1367
+ if (file.relationship !== "local-existing-unknown")
1368
+ continue;
1369
+ const priorHash = input.preserveOwnershipFrom.files[pathName]?.currentSha256;
1370
+ if (priorHash !== undefined) {
1371
+ file.currentSha256 = priorHash;
1372
+ }
1373
+ else {
1374
+ delete file.currentSha256;
1375
+ }
1376
+ }
1377
+ }
1295
1378
  const target = path.join(input.repoRoot, INIT_SURFACE_STATE_PATH);
1296
1379
  await mkdir(path.dirname(target), { recursive: true });
1297
1380
  await writeFile(target, `${JSON.stringify(state, null, 2)}\n`, "utf-8");
@@ -1824,14 +1907,45 @@ function buildTargetFeatureWorkflow(input) {
1824
1907
  DAG_HARD_GATE_TRIGGER,
1825
1908
  "",
1826
1909
  "Before executing a DAG, review profile routing, governance profile, writer writeSet, allowed paths, forbidden paths, shell verification, and decision gate mode.",
1910
+ "Generated DAGs bind authoritative task-source paths, SHA-256 hashes, and explicit REQ/BR/AC identifiers. Frontend plans with explicit identifiers pass a deterministic coverage gate before final design review and implementation.",
1911
+ "After an interrupted run, repair the task source and regenerate the complete DAG. Do not construct an impl-only recovery DAG from an upstream summary; strict governance rejects a v3 orphan writer without sourceBinding or a read-only planner ancestor.",
1827
1912
  "",
1828
1913
  "## Specialized Task Kinds",
1829
1914
  "",
1830
- "Set `taskKind` in `.harness/tasks/<task-id>/task.json` to route a task to a dedicated DAG profile instead of the default auto profile:",
1915
+ "- standard tasks are classified conservatively from the task title, `source/需求.md`, and structured `allowedPaths`.",
1916
+ "- Only high-confidence frontend implementation demands select the frontend DAG automatically; backend, mixed, negated, and unknown demands keep the template selected by the normal governance profile.",
1917
+ "- `frontend-mock-assess-pi` reads Mock/API/schema evidence and selects `native|browser-intercept|request-adapter|not-needed|blocked` before frontend planning; its deterministic gate rejects blocked or malformed output, while the existing frontend implementer remains the only writer.",
1918
+ "- Mock-backed verification never proves real API integration. When the backend was not exercised, closeout must retain the gap and name `<task-id>-real-api-integration-verify`; that follow-up is explicitly created/run after backend readiness, never automatic.",
1919
+ "- Automatic frontend classification never replaces a supervised template selected by an explicit profile, workflowPolicy, or the supervised quality gate.",
1920
+ "- A backend implementation does not select the backend test DAG. `backend-test` remains an explicit test-engineering workflow.",
1921
+ "- Explicit specialized `taskKind` values remain compatible and take precedence over task-source classification.",
1831
1922
  "",
1923
+ "Set an explicit specialized `taskKind` in `.harness/tasks/<task-id>/task.json` only when the dedicated workflow itself is part of the task contract:",
1924
+ "",
1925
+ "- `taskKind: \"frontend-implementation\"` explicitly selects the frontend DAG for compatibility or intentional override. Optional `frontendMock` config sets `policy: auto|required|disabled`, an existing `serviceRoot`, and generation-time-frozen `verifyCommands`; an unsafe or incomplete explicit required contract produces an assessment-only DAG with no writer, while a complete required contract adds Mock-specific verification only when trusted commands exist. Auto API tasks without a native service may use an existing browser interception harness or reversible request adapter, then continue through static and behavior verification.",
1832
1926
  "- `taskKind: \"backend-test\"` selects the dedicated backend test DAG. Its Pi nodes analyze requirements, generate and review backend cases, generate pytest, and retrospect on results; shell gate/execution nodes enforce the review verdict and run the target project's pytest. The backend test templates (`backend-test-dag.json` and the `backend-test-dag.*.prompt.md` files) ship inside the loop-agent package as static references and are projected to target projects under the governance `templates/` directory.",
1927
+ "- `taskKind: \"knowledge-sync\"` selects the Feature-scoped test-knowledge write-back DAG (collect → draft → validate → apply → pointer). Bind `featureId` in `task.json` (or hardConstraints / requirement text). It writes only under `features/<featureId>/…` after final verification evidence exists.",
1928
+ "- `taskKind: \"knowledge-graph-bootstrap\"` selects the business knowledge-graph bootstrap DAG (preflight → inventory → propose → validate → review → gate → promote → materialize). AI writes only `knowledge/bootstrap/staging/**`; promote is merge-new-only.",
1929
+ "- `taskKind: \"frontend-test\"` selects the FE-test RAG DAG. It writes a traceable frontend RAG package and Markdown case manifest, then executes manifest cases serially with `playwright-cli` in isolated test environments and retains per-case evidence. It never generates pytest or Playwright source code. `frontendTest.maxCasesPerBatch` defaults to 20 (maximum 50); optional `maxTokensPerCase` and `maxTotalTokens` stop only later cases after a completed case's token usage is recorded, marking them `blocked: token-budget-exhausted`. Generated browser startup uses `playwright-cli open --browser=chrome --headed <base-url>`; the generic playwright-cli skill is unchanged.",
1833
1930
  "- Only eligible read-only Pi nodes (planner, scout, reviewer, verifier, closeout with no write-capable tool profile) receive the conservative automatic retry policy. Supervisor, implementer, writer, docs-only, dynamic, shell, static, and decision-gate nodes are not retried automatically. Eligible nodes cannot write repository files; the controller only records immutable attempt evidence under `.harness/dag-runs/<state>/<run-id>/<node-id>/attempt-<n>.json`.",
1834
1931
  "",
1932
+ "Use the package-backed public knowledge CLI for graph operations. Do not require target projects to run package-only kb runtime scripts:",
1933
+ "",
1934
+ "```bash",
1935
+ "loop-agent knowledge graph-init --product-name <name>",
1936
+ "# edit knowledge/bootstrap/scope.yaml, then:",
1937
+ "loop-agent dag run-task <task-id> # taskKind: knowledge-graph-bootstrap",
1938
+ "loop-agent knowledge query --mode by_feature --feature F-2026-004 --json",
1939
+ "loop-agent knowledge query --mode by_id --id SVC-order --json",
1940
+ "loop-agent knowledge query --mode search --text \"keyword\" --json",
1941
+ "loop-agent knowledge graph-incremental-prepare --feature F-2026-004 --service <service>",
1942
+ "# review the prepared scope/staging; for a manual reviewed promotion:",
1943
+ "loop-agent knowledge graph-promote",
1944
+ "loop-agent knowledge graph-materialize",
1945
+ "```",
1946
+ "",
1947
+ "Daily Feature test-knowledge write-back still uses `taskKind: \"knowledge-sync\"` with a bound `featureId`, separate from graph bootstrap/incremental entry points.",
1948
+ "",
1835
1949
  "## Verification",
1836
1950
  "",
1837
1951
  `Use \`${input.governanceRoot}/verification-matrix.md\` to choose the narrowest command that proves the claim.`,
@@ -2525,38 +2639,31 @@ export async function applyInitUpdate(input) {
2525
2639
  });
2526
2640
  }
2527
2641
  if (input.applySafe) {
2642
+ const existingSurface = await readExistingSurfaceState(repoRoot);
2643
+ // Preserve source strength: recorded stays recorded so unchanged owned files
2644
+ // remain deterministic refresh candidates; bootstrap/inferred stays inferred.
2645
+ const preservedStateKind = existingSurface?.stateKind === "recorded" ? "recorded" : "inferred-baseline";
2528
2646
  const report = await checkInitUpdate({ repoRoot, projectName, governanceRoot });
2529
2647
  for (const action of report.deterministicActions) {
2530
2648
  if (action.type === "bootstrap-surface") {
2531
2649
  skipped.push(action);
2532
2650
  continue;
2533
2651
  }
2534
- if (action.type !== "strip-pi-requires-api-key" &&
2535
- action.type !== "add-harness-schema-ref" &&
2536
- action.type !== "refresh-owned-file" &&
2537
- action.type !== "migrate-harness-governance-root" &&
2538
- action.type !== "migrate-harness-model-fields" &&
2539
- action.type !== "migrate-owned-file" &&
2540
- action.type !== "remove-owned-file" &&
2541
- action.type !== "remove-empty-directory") {
2542
- const currentState = await buildCurrentSurfaceState({
2543
- repoRoot,
2544
- projectName,
2545
- governanceRoot,
2546
- stateKind: "inferred-baseline",
2547
- });
2548
- const relationship = currentState.files[action.path]?.relationship;
2549
- if (relationship === "local-existing-unknown") {
2550
- skipped.push(action);
2551
- continue;
2552
- }
2553
- }
2652
+ // The report was produced immediately before this batch. Each action also
2653
+ // performs its own target check; missing-file writes refuse existing paths,
2654
+ // while managed blocks merge into the latest user-authored content.
2554
2655
  if (await applySafeAction({ repoRoot, projectName, governanceRoot, action }))
2555
2656
  applied.push(action);
2556
2657
  else
2557
2658
  skipped.push(action);
2558
2659
  }
2559
- await writeInitSurfaceState({ repoRoot, projectName, governanceRoot, stateKind: "inferred-baseline" });
2660
+ await writeInitSurfaceState({
2661
+ repoRoot,
2662
+ projectName,
2663
+ governanceRoot,
2664
+ stateKind: preservedStateKind,
2665
+ preserveOwnershipFrom: existingSurface,
2666
+ });
2560
2667
  }
2561
2668
  return {
2562
2669
  applied,
@@ -51,31 +51,38 @@ export function setPiSdkImportOverrideForTests(fn) {
51
51
  export function setPiSdkModuleOverrideForTests(fn) {
52
52
  sdkModuleOverrideForTests = fn;
53
53
  }
54
- /** Check whether the Pi SDK optional dependency is importable. */
54
+ /** Check whether the Pi SDK optional dependency satisfies the 0.80.10 runtime contract. */
55
55
  export async function checkPiSdkAvailability(_repoRoot) {
56
56
  if (sdkSessionFactoryOverride) {
57
57
  return { ok: true, detail: 'pi SDK session factory override active' };
58
58
  }
59
59
  try {
60
- if (sdkImportOverrideForTests) {
61
- await sdkImportOverrideForTests();
60
+ const imported = sdkImportOverrideForTests
61
+ ? await sdkImportOverrideForTests()
62
+ : await loadPiSdkModule();
63
+ const sdk = imported;
64
+ const ModelRuntime = sdk.ModelRuntime;
65
+ if (typeof sdk.createAgentSession !== 'function'
66
+ || typeof sdk.getAgentDir !== 'function'
67
+ || typeof ModelRuntime?.create !== 'function') {
68
+ return {
69
+ ok: false,
70
+ detail: 'pi SDK incompatible: requires createAgentSession, getAgentDir, and ModelRuntime.create (0.80.10 contract)',
71
+ };
62
72
  }
63
- else {
64
- await import('@earendil-works/pi-coding-agent');
65
- }
66
- return { ok: true, detail: 'pi SDK available' };
73
+ return { ok: true, detail: 'pi SDK 0.80.10 contract available' };
67
74
  }
68
75
  catch (error) {
69
76
  const message = error instanceof Error ? error.message : String(error);
70
77
  return { ok: false, detail: `pi SDK not available: ${message}` };
71
78
  }
72
79
  }
73
- async function resolveModelViaRegistry(modelRegistry, provider, modelId) {
74
- const fromRegistry = modelRegistry.find(provider, modelId);
75
- if (fromRegistry)
76
- return fromRegistry;
80
+ async function resolveModel(modelRuntime, provider, modelId) {
81
+ const fromRuntime = modelRuntime.getModel(provider, modelId);
82
+ if (fromRuntime)
83
+ return fromRuntime;
77
84
  try {
78
- const piAi = await import('@earendil-works/pi-ai');
85
+ const piAi = await import('@earendil-works/pi-ai/compat');
79
86
  return piAi.getModel?.(provider, modelId);
80
87
  }
81
88
  catch {
@@ -93,12 +100,16 @@ async function getOrCreateSharedResources(state) {
93
100
  return state.resources;
94
101
  const sdk = await loadPiSdkModule();
95
102
  const getAgentDir = sdk.getAgentDir;
96
- const AuthStorage = sdk.AuthStorage;
97
- const ModelRegistry = sdk.ModelRegistry;
103
+ const ModelRuntime = sdk.ModelRuntime;
104
+ if (typeof ModelRuntime?.create !== 'function') {
105
+ throw new Error('incompatible pi SDK: ModelRuntime.create is unavailable');
106
+ }
98
107
  const agentDir = getAgentDir();
99
- const authStorage = AuthStorage.create();
100
- const modelRegistry = ModelRegistry.create(authStorage);
101
- state.resources = { agentDir, authStorage, modelRegistry };
108
+ const modelRuntime = await ModelRuntime.create({
109
+ authPath: path.join(agentDir, 'auth.json'),
110
+ modelsPath: path.join(agentDir, 'models.json'),
111
+ });
112
+ state.resources = { agentDir, modelRuntime };
102
113
  return state.resources;
103
114
  }
104
115
  async function createSdkSession(sdk, input, shared) {
@@ -106,13 +117,17 @@ async function createSdkSession(sdk, input, shared) {
106
117
  const SessionManager = sdk.SessionManager;
107
118
  const DefaultResourceLoader = sdk.DefaultResourceLoader;
108
119
  const getAgentDir = sdk.getAgentDir;
109
- const AuthStorage = sdk.AuthStorage;
110
- const ModelRegistry = sdk.ModelRegistry;
120
+ const ModelRuntime = sdk.ModelRuntime;
111
121
  const agentDir = shared?.agentDir ?? getAgentDir();
112
- const authStorage = shared?.authStorage ?? AuthStorage.create();
113
- const modelRegistry = shared?.modelRegistry ?? ModelRegistry.create(authStorage);
122
+ if (!shared && typeof ModelRuntime?.create !== 'function') {
123
+ throw new Error('incompatible pi SDK: ModelRuntime.create is unavailable');
124
+ }
125
+ const modelRuntime = shared?.modelRuntime ?? await ModelRuntime.create({
126
+ authPath: path.join(agentDir, 'auth.json'),
127
+ modelsPath: path.join(agentDir, 'models.json'),
128
+ });
114
129
  const model = input.provider && input.model
115
- ? await resolveModelViaRegistry(modelRegistry, input.provider, input.model)
130
+ ? await resolveModel(modelRuntime, input.provider, input.model)
116
131
  : undefined;
117
132
  const loader = new DefaultResourceLoader({
118
133
  cwd: input.cwd,
@@ -127,8 +142,7 @@ async function createSdkSession(sdk, input, shared) {
127
142
  sessionManager: SessionManager.inMemory(input.cwd),
128
143
  resourceLoader: loader,
129
144
  tools: input.toolNames,
130
- authStorage,
131
- modelRegistry,
145
+ modelRuntime,
132
146
  ...(model ? { model } : {}),
133
147
  ...(input.thinking ? { thinkingLevel: input.thinking } : {}),
134
148
  });