@tea-agent/loop-agent 0.26.0 → 0.26.2

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 (197) hide show
  1. package/CHANGELOG.md +1056 -1020
  2. package/README.md +8 -3
  3. package/bin/loop-agent.js +21 -21
  4. package/dist/application/dag/generate-task-dag.js +33 -0
  5. package/dist/cli/command-definitions.js +25 -10
  6. package/dist/cli/help.js +4 -3
  7. package/dist/cli/program.js +43 -17
  8. package/dist/commands/cursor-prompt.js +6 -6
  9. package/dist/commands/import-prd.js +7 -2
  10. package/dist/commands/init.js +7 -5
  11. package/dist/commands/loop-benchmark.js +11 -11
  12. package/dist/commands/pi-reuse-benchmark.js +16 -16
  13. package/dist/commands/task-source-prepare.js +474 -0
  14. package/dist/executors/dag-pi-executor.js +40 -5
  15. package/dist/executors/shell-executor.js +111 -0
  16. package/dist/executors/shell-presets.js +12 -4
  17. package/dist/executors/shell-write-guard.js +161 -25
  18. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  19. package/dist/task/config-types.js +6 -0
  20. package/dist/task/contract/constants.js +1 -0
  21. package/dist/task/contract/project.js +8 -0
  22. package/dist/task/contract/schema.js +1 -0
  23. package/dist/task/frontend-preflight.js +131 -0
  24. package/dist/task/runtime.js +2 -4
  25. package/dist/task/source-prepare/build-draft.js +224 -0
  26. package/dist/task/source-prepare/completeness.js +195 -0
  27. package/dist/task/source-prepare/index.js +7 -0
  28. package/dist/task/source-prepare/parse-intent.js +373 -0
  29. package/dist/task/source-prepare/path-policy.js +197 -0
  30. package/dist/task/source-prepare/prepare.js +506 -0
  31. package/dist/task/source-prepare/reference-integrity.js +274 -0
  32. package/dist/task/source-prepare/types.js +7 -0
  33. package/dist/worker/observability/read-model.js +134 -0
  34. package/dist/worker/observe/static/copy.js +67 -67
  35. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  36. package/dist/worker/observe/static/dag-layout.js +83 -83
  37. package/dist/worker/observe/static/dom.js +220 -220
  38. package/dist/worker/observe/static/relations.js +133 -133
  39. package/dist/worker/observe/static/router.js +93 -93
  40. package/dist/worker/observe/static/run-processing.js +148 -148
  41. package/dist/worker/observe/static/state.js +61 -0
  42. package/dist/worker/observe/static/styles.css +8 -0
  43. package/dist/worker/observe/static/views/batch.js +227 -227
  44. package/dist/worker/observe/static/views/dag-graph.js +248 -172
  45. package/dist/worker/observe/static/views/dag-inspector.js +374 -157
  46. package/dist/worker/observe/static/views/dag.js +4 -11
  47. package/dist/worker/observe/static/views/failures.js +143 -143
  48. package/dist/worker/observe/static/views/feature.js +492 -492
  49. package/dist/worker/observe/static/views/run.js +453 -453
  50. package/dist/worker/observe/static/views/shell.js +7 -7
  51. package/dist/worker/observe/static/views/timeline.js +163 -163
  52. package/dist/workflows/dag/backend-test-pytest-collection.js +277 -0
  53. package/dist/workflows/dag/canvas-observer.js +275 -275
  54. package/dist/workflows/dag/convergence/controller.js +110 -21
  55. package/dist/workflows/dag/frontend-implementation-contract.js +218 -17
  56. package/dist/workflows/dag/frontend-review-context.js +7 -1
  57. package/dist/workflows/dag/frontend-verification-trace.js +14 -3
  58. package/dist/workflows/dag/frontend-worktree-diff.js +14 -3
  59. package/dist/workflows/dag/init-hybrid.js +96 -34
  60. package/dist/workflows/dag/output-protocol.js +180 -7
  61. package/dist/workflows/dag/runner.js +141 -52
  62. package/dist/workflows/dag/types.js +4 -0
  63. package/dist/workflows/dag/validate.js +3 -2
  64. package/docs/skills/README.md +7 -7
  65. package/docs/templates/adr.md +60 -60
  66. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  67. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  68. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  69. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  70. package/docs/templates/agent-dag-report.schema.json +473 -473
  71. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  72. package/docs/templates/backend-test-dag.json +100 -8
  73. package/docs/templates/backend-test-result.schema.json +99 -99
  74. package/docs/templates/feature-spec.md +53 -53
  75. package/docs/templates/frontend-design-contract.md +42 -42
  76. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  77. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  78. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  79. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  80. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  81. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  82. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  83. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  84. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  85. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  86. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  87. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  88. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  89. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  90. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  91. package/docs/templates/frontend-eval/metrics.md +138 -138
  92. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  93. package/docs/templates/frontend-task-constraints.md +35 -35
  94. package/docs/templates/frontend-task-requirement.md +70 -70
  95. package/docs/templates/init-evolution-review.md +35 -35
  96. package/docs/templates/init-managed-agents.md +10 -5
  97. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  98. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  99. package/docs/templates/knowledge-sync-dag.json +178 -178
  100. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  101. package/docs/templates/product-line/closeout.yaml +9 -9
  102. package/docs/templates/product-line/design.md +13 -13
  103. package/docs/templates/product-line/links.md +10 -10
  104. package/docs/templates/product-line/requirement.md +17 -17
  105. package/docs/templates/product-line/test-plan.md +7 -7
  106. package/docs/templates/production-readiness-checklist.md +57 -57
  107. package/docs/templates/project-start-checklist.md +9 -9
  108. package/docs/templates/qa-report.md +48 -48
  109. package/docs/templates/sprint-contract.md +29 -29
  110. package/docs/templates/worker-dogfood-evidence.md +80 -80
  111. package/docs/templates/worker-dogfood-setup.md +68 -68
  112. package/package.json +1 -1
  113. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  114. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  115. package/scripts/kb-graph-materialize.mjs +105 -105
  116. package/scripts/kb-graph-promote.mjs +164 -164
  117. package/scripts/kb-query.mjs +554 -554
  118. package/skills/ai-engineering-context/SKILL.md +48 -48
  119. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  120. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  121. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  122. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  123. package/skills/analyze-product-dependencies/references/example.md +76 -76
  124. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  125. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  126. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  127. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  128. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  129. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  130. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  131. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  132. package/skills/analyze-product-requirements/SKILL.md +90 -90
  133. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  134. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  135. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  136. package/skills/analyze-product-requirements/references/example.md +86 -86
  137. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  138. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  139. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  140. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  141. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  142. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  143. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  144. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  145. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  146. package/skills/browser-tools/browser-content.js +103 -103
  147. package/skills/browser-tools/browser-cookies.js +35 -35
  148. package/skills/browser-tools/browser-eval.js +53 -53
  149. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  150. package/skills/browser-tools/browser-nav.js +44 -44
  151. package/skills/browser-tools/browser-pick.js +162 -162
  152. package/skills/browser-tools/browser-screenshot.js +34 -34
  153. package/skills/browser-tools/browser-start.js +86 -86
  154. package/skills/browser-tools/package-lock.json +2556 -2556
  155. package/skills/browser-tools/package.json +19 -19
  156. package/skills/code-review-core/SKILL.md +20 -20
  157. package/skills/codebase-scout/SKILL.md +19 -19
  158. package/skills/grill-me/SKILL.md +10 -10
  159. package/skills/loop-agent/SKILL.md +5 -2
  160. package/skills/loop-agent/references/README.md +67 -67
  161. package/skills/loop-agent/references/command-reference.md +17 -15
  162. package/skills/loop-agent/references/docs-converge.md +126 -126
  163. package/skills/loop-agent/references/harness-policy.md +3 -4
  164. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  165. package/skills/loop-agent/references/learned/README.md +21 -21
  166. package/skills/loop-agent/references/long-running-loop.md +57 -57
  167. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  168. package/skills/loop-agent/references/pi-prompt.md +23 -23
  169. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  170. package/skills/loop-agent/references/post-implementation-and-patterns.md +1 -1
  171. package/skills/loop-agent/references/source-and-plan-practice.md +3 -2
  172. package/skills/loop-agent/references/task-workflow.md +7 -5
  173. package/skills/playwright-cli/SKILL.md +420 -420
  174. package/skills/playwright-cli/references/element-attributes.md +23 -23
  175. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  176. package/skills/playwright-cli/references/request-mocking.md +87 -87
  177. package/skills/playwright-cli/references/running-code.md +241 -241
  178. package/skills/playwright-cli/references/session-management.md +225 -225
  179. package/skills/playwright-cli/references/storage-state.md +275 -275
  180. package/skills/playwright-cli/references/test-generation.md +433 -433
  181. package/skills/playwright-cli/references/tracing.md +139 -139
  182. package/skills/playwright-cli/references/video-recording.md +143 -143
  183. package/skills/requesting-code-review/SKILL.md +101 -101
  184. package/skills/requesting-code-review/code-reviewer.md +168 -168
  185. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  186. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  187. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  188. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  189. package/skills/systematic-debugging/find-polluter.sh +63 -63
  190. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  191. package/skills/systematic-debugging/test-academic.md +14 -14
  192. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  193. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  194. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  195. package/skills/using-git-worktrees/SKILL.md +215 -215
  196. package/skills/verification-before-completion/SKILL.md +154 -154
  197. package/skills/webapp-testing/SKILL.md +19 -19
@@ -0,0 +1,506 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstat } from "node:fs/promises";
3
+ import { ContractMutationError, applyTaskContract, logicalRevisionForState, observeTaskContractState, projectConstraintsMarkdown, projectRequirementMarkdown, sha256OfCanonicalJson, validateDraftForTask, } from "../contract/index.js";
4
+ import { getTaskContractPaths } from "../contract/paths.js";
5
+ import { loadTaskConfig } from "../runtime.js";
6
+ import { buildPrepareDraft } from "./build-draft.js";
7
+ import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, } from "./completeness.js";
8
+ import { extractRequirementFactsFromMarkdown, fillMissingRequirementFacts, mergeRequirementFacts, } from "./parse-intent.js";
9
+ import { draftReferencesFromManifest, validateImportedPrdReferences, } from "./reference-integrity.js";
10
+ import { readSourceManifest } from "../source-references.js";
11
+ import { PREPARE_MAX_FILE_BYTES } from "./types.js";
12
+ async function fileNonEmpty(filePath) {
13
+ try {
14
+ const stats = await lstat(filePath);
15
+ return stats.isFile() && stats.size > 0;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function loadExistingTaskConfig(repoRoot, taskId) {
22
+ try {
23
+ return await loadTaskConfig(repoRoot, taskId);
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function mutationOutcome(code) {
30
+ switch (code) {
31
+ case "REVISION_CONFLICT":
32
+ case "OBSERVED_HASH_CONFLICT":
33
+ case "REQUEST_ID_REUSE_CONFLICT":
34
+ case "LEASE_HELD":
35
+ return "conflict";
36
+ case "NOT_FOUND":
37
+ return "not-found";
38
+ case "TRANSACTION_INCOMPLETE":
39
+ case "OPERATION_NEEDS_RECONCILE":
40
+ return "needs-reconcile";
41
+ case "EXTERNALLY_MODIFIED":
42
+ case "NOT_MANAGED":
43
+ case "BINDING_DRIFT":
44
+ return "blocked";
45
+ case "INVALID_INPUT":
46
+ case "PATH_OUTSIDE_REPO":
47
+ case "UNSUPPORTED_SCHEMA_VERSION":
48
+ return "invalid";
49
+ default:
50
+ return "rejected";
51
+ }
52
+ }
53
+ function buildNextSteps(input) {
54
+ const next = [];
55
+ if (!input.hasImportedPrd) {
56
+ next.push(`loop-agent import-prd ${input.taskId} --file <prd.md> --role requirement`);
57
+ }
58
+ if (input.gaps.some((g) => g.code === "EMPTY_ALLOWED_PATHS")) {
59
+ next.push("补 --allowed-path <glob>");
60
+ }
61
+ if (input.gaps.some((g) => g.code === "EMPTY_ACCEPTANCE")) {
62
+ next.push("补 --ac <id:text> 或完善 PRD 验收标准章节");
63
+ }
64
+ if (input.mode === "dry-run" && input.ok) {
65
+ next.push(`loop-agent task source prepare ${input.taskId} --use-imported-prd ... --apply --json`);
66
+ }
67
+ if (input.ok && input.mode === "apply") {
68
+ next.push(`loop-agent task contract show ${input.taskId} --json`);
69
+ next.push(`loop-agent dag run-task ${input.taskId} --profile auto --strict-models`);
70
+ next.push("审查 DAG writeSet 后再 run-dag");
71
+ }
72
+ if (input.gaps.some((g) => g.code === "DIRTY_SOURCE")) {
73
+ next.push("loop-agent task contract show|diff|adopt|recover");
74
+ next.push("或显式 --force-overwrite-source 后重跑 prepare");
75
+ }
76
+ if (input.gaps.some((g) => g.code === "TRANSACTION_INCOMPLETE")) {
77
+ next.push(`loop-agent task contract recover --task ${input.taskId}`);
78
+ }
79
+ return next;
80
+ }
81
+ function emptyObservedHashPlaceholder() {
82
+ return "0".repeat(64);
83
+ }
84
+ /**
85
+ * Core prepare orchestration: facts → thin Draft → gates → optional apply.
86
+ * No LLM. Production md writes only via applyTaskContract projection.
87
+ */
88
+ export async function prepareTaskSource(input) {
89
+ const { repoRoot, taskId } = input;
90
+ const contractPaths = getTaskContractPaths(repoRoot, taskId);
91
+ const state = await observeTaskContractState(repoRoot, taskId);
92
+ if (state.observationErrors.some((e) => e.code === "NOT_FOUND")) {
93
+ return {
94
+ ok: false,
95
+ outcome: "not-found",
96
+ mode: input.mode,
97
+ taskId,
98
+ gaps: [
99
+ {
100
+ code: "TASK_NOT_FOUND",
101
+ level: "blocking",
102
+ message: `task not found: ${taskId}`,
103
+ },
104
+ ],
105
+ risks: [],
106
+ next: [`loop-agent new-task ${taskId} "<title>"`],
107
+ error: {
108
+ code: "NOT_FOUND",
109
+ message: `task not found: ${taskId}`,
110
+ },
111
+ };
112
+ }
113
+ const existingTaskConfig = await loadExistingTaskConfig(repoRoot, taskId);
114
+ const sourceFilesPresent = {
115
+ requirement: await fileNonEmpty(contractPaths.requirementPath),
116
+ constraints: await fileNonEmpty(contractPaths.constraintsPath),
117
+ };
118
+ // Contract may record referenceManifestSha256 while observe only reports
119
+ // EXTERNALLY_MODIFIED after the manifest file disappears (hash already drifts).
120
+ // That is a non-fresh observation condition force must not bypass.
121
+ if (state.ref?.sourceHashes.referenceManifestSha256) {
122
+ const manifest = await readSourceManifest(contractPaths.sourceDir);
123
+ if (!manifest) {
124
+ state.observationErrors = [
125
+ ...state.observationErrors,
126
+ {
127
+ code: "MANIFEST_MISSING",
128
+ path: contractPaths.sourceManifestPath,
129
+ message: "contract records referenceManifestSha256 but source-manifest.json is missing",
130
+ },
131
+ ];
132
+ }
133
+ }
134
+ let baseDraft;
135
+ let facts;
136
+ let sourceIntegrity = null;
137
+ let hasImportedPrd = false;
138
+ let references;
139
+ if (input.intent.kind === "draft") {
140
+ baseDraft = input.intent.draft;
141
+ if (baseDraft.taskId !== taskId) {
142
+ return {
143
+ ok: false,
144
+ outcome: "invalid",
145
+ mode: input.mode,
146
+ taskId,
147
+ gaps: [
148
+ {
149
+ code: "DRAFT_TASK_ID_MISMATCH",
150
+ level: "blocking",
151
+ message: `draft.taskId "${baseDraft.taskId}" does not match ${taskId}`,
152
+ },
153
+ ],
154
+ risks: [],
155
+ next: [],
156
+ error: {
157
+ code: "INVALID_INPUT",
158
+ message: `draft.taskId "${baseDraft.taskId}" does not match ${taskId}`,
159
+ },
160
+ };
161
+ }
162
+ // Still verify any references in draft against manifest when present.
163
+ if (baseDraft.references?.length) {
164
+ sourceIntegrity = await validateImportedPrdReferences({
165
+ repoRoot,
166
+ taskId,
167
+ requireParseableRequirement: false,
168
+ });
169
+ hasImportedPrd = sourceIntegrity.documents.length > 0;
170
+ const allowedRefs = new Set(sourceIntegrity.documents.map((document) => document.materializedPath));
171
+ for (const ref of baseDraft.references) {
172
+ const normalizedRef = ref.ref.replace(/\\/g, "/");
173
+ if (!allowedRefs.has(normalizedRef)) {
174
+ sourceIntegrity.issues.push({
175
+ code: "REFERENCE_NOT_IN_MANIFEST",
176
+ level: "blocking",
177
+ message: `draft reference not found in source-manifest: ${ref.ref}`,
178
+ path: ref.ref,
179
+ });
180
+ sourceIntegrity.ok = false;
181
+ }
182
+ }
183
+ }
184
+ }
185
+ else {
186
+ const importedFactParts = [];
187
+ let supplementalFacts;
188
+ if (input.intent.useImportedPrd) {
189
+ sourceIntegrity = await validateImportedPrdReferences({
190
+ repoRoot,
191
+ taskId,
192
+ requireParseableRequirement: false,
193
+ });
194
+ hasImportedPrd = true;
195
+ if (sourceIntegrity.ok) {
196
+ references = draftReferencesFromManifest(sourceIntegrity.documents);
197
+ let acStart = 1;
198
+ for (const doc of sourceIntegrity.parseableDocuments) {
199
+ if (!doc.content)
200
+ continue;
201
+ const extracted = extractRequirementFactsFromMarkdown(doc.content, {
202
+ sourceLabel: doc.materializedPath,
203
+ acCounterStart: acStart,
204
+ });
205
+ acStart += extracted.acceptanceCriteria.filter((ac) => /^AC-\d+$/i.test(ac.id)).length;
206
+ // Better: count all auto ids; keep simple sequential across docs.
207
+ const maxAuto = extracted.acceptanceCriteria.reduce((max, ac) => {
208
+ const m = /^AC-(\d+)$/i.exec(ac.id);
209
+ if (!m)
210
+ return max;
211
+ return Math.max(max, Number(m[1]));
212
+ }, acStart - 1);
213
+ acStart = maxAuto + 1;
214
+ importedFactParts.push(extracted);
215
+ }
216
+ }
217
+ }
218
+ if (input.intent.text) {
219
+ if (Buffer.byteLength(input.intent.text, "utf-8") > PREPARE_MAX_FILE_BYTES) {
220
+ return {
221
+ ok: false,
222
+ outcome: "invalid",
223
+ mode: input.mode,
224
+ taskId,
225
+ gaps: [
226
+ {
227
+ code: "INPUT_TOO_LARGE",
228
+ level: "blocking",
229
+ message: `text input exceeds ${PREPARE_MAX_FILE_BYTES} bytes`,
230
+ },
231
+ ],
232
+ risks: [],
233
+ next: [],
234
+ error: {
235
+ code: "INPUT_TOO_LARGE",
236
+ message: `text input exceeds ${PREPARE_MAX_FILE_BYTES} bytes`,
237
+ },
238
+ };
239
+ }
240
+ supplementalFacts = extractRequirementFactsFromMarkdown(input.intent.text, {
241
+ sourceLabel: input.intent.textLabel ?? "supplemental-text",
242
+ });
243
+ }
244
+ const importedFacts = importedFactParts.length > 0
245
+ ? mergeRequirementFacts(importedFactParts)
246
+ : undefined;
247
+ const mergedSupplemental = supplementalFacts
248
+ ? mergeRequirementFacts([supplementalFacts])
249
+ : undefined;
250
+ if (importedFacts && mergedSupplemental) {
251
+ facts = fillMissingRequirementFacts(importedFacts, mergedSupplemental);
252
+ }
253
+ else {
254
+ facts = importedFacts ?? mergedSupplemental;
255
+ }
256
+ }
257
+ const built = buildPrepareDraft({
258
+ taskId,
259
+ existingTaskConfig,
260
+ baseDraft,
261
+ facts,
262
+ flags: input.flags,
263
+ references,
264
+ });
265
+ const { gaps, risks } = listPrepareGaps({
266
+ draft: built.draft,
267
+ state,
268
+ sourceIntegrity,
269
+ pathAssessment: built.pathAssessment,
270
+ hasImportedPrd,
271
+ acceptanceConflicts: built.acceptanceConflicts,
272
+ sourceFilesPresent,
273
+ forceOverwriteSource: input.forceOverwriteSource,
274
+ });
275
+ // If --use-imported-prd and no parseable requirement and no AC from flags/text → blocking
276
+ if (input.intent.kind === "facts" &&
277
+ input.intent.useImportedPrd &&
278
+ sourceIntegrity &&
279
+ !sourceIntegrity.parseableDocuments.some((d) => d.role === "requirement") &&
280
+ built.draft.requirement.acceptanceCriteria.length === 0 &&
281
+ !input.flags.acceptanceCriteria?.length &&
282
+ !input.intent.text) {
283
+ gaps.push({
284
+ code: "NO_PARSEABLE_REQUIREMENT",
285
+ level: "blocking",
286
+ message: "no parseable requirement document and no --ac / supplemental text; import a requirement-role PRD or pass flags",
287
+ });
288
+ }
289
+ const validation = await validateDraftForTask({
290
+ repoRoot,
291
+ taskId,
292
+ draft: built.draft,
293
+ });
294
+ if (!validation.ok) {
295
+ for (const message of validation.errors) {
296
+ gaps.push({
297
+ code: "DRAFT_VALIDATION",
298
+ level: "blocking",
299
+ message,
300
+ });
301
+ }
302
+ }
303
+ const requirementPreview = projectRequirementMarkdown(built.draft);
304
+ const constraintsPreview = projectConstraintsMarkdown(built.draft);
305
+ const projected = {
306
+ requirementPath: "source/需求.md",
307
+ constraintsPath: "source/执行约束.md",
308
+ requirementPreview,
309
+ constraintsPreview,
310
+ };
311
+ const blocking = hasBlockingGaps(gaps) ||
312
+ hasBlockingRisks(risks) ||
313
+ !validation.ok;
314
+ const warnings = gaps
315
+ .filter((g) => g.level === "warning")
316
+ .map((g) => ({ code: g.code, message: g.message }));
317
+ const next = buildNextSteps({
318
+ taskId,
319
+ hasImportedPrd,
320
+ mode: input.mode,
321
+ ok: !blocking,
322
+ gaps,
323
+ });
324
+ if (input.mode !== "apply") {
325
+ return {
326
+ ok: !blocking,
327
+ outcome: blocking ? "invalid" : "succeeded",
328
+ mode: "dry-run",
329
+ taskId,
330
+ taskKind: built.draft.taskKind,
331
+ title: built.draft.title,
332
+ draft: built.draft,
333
+ gaps,
334
+ risks,
335
+ projected,
336
+ next,
337
+ contractStatus: state.effectiveStatus,
338
+ warnings,
339
+ ...(blocking
340
+ ? {
341
+ error: {
342
+ code: "INVALID_INPUT",
343
+ message: "task source draft has blocking gaps",
344
+ details: {
345
+ gaps: gaps
346
+ .filter((g) => g.level === "blocking")
347
+ .map((g) => g.code),
348
+ },
349
+ },
350
+ }
351
+ : {}),
352
+ };
353
+ }
354
+ // apply path — prioritize state-machine outcomes over generic invalid gates
355
+ if (state.effectiveStatus === "transaction-incomplete" ||
356
+ gaps.some((g) => g.code === "TRANSACTION_INCOMPLETE")) {
357
+ return {
358
+ ok: false,
359
+ outcome: "needs-reconcile",
360
+ mode: "apply",
361
+ taskId,
362
+ taskKind: built.draft.taskKind,
363
+ title: built.draft.title,
364
+ draft: built.draft,
365
+ gaps,
366
+ risks,
367
+ projected,
368
+ next: [`loop-agent task contract recover --task ${taskId}`],
369
+ contractStatus: state.effectiveStatus,
370
+ warnings,
371
+ error: {
372
+ code: "TRANSACTION_INCOMPLETE",
373
+ message: "unfinished transaction; run task contract recover first",
374
+ },
375
+ };
376
+ }
377
+ if (blocking) {
378
+ const blockingCodes = gaps
379
+ .filter((g) => g.level === "blocking")
380
+ .map((g) => g.code);
381
+ const outcome = blockingCodes.includes("DIRTY_SOURCE")
382
+ ? "blocked"
383
+ : "invalid";
384
+ return {
385
+ ok: false,
386
+ outcome,
387
+ mode: "apply",
388
+ taskId,
389
+ taskKind: built.draft.taskKind,
390
+ title: built.draft.title,
391
+ draft: built.draft,
392
+ gaps,
393
+ risks,
394
+ projected,
395
+ next,
396
+ contractStatus: state.effectiveStatus,
397
+ warnings,
398
+ error: {
399
+ code: outcome === "blocked" ? "DIRTY_SOURCE" : "INVALID_INPUT",
400
+ message: "task source draft has blocking gaps",
401
+ details: {
402
+ gaps: blockingCodes,
403
+ },
404
+ },
405
+ };
406
+ }
407
+ const expectedRevision = logicalRevisionForState(state);
408
+ const expectedObservedHash = state.observedCanonicalHash ??
409
+ (expectedRevision === 0 ? emptyObservedHashPlaceholder() : null);
410
+ if (!expectedObservedHash) {
411
+ return {
412
+ ok: false,
413
+ outcome: "conflict",
414
+ mode: "apply",
415
+ taskId,
416
+ draft: built.draft,
417
+ gaps,
418
+ risks,
419
+ projected,
420
+ next: [`loop-agent task contract doctor --task ${taskId}`],
421
+ error: {
422
+ code: "OBSERVED_HASH_CONFLICT",
423
+ message: "unable to determine expectedObservedHash",
424
+ },
425
+ };
426
+ }
427
+ // Dirty without force already blocked above. Fresh legacy is allowed.
428
+ void isFreshLegacyUnversioned;
429
+ const requestId = input.requestId?.trim() || `prepare-${taskId}-${randomUUID()}`;
430
+ const requestPayloadSha256 = sha256OfCanonicalJson(built.draft);
431
+ try {
432
+ const applyResult = await applyTaskContract({
433
+ repoRoot,
434
+ taskId,
435
+ draft: built.draft,
436
+ expectedRevision,
437
+ expectedObservedHash,
438
+ requestId,
439
+ requestPayloadSha256,
440
+ payloadSha256Trusted: true,
441
+ });
442
+ return {
443
+ ok: true,
444
+ outcome: "succeeded",
445
+ mode: "apply",
446
+ taskId,
447
+ taskKind: built.draft.taskKind,
448
+ title: built.draft.title,
449
+ draft: built.draft,
450
+ gaps,
451
+ risks,
452
+ projected,
453
+ apply: applyResult,
454
+ next: buildNextSteps({
455
+ taskId,
456
+ hasImportedPrd,
457
+ mode: "apply",
458
+ ok: true,
459
+ gaps,
460
+ }),
461
+ contractStatus: "managed",
462
+ warnings,
463
+ };
464
+ }
465
+ catch (error) {
466
+ if (error instanceof ContractMutationError) {
467
+ const outcome = mutationOutcome(error.code);
468
+ return {
469
+ ok: false,
470
+ outcome,
471
+ mode: "apply",
472
+ taskId,
473
+ taskKind: built.draft.taskKind,
474
+ title: built.draft.title,
475
+ draft: built.draft,
476
+ gaps,
477
+ risks,
478
+ projected,
479
+ next: [
480
+ `loop-agent task contract show ${taskId} --json`,
481
+ `loop-agent task contract doctor --task ${taskId}`,
482
+ ],
483
+ error: {
484
+ code: error.code,
485
+ message: error.message,
486
+ details: error.details,
487
+ },
488
+ };
489
+ }
490
+ return {
491
+ ok: false,
492
+ outcome: "rejected",
493
+ mode: "apply",
494
+ taskId,
495
+ draft: built.draft,
496
+ gaps,
497
+ risks,
498
+ projected,
499
+ next: [],
500
+ error: {
501
+ code: "INTERNAL_ERROR",
502
+ message: error instanceof Error ? error.message : String(error),
503
+ },
504
+ };
505
+ }
506
+ }