@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,474 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { stdin as stdinStream } from "node:process";
4
+ import { parseAcceptanceFlag, parseVerifyFlag, parseVerifyTimeoutFlag, prepareTaskSource, PREPARE_MAX_FILE_BYTES, } from "../task/source-prepare/index.js";
5
+ import { parseTaskContractDraft } from "../task/contract/schema.js";
6
+ import { buildOperatorResult, operatorFailed, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
7
+ const COMMAND = "task source prepare";
8
+ const USAGE = `usage:
9
+ task source prepare <task-id>
10
+ [--from-text <string>]
11
+ [--from-file <path>]
12
+ [--from-stdin]
13
+ [--from-draft <path-to-TaskContractDraftV1.json>]
14
+ [--use-imported-prd]
15
+ [--title <string>]
16
+ [--objective <text>]
17
+ [--scope <text>]...
18
+ [--task-kind <TaskKind>]
19
+ [--feature-id <string>]
20
+ [--allowed-path <glob>]...
21
+ [--allowed-root <glob>]...
22
+ [--forbidden-path <glob>]...
23
+ [--no-default-forbidden-paths]
24
+ [--invariant <text>]...
25
+ [--ac <id:text> | --acceptance <id:text>]...
26
+ [--non-goal <text>]...
27
+ [--assumption <text>]...
28
+ [--open-question <text>]...
29
+ [--verify <label:command>]...
30
+ [--verify-timeout <label:timeoutMs>]...
31
+ [--request-id <id>]
32
+ [--dry-run | --apply]
33
+ [--force-overwrite-source]
34
+ [--json]`;
35
+ function pushList(target, value) {
36
+ const list = target ?? [];
37
+ list.push(value);
38
+ return list;
39
+ }
40
+ export function parseTaskSourcePrepareArgs(args) {
41
+ const options = {};
42
+ const positional = [];
43
+ for (let i = 0; i < args.length; i += 1) {
44
+ const token = args[i];
45
+ const next = () => {
46
+ const value = args[i + 1];
47
+ if (value === undefined || value.startsWith("-")) {
48
+ throw new Error(`missing value for ${token}\n${USAGE}`);
49
+ }
50
+ i += 1;
51
+ return value;
52
+ };
53
+ if (token === "--json") {
54
+ options.json = true;
55
+ continue;
56
+ }
57
+ if (token === "--dry-run") {
58
+ options.dryRun = true;
59
+ continue;
60
+ }
61
+ if (token === "--apply") {
62
+ options.apply = true;
63
+ continue;
64
+ }
65
+ if (token === "--force-overwrite-source") {
66
+ options.forceOverwriteSource = true;
67
+ continue;
68
+ }
69
+ if (token === "--use-imported-prd") {
70
+ options.useImportedPrd = true;
71
+ continue;
72
+ }
73
+ if (token === "--from-stdin") {
74
+ options.fromStdin = true;
75
+ continue;
76
+ }
77
+ if (token === "--no-default-forbidden-paths") {
78
+ options.noDefaultForbiddenPaths = true;
79
+ continue;
80
+ }
81
+ if (token === "--from-text") {
82
+ options.fromText = next();
83
+ continue;
84
+ }
85
+ if (token === "--from-file") {
86
+ options.fromFile = next();
87
+ continue;
88
+ }
89
+ if (token === "--from-draft") {
90
+ options.fromDraft = next();
91
+ continue;
92
+ }
93
+ if (token === "--title") {
94
+ options.title = next();
95
+ continue;
96
+ }
97
+ if (token === "--objective") {
98
+ options.objective = next();
99
+ continue;
100
+ }
101
+ if (token === "--scope") {
102
+ options.scope = pushList(options.scope, next());
103
+ continue;
104
+ }
105
+ if (token === "--task-kind") {
106
+ options.taskKind = next();
107
+ continue;
108
+ }
109
+ if (token === "--feature-id") {
110
+ options.featureId = next();
111
+ continue;
112
+ }
113
+ if (token === "--allowed-path") {
114
+ options.allowedPaths = pushList(options.allowedPaths, next());
115
+ continue;
116
+ }
117
+ if (token === "--allowed-root") {
118
+ options.allowedRoots = pushList(options.allowedRoots, next());
119
+ continue;
120
+ }
121
+ if (token === "--forbidden-path") {
122
+ options.forbiddenPaths = pushList(options.forbiddenPaths, next());
123
+ continue;
124
+ }
125
+ if (token === "--invariant") {
126
+ options.invariants = pushList(options.invariants, next());
127
+ continue;
128
+ }
129
+ if (token === "--ac" || token === "--acceptance") {
130
+ const parsed = parseAcceptanceFlag(next());
131
+ options.acceptanceCriteria = [
132
+ ...(options.acceptanceCriteria ?? []),
133
+ parsed,
134
+ ];
135
+ continue;
136
+ }
137
+ if (token === "--non-goal") {
138
+ options.nonGoals = pushList(options.nonGoals, next());
139
+ continue;
140
+ }
141
+ if (token === "--assumption") {
142
+ options.assumptions = pushList(options.assumptions, next());
143
+ continue;
144
+ }
145
+ if (token === "--open-question") {
146
+ options.openQuestions = pushList(options.openQuestions, next());
147
+ continue;
148
+ }
149
+ if (token === "--verify") {
150
+ options.verify = pushList(options.verify, next());
151
+ continue;
152
+ }
153
+ if (token === "--verify-timeout") {
154
+ options.verifyTimeout = pushList(options.verifyTimeout, next());
155
+ continue;
156
+ }
157
+ if (token === "--request-id") {
158
+ options.requestId = next();
159
+ continue;
160
+ }
161
+ if (token.startsWith("-")) {
162
+ throw new Error(`unknown flag ${token}\n${USAGE}`);
163
+ }
164
+ positional.push(token);
165
+ }
166
+ const taskId = positional[0];
167
+ if (!taskId) {
168
+ throw new Error(USAGE);
169
+ }
170
+ if (positional.length > 1) {
171
+ throw new Error(`unexpected arguments: ${positional.slice(1).join(" ")}\n${USAGE}`);
172
+ }
173
+ return { taskId, options };
174
+ }
175
+ function validateOptionCombinations(options) {
176
+ if (options.dryRun && options.apply) {
177
+ throw new Error(`--dry-run and --apply are mutually exclusive\n${USAGE}`);
178
+ }
179
+ const textSources = [
180
+ options.fromText !== undefined,
181
+ options.fromFile !== undefined,
182
+ Boolean(options.fromStdin),
183
+ ].filter(Boolean).length;
184
+ if (textSources > 1) {
185
+ throw new Error(`--from-text / --from-file / --from-stdin are mutually exclusive\n${USAGE}`);
186
+ }
187
+ if (options.fromDraft) {
188
+ if (options.useImportedPrd ||
189
+ options.fromText !== undefined ||
190
+ options.fromFile !== undefined ||
191
+ options.fromStdin) {
192
+ throw new Error(`--from-draft is mutually exclusive with --use-imported-prd / --from-text / --from-file / --from-stdin\n${USAGE}`);
193
+ }
194
+ }
195
+ const hasIntentSource = Boolean(options.fromDraft) ||
196
+ Boolean(options.useImportedPrd) ||
197
+ options.fromText !== undefined ||
198
+ options.fromFile !== undefined ||
199
+ Boolean(options.fromStdin) ||
200
+ Boolean(options.title) ||
201
+ Boolean(options.objective) ||
202
+ Boolean(options.acceptanceCriteria?.length) ||
203
+ Boolean(options.allowedPaths?.length);
204
+ if (!hasIntentSource) {
205
+ throw new Error(`need at least one intent source: --use-imported-prd, --from-text/--from-file/--from-stdin, --from-draft, or sufficient flags (title/objective + --ac + --allowed-path)\n${USAGE}`);
206
+ }
207
+ }
208
+ async function readStdinText() {
209
+ if (stdinStream.isTTY) {
210
+ throw new Error("--from-stdin refused on TTY; pipe input or use --from-file/--from-text");
211
+ }
212
+ const chunks = [];
213
+ let total = 0;
214
+ for await (const chunk of stdinStream) {
215
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
216
+ total += buf.byteLength;
217
+ if (total > PREPARE_MAX_FILE_BYTES) {
218
+ throw new Error(`stdin input exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
219
+ }
220
+ chunks.push(buf);
221
+ }
222
+ return Buffer.concat(chunks).toString("utf-8");
223
+ }
224
+ async function loadTextInput(repoRoot, options) {
225
+ if (options.fromText !== undefined) {
226
+ if (Buffer.byteLength(options.fromText, "utf-8") > PREPARE_MAX_FILE_BYTES) {
227
+ throw new Error(`--from-text exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
228
+ }
229
+ return { text: options.fromText, label: "from-text" };
230
+ }
231
+ if (options.fromFile) {
232
+ const absolute = path.isAbsolute(options.fromFile)
233
+ ? options.fromFile
234
+ : path.resolve(repoRoot, options.fromFile);
235
+ const raw = await readFile(absolute);
236
+ if (raw.byteLength > PREPARE_MAX_FILE_BYTES) {
237
+ throw new Error(`--from-file exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
238
+ }
239
+ return { text: raw.toString("utf-8"), label: options.fromFile };
240
+ }
241
+ if (options.fromStdin) {
242
+ const text = await readStdinText();
243
+ return { text, label: "stdin" };
244
+ }
245
+ return {};
246
+ }
247
+ async function loadDraftInput(repoRoot, draftPath) {
248
+ const absolute = path.isAbsolute(draftPath)
249
+ ? draftPath
250
+ : path.resolve(repoRoot, draftPath);
251
+ const raw = await readFile(absolute);
252
+ if (raw.byteLength > PREPARE_MAX_FILE_BYTES) {
253
+ throw new Error(`--from-draft exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
254
+ }
255
+ let json;
256
+ try {
257
+ json = JSON.parse(raw.toString("utf-8"));
258
+ }
259
+ catch (error) {
260
+ throw new Error(`--from-draft is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
261
+ }
262
+ return parseTaskContractDraft(json);
263
+ }
264
+ function buildFlags(options) {
265
+ const flags = {
266
+ title: options.title,
267
+ objective: options.objective,
268
+ scope: options.scope,
269
+ taskKind: options.taskKind,
270
+ featureId: options.featureId,
271
+ allowedPaths: options.allowedPaths,
272
+ allowedRoots: options.allowedRoots,
273
+ forbiddenPaths: options.forbiddenPaths,
274
+ noDefaultForbiddenPaths: options.noDefaultForbiddenPaths,
275
+ invariants: options.invariants,
276
+ acceptanceCriteria: options.acceptanceCriteria,
277
+ nonGoals: options.nonGoals,
278
+ assumptions: options.assumptions,
279
+ openQuestions: options.openQuestions,
280
+ };
281
+ if (options.verify || options.verifyTimeout) {
282
+ const timeouts = new Map();
283
+ for (const raw of options.verifyTimeout ?? []) {
284
+ const parsed = parseVerifyTimeoutFlag(raw);
285
+ timeouts.set(parsed.label, parsed.timeoutMs);
286
+ }
287
+ const commands = (options.verify ?? []).map((raw) => {
288
+ const parsed = parseVerifyFlag(raw);
289
+ const timeoutMs = timeouts.get(parsed.label);
290
+ return {
291
+ label: parsed.label,
292
+ command: parsed.command,
293
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
294
+ };
295
+ });
296
+ // If only timeouts provided without verify, treat as empty explicit list? No — only set when --verify appears.
297
+ if (options.verify) {
298
+ flags.verifyCommands = commands;
299
+ }
300
+ }
301
+ return flags;
302
+ }
303
+ function toOperatorEnvelope(result) {
304
+ const payload = {
305
+ taskId: result.taskId,
306
+ mode: result.mode,
307
+ taskKind: result.taskKind,
308
+ title: result.title,
309
+ draft: result.draft,
310
+ gaps: result.gaps,
311
+ risks: result.risks,
312
+ projected: result.projected,
313
+ apply: result.apply ?? null,
314
+ next: result.next,
315
+ contractStatus: result.contractStatus,
316
+ };
317
+ if (result.ok) {
318
+ return buildOperatorResult({
319
+ command: COMMAND,
320
+ ok: true,
321
+ outcome: result.outcome,
322
+ result: payload,
323
+ warnings: result.warnings ?? [],
324
+ });
325
+ }
326
+ return buildOperatorResult({
327
+ command: COMMAND,
328
+ ok: false,
329
+ outcome: result.outcome,
330
+ result: payload,
331
+ warnings: result.warnings ?? [],
332
+ error: result.error ?? {
333
+ code: "INVALID_INPUT",
334
+ message: "task source prepare failed",
335
+ },
336
+ });
337
+ }
338
+ function printHuman(result) {
339
+ const lines = [
340
+ `task source prepare: ${result.taskId}`,
341
+ ` mode: ${result.mode}`,
342
+ ` outcome: ${result.outcome}`,
343
+ ` ok: ${result.ok}`,
344
+ ];
345
+ if (result.title)
346
+ lines.push(` title: ${result.title}`);
347
+ if (result.taskKind)
348
+ lines.push(` taskKind: ${result.taskKind}`);
349
+ if (result.contractStatus) {
350
+ lines.push(` contractStatus: ${result.contractStatus}`);
351
+ }
352
+ if (result.gaps.length > 0) {
353
+ lines.push(" gaps:");
354
+ for (const gap of result.gaps) {
355
+ lines.push(` - [${gap.level}] ${gap.code}: ${gap.message}`);
356
+ }
357
+ }
358
+ if (result.risks.length > 0) {
359
+ lines.push(" risks:");
360
+ for (const risk of result.risks) {
361
+ lines.push(` - [${risk.level}] ${risk.code}: ${risk.message}`);
362
+ }
363
+ }
364
+ if (result.projected) {
365
+ lines.push(` projected: ${result.projected.requirementPath}, ${result.projected.constraintsPath}`);
366
+ if (result.draft) {
367
+ lines.push(` allowedPaths: ${result.draft.constraints.allowedPaths.join(", ") || "(none)"}`);
368
+ lines.push(` forbiddenPaths: ${result.draft.constraints.forbiddenPaths.join(", ") || "(none)"}`);
369
+ lines.push(` verifyCommands: ${result.draft.verification.commands
370
+ .map((c) => c.label)
371
+ .join(", ") || "(none)"}`);
372
+ }
373
+ }
374
+ if (result.apply) {
375
+ lines.push(` apply: revision=${result.apply.ref.revision} txId=${result.apply.txId}`);
376
+ }
377
+ if (result.error) {
378
+ lines.push(` error: ${result.error.code}: ${result.error.message}`);
379
+ }
380
+ if (result.next.length > 0) {
381
+ lines.push(" next:");
382
+ for (const step of result.next) {
383
+ lines.push(` - ${step}`);
384
+ }
385
+ }
386
+ console.log(lines.join("\n"));
387
+ }
388
+ export async function runTaskSourcePrepare(repoRoot, args) {
389
+ let taskId;
390
+ let options = {};
391
+ try {
392
+ ({ taskId, options } = parseTaskSourcePrepareArgs(args));
393
+ validateOptionCombinations(options);
394
+ }
395
+ catch (error) {
396
+ const message = error instanceof Error ? error.message : String(error);
397
+ const envelope = operatorFailed({
398
+ command: COMMAND,
399
+ outcome: "invalid",
400
+ code: "INVALID_INPUT",
401
+ message,
402
+ });
403
+ if (options.json || args.includes("--json")) {
404
+ writeOperatorJson(envelope);
405
+ }
406
+ else {
407
+ console.error(message);
408
+ }
409
+ process.exitCode = processExitCodeForOutcome(envelope.outcome);
410
+ return;
411
+ }
412
+ try {
413
+ const flags = buildFlags(options);
414
+ const mode = options.apply ? "apply" : "dry-run";
415
+ let prepareInput;
416
+ if (options.fromDraft) {
417
+ const draft = await loadDraftInput(repoRoot, options.fromDraft);
418
+ prepareInput = {
419
+ repoRoot,
420
+ taskId,
421
+ mode,
422
+ intent: { kind: "draft", draft, sourcePath: options.fromDraft },
423
+ flags,
424
+ requestId: options.requestId,
425
+ forceOverwriteSource: options.forceOverwriteSource,
426
+ };
427
+ }
428
+ else {
429
+ const textInput = await loadTextInput(repoRoot, options);
430
+ prepareInput = {
431
+ repoRoot,
432
+ taskId,
433
+ mode,
434
+ intent: {
435
+ kind: "facts",
436
+ useImportedPrd: Boolean(options.useImportedPrd),
437
+ text: textInput.text,
438
+ textLabel: textInput.label,
439
+ },
440
+ flags,
441
+ requestId: options.requestId,
442
+ forceOverwriteSource: options.forceOverwriteSource,
443
+ };
444
+ }
445
+ const result = await prepareTaskSource(prepareInput);
446
+ const envelope = toOperatorEnvelope(result);
447
+ if (options.json) {
448
+ writeOperatorJson(envelope);
449
+ }
450
+ else {
451
+ printHuman(result);
452
+ }
453
+ process.exitCode = processExitCodeForOutcome(result.outcome);
454
+ }
455
+ catch (error) {
456
+ const message = error instanceof Error ? error.message : String(error);
457
+ const code = message.includes("INPUT_TOO_LARGE")
458
+ ? "INPUT_TOO_LARGE"
459
+ : "INVALID_INPUT";
460
+ const envelope = operatorFailed({
461
+ command: COMMAND,
462
+ outcome: "invalid",
463
+ code,
464
+ message,
465
+ });
466
+ if (options.json) {
467
+ writeOperatorJson(envelope);
468
+ }
469
+ else {
470
+ console.error(message);
471
+ }
472
+ process.exitCode = processExitCodeForOutcome(envelope.outcome);
473
+ }
474
+ }
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { writeDagNodeJsonArtifact, writeTextArtifactFile, } from "../infrastructure/harness/artifact-store.js";
4
4
  import { executePiStep, } from "./pi-executor.js";
5
5
  import { redactPromptForLog, truncateOutput, } from "../shared/output-truncation.js";
6
- import { pathsChangedDuringRun, readGitStatusPorcelain, snapshotGitStatusPathFingerprints, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
6
+ import { GitStatusUnavailableError, pathsChangedDuringRun, readGitStatusPorcelain, snapshotGitStatusPathFingerprints, snapshotGitStatusPorcelain, validateShellWriteGuard, } from "./shell-write-guard.js";
7
7
  import { redactSecrets, truncateUtf8Preview } from "../shared/preview.js";
8
8
  export const DAG_PI_READONLY_TOOLS = ["read", "grep", "find", "ls"];
9
9
  export const DAG_PI_WRITE_TOOLS = [
@@ -257,7 +257,10 @@ export async function writePiExecutorArtifacts(artifactsDir, input) {
257
257
  await writeTextArtifactFile(summaryPath, buildPiResultSummaryMarkdown(input));
258
258
  return { promptPath, summaryPath };
259
259
  }
260
- export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
260
+ const DEFAULT_DAG_PI_WRITE_GUARD_DEPENDENCIES = {
261
+ readGitStatusPorcelain,
262
+ };
263
+ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, writeGuardDependencies = DEFAULT_DAG_PI_WRITE_GUARD_DEPENDENCIES) {
261
264
  const started = Date.now();
262
265
  const persona = resolveDagPiPersona(input.task);
263
266
  const step = resolveDagPiStepName(input.task);
@@ -276,10 +279,19 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
276
279
  let beforePathFingerprints;
277
280
  if (isWriteTask) {
278
281
  try {
279
- beforeStatus = await readGitStatusPorcelain(input.cwd);
280
- beforePathFingerprints = await snapshotGitStatusPathFingerprints(input.cwd, snapshotGitStatusPorcelain(beforeStatus));
282
+ beforeStatus = await writeGuardDependencies.readGitStatusPorcelain(input.cwd, { phase: "pi-writer-before" });
283
+ const beforeSnapshot = snapshotGitStatusPorcelain(beforeStatus);
284
+ beforePathFingerprints = await snapshotGitStatusPathFingerprints(input.cwd, beforeSnapshot);
285
+ await persistWriterGitBaseline({
286
+ runDir: meta.runDir,
287
+ nodeId: input.task.id,
288
+ beforeStatus,
289
+ beforeSnapshot,
290
+ beforePathFingerprints,
291
+ });
281
292
  }
282
293
  catch (error) {
294
+ await persistGitWriteGuardDiagnostics(meta.runDir, input.task.id, error);
283
295
  return {
284
296
  ok: false,
285
297
  stdout: "",
@@ -349,7 +361,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
349
361
  let changeManifestChangedFiles;
350
362
  if (beforeStatus !== undefined) {
351
363
  try {
352
- const afterStatus = await readGitStatusPorcelain(input.cwd);
364
+ const afterStatus = await writeGuardDependencies.readGitStatusPorcelain(input.cwd, { phase: "pi-writer-after" });
353
365
  const afterSnapshot = snapshotGitStatusPorcelain(afterStatus);
354
366
  const afterPathFingerprints = await snapshotGitStatusPathFingerprints(input.cwd, afterSnapshot);
355
367
  const changedFiles = pathsChangedDuringRun(snapshotGitStatusPorcelain(beforeStatus), afterSnapshot, beforePathFingerprints, afterPathFingerprints);
@@ -364,6 +376,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep) {
364
376
  writeGuardViolations = guard.violations;
365
377
  }
366
378
  catch (error) {
379
+ await persistGitWriteGuardDiagnostics(meta.runDir, input.task.id, error);
367
380
  writeGuardOk = false;
368
381
  writeGuardViolations = [
369
382
  `git status unavailable: ${error instanceof Error ? error.message : String(error)}`,
@@ -604,6 +617,28 @@ function stripMarkdownLineDecorations(line) {
604
617
  function escapeRegExp(value) {
605
618
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
606
619
  }
620
+ async function persistWriterGitBaseline(input) {
621
+ const fingerprintEntries = [...input.beforePathFingerprints.entries()].sort(([left], [right]) => left.localeCompare(right));
622
+ const baseline = {
623
+ schemaVersion: 1,
624
+ nodeId: input.nodeId,
625
+ phase: "pi-writer-before",
626
+ capturedAt: new Date().toISOString(),
627
+ statusSha256: createHash("sha256")
628
+ .update(input.beforeStatus)
629
+ .digest("hex"),
630
+ dirtyPaths: [...input.beforeSnapshot.keys()].sort(),
631
+ pathFingerprintsSha256: createHash("sha256")
632
+ .update(JSON.stringify(fingerprintEntries))
633
+ .digest("hex"),
634
+ };
635
+ await writeDagNodeJsonArtifact(input.runDir, input.nodeId, "write-guard-baseline.json", baseline);
636
+ }
637
+ async function persistGitWriteGuardDiagnostics(runDir, nodeId, error) {
638
+ if (!(error instanceof GitStatusUnavailableError))
639
+ return;
640
+ await writeDagNodeJsonArtifact(runDir, nodeId, "git-write-guard-diagnostics.json", error.diagnostics);
641
+ }
607
642
  /**
608
643
  * Validate the writer's observed diff against its declared write boundary.
609
644
  * Inlined mirror of runPostRunWriteGuard that reuses the already-computed diff