@tea-agent/loop-agent 0.26.0 → 0.26.1

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 (170) hide show
  1. package/CHANGELOG.md +1032 -1020
  2. package/README.md +8 -3
  3. package/bin/loop-agent.js +21 -21
  4. package/dist/cli/command-definitions.js +25 -10
  5. package/dist/cli/help.js +4 -3
  6. package/dist/cli/program.js +43 -17
  7. package/dist/commands/cursor-prompt.js +6 -6
  8. package/dist/commands/import-prd.js +7 -2
  9. package/dist/commands/init.js +7 -5
  10. package/dist/commands/loop-benchmark.js +11 -11
  11. package/dist/commands/pi-reuse-benchmark.js +16 -16
  12. package/dist/commands/task-source-prepare.js +468 -0
  13. package/dist/executors/dag-pi-executor.js +40 -5
  14. package/dist/executors/shell-write-guard.js +161 -25
  15. package/dist/sidecars/cursor-prompt/executor.js +1 -1
  16. package/dist/task/source-prepare/build-draft.js +215 -0
  17. package/dist/task/source-prepare/completeness.js +195 -0
  18. package/dist/task/source-prepare/index.js +7 -0
  19. package/dist/task/source-prepare/parse-intent.js +373 -0
  20. package/dist/task/source-prepare/path-policy.js +197 -0
  21. package/dist/task/source-prepare/prepare.js +506 -0
  22. package/dist/task/source-prepare/reference-integrity.js +274 -0
  23. package/dist/task/source-prepare/types.js +7 -0
  24. package/dist/worker/observe/static/copy.js +67 -67
  25. package/dist/worker/observe/static/dag-layout.d.ts +31 -31
  26. package/dist/worker/observe/static/dag-layout.js +83 -83
  27. package/dist/worker/observe/static/dom.js +220 -220
  28. package/dist/worker/observe/static/relations.js +133 -133
  29. package/dist/worker/observe/static/router.js +93 -93
  30. package/dist/worker/observe/static/run-processing.js +148 -148
  31. package/dist/worker/observe/static/views/batch.js +227 -227
  32. package/dist/worker/observe/static/views/dag-graph.js +172 -172
  33. package/dist/worker/observe/static/views/failures.js +143 -143
  34. package/dist/worker/observe/static/views/feature.js +492 -492
  35. package/dist/worker/observe/static/views/run.js +453 -453
  36. package/dist/worker/observe/static/views/shell.js +7 -7
  37. package/dist/worker/observe/static/views/timeline.js +163 -163
  38. package/dist/workflows/dag/canvas-observer.js +275 -275
  39. package/docs/skills/README.md +7 -7
  40. package/docs/templates/adr.md +60 -60
  41. package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
  42. package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
  43. package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
  44. package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
  45. package/docs/templates/agent-dag-report.schema.json +473 -473
  46. package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
  47. package/docs/templates/backend-test-result.schema.json +99 -99
  48. package/docs/templates/feature-spec.md +53 -53
  49. package/docs/templates/frontend-design-contract.md +42 -42
  50. package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
  51. package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
  52. package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
  53. package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
  54. package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
  55. package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
  56. package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
  57. package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
  58. package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
  59. package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
  60. package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
  61. package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
  62. package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
  63. package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
  64. package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
  65. package/docs/templates/frontend-eval/metrics.md +138 -138
  66. package/docs/templates/frontend-eval/smoke-targets.md +53 -53
  67. package/docs/templates/frontend-task-constraints.md +35 -35
  68. package/docs/templates/frontend-task-requirement.md +70 -70
  69. package/docs/templates/init-evolution-review.md +35 -35
  70. package/docs/templates/init-managed-agents.md +10 -5
  71. package/docs/templates/interactive-ui-round2-experiment.md +66 -66
  72. package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
  73. package/docs/templates/knowledge-sync-dag.json +178 -178
  74. package/docs/templates/knowledge-sync-draft.schema.json +71 -71
  75. package/docs/templates/product-line/closeout.yaml +9 -9
  76. package/docs/templates/product-line/design.md +13 -13
  77. package/docs/templates/product-line/links.md +10 -10
  78. package/docs/templates/product-line/requirement.md +17 -17
  79. package/docs/templates/product-line/test-plan.md +7 -7
  80. package/docs/templates/production-readiness-checklist.md +57 -57
  81. package/docs/templates/project-start-checklist.md +9 -9
  82. package/docs/templates/qa-report.md +48 -48
  83. package/docs/templates/sprint-contract.md +29 -29
  84. package/docs/templates/worker-dogfood-evidence.md +80 -80
  85. package/docs/templates/worker-dogfood-setup.md +68 -68
  86. package/package.json +1 -1
  87. package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
  88. package/scripts/kb-graph-incremental-prepare.mjs +386 -386
  89. package/scripts/kb-graph-materialize.mjs +105 -105
  90. package/scripts/kb-graph-promote.mjs +164 -164
  91. package/scripts/kb-query.mjs +554 -554
  92. package/skills/ai-engineering-context/SKILL.md +48 -48
  93. package/skills/analyze-product-dependencies/SKILL.md +67 -67
  94. package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
  95. package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
  96. package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
  97. package/skills/analyze-product-dependencies/references/example.md +76 -76
  98. package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
  99. package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
  100. package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
  101. package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
  102. package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
  103. package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
  104. package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
  105. package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
  106. package/skills/analyze-product-requirements/SKILL.md +90 -90
  107. package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
  108. package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
  109. package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
  110. package/skills/analyze-product-requirements/references/example.md +86 -86
  111. package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
  112. package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
  113. package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
  114. package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
  115. package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
  116. package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
  117. package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
  118. package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
  119. package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
  120. package/skills/browser-tools/browser-content.js +103 -103
  121. package/skills/browser-tools/browser-cookies.js +35 -35
  122. package/skills/browser-tools/browser-eval.js +53 -53
  123. package/skills/browser-tools/browser-hn-scraper.js +108 -108
  124. package/skills/browser-tools/browser-nav.js +44 -44
  125. package/skills/browser-tools/browser-pick.js +162 -162
  126. package/skills/browser-tools/browser-screenshot.js +34 -34
  127. package/skills/browser-tools/browser-start.js +86 -86
  128. package/skills/browser-tools/package-lock.json +2556 -2556
  129. package/skills/browser-tools/package.json +19 -19
  130. package/skills/code-review-core/SKILL.md +20 -20
  131. package/skills/codebase-scout/SKILL.md +19 -19
  132. package/skills/grill-me/SKILL.md +10 -10
  133. package/skills/loop-agent/SKILL.md +5 -2
  134. package/skills/loop-agent/references/README.md +67 -67
  135. package/skills/loop-agent/references/command-reference.md +17 -15
  136. package/skills/loop-agent/references/docs-converge.md +126 -126
  137. package/skills/loop-agent/references/harness-policy.md +3 -4
  138. package/skills/loop-agent/references/learned/README.md +21 -21
  139. package/skills/loop-agent/references/long-running-loop.md +57 -57
  140. package/skills/loop-agent/references/one-shot-runs.md +85 -85
  141. package/skills/loop-agent/references/pi-prompt.md +23 -23
  142. package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
  143. package/skills/loop-agent/references/post-implementation-and-patterns.md +1 -1
  144. package/skills/loop-agent/references/source-and-plan-practice.md +3 -2
  145. package/skills/loop-agent/references/task-workflow.md +7 -5
  146. package/skills/playwright-cli/SKILL.md +420 -420
  147. package/skills/playwright-cli/references/element-attributes.md +23 -23
  148. package/skills/playwright-cli/references/playwright-tests.md +39 -39
  149. package/skills/playwright-cli/references/request-mocking.md +87 -87
  150. package/skills/playwright-cli/references/running-code.md +241 -241
  151. package/skills/playwright-cli/references/session-management.md +225 -225
  152. package/skills/playwright-cli/references/storage-state.md +275 -275
  153. package/skills/playwright-cli/references/test-generation.md +433 -433
  154. package/skills/playwright-cli/references/tracing.md +139 -139
  155. package/skills/playwright-cli/references/video-recording.md +143 -143
  156. package/skills/requesting-code-review/SKILL.md +101 -101
  157. package/skills/requesting-code-review/code-reviewer.md +168 -168
  158. package/skills/systematic-debugging/CREATION-LOG.md +119 -119
  159. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
  160. package/skills/systematic-debugging/condition-based-waiting.md +115 -115
  161. package/skills/systematic-debugging/defense-in-depth.md +122 -122
  162. package/skills/systematic-debugging/find-polluter.sh +63 -63
  163. package/skills/systematic-debugging/root-cause-tracing.md +169 -169
  164. package/skills/systematic-debugging/test-academic.md +14 -14
  165. package/skills/systematic-debugging/test-pressure-1.md +58 -58
  166. package/skills/systematic-debugging/test-pressure-2.md +68 -68
  167. package/skills/systematic-debugging/test-pressure-3.md +69 -69
  168. package/skills/using-git-worktrees/SKILL.md +215 -215
  169. package/skills/verification-before-completion/SKILL.md +154 -154
  170. package/skills/webapp-testing/SKILL.md +19 -19
@@ -0,0 +1,468 @@
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
+ [--forbidden-path <glob>]...
22
+ [--no-default-forbidden-paths]
23
+ [--invariant <text>]...
24
+ [--ac <id:text> | --acceptance <id:text>]...
25
+ [--non-goal <text>]...
26
+ [--assumption <text>]...
27
+ [--open-question <text>]...
28
+ [--verify <label:command>]...
29
+ [--verify-timeout <label:timeoutMs>]...
30
+ [--request-id <id>]
31
+ [--dry-run | --apply]
32
+ [--force-overwrite-source]
33
+ [--json]`;
34
+ function pushList(target, value) {
35
+ const list = target ?? [];
36
+ list.push(value);
37
+ return list;
38
+ }
39
+ export function parseTaskSourcePrepareArgs(args) {
40
+ const options = {};
41
+ const positional = [];
42
+ for (let i = 0; i < args.length; i += 1) {
43
+ const token = args[i];
44
+ const next = () => {
45
+ const value = args[i + 1];
46
+ if (value === undefined || value.startsWith("-")) {
47
+ throw new Error(`missing value for ${token}\n${USAGE}`);
48
+ }
49
+ i += 1;
50
+ return value;
51
+ };
52
+ if (token === "--json") {
53
+ options.json = true;
54
+ continue;
55
+ }
56
+ if (token === "--dry-run") {
57
+ options.dryRun = true;
58
+ continue;
59
+ }
60
+ if (token === "--apply") {
61
+ options.apply = true;
62
+ continue;
63
+ }
64
+ if (token === "--force-overwrite-source") {
65
+ options.forceOverwriteSource = true;
66
+ continue;
67
+ }
68
+ if (token === "--use-imported-prd") {
69
+ options.useImportedPrd = true;
70
+ continue;
71
+ }
72
+ if (token === "--from-stdin") {
73
+ options.fromStdin = true;
74
+ continue;
75
+ }
76
+ if (token === "--no-default-forbidden-paths") {
77
+ options.noDefaultForbiddenPaths = true;
78
+ continue;
79
+ }
80
+ if (token === "--from-text") {
81
+ options.fromText = next();
82
+ continue;
83
+ }
84
+ if (token === "--from-file") {
85
+ options.fromFile = next();
86
+ continue;
87
+ }
88
+ if (token === "--from-draft") {
89
+ options.fromDraft = next();
90
+ continue;
91
+ }
92
+ if (token === "--title") {
93
+ options.title = next();
94
+ continue;
95
+ }
96
+ if (token === "--objective") {
97
+ options.objective = next();
98
+ continue;
99
+ }
100
+ if (token === "--scope") {
101
+ options.scope = pushList(options.scope, next());
102
+ continue;
103
+ }
104
+ if (token === "--task-kind") {
105
+ options.taskKind = next();
106
+ continue;
107
+ }
108
+ if (token === "--feature-id") {
109
+ options.featureId = next();
110
+ continue;
111
+ }
112
+ if (token === "--allowed-path") {
113
+ options.allowedPaths = pushList(options.allowedPaths, next());
114
+ continue;
115
+ }
116
+ if (token === "--forbidden-path") {
117
+ options.forbiddenPaths = pushList(options.forbiddenPaths, next());
118
+ continue;
119
+ }
120
+ if (token === "--invariant") {
121
+ options.invariants = pushList(options.invariants, next());
122
+ continue;
123
+ }
124
+ if (token === "--ac" || token === "--acceptance") {
125
+ const parsed = parseAcceptanceFlag(next());
126
+ options.acceptanceCriteria = [
127
+ ...(options.acceptanceCriteria ?? []),
128
+ parsed,
129
+ ];
130
+ continue;
131
+ }
132
+ if (token === "--non-goal") {
133
+ options.nonGoals = pushList(options.nonGoals, next());
134
+ continue;
135
+ }
136
+ if (token === "--assumption") {
137
+ options.assumptions = pushList(options.assumptions, next());
138
+ continue;
139
+ }
140
+ if (token === "--open-question") {
141
+ options.openQuestions = pushList(options.openQuestions, next());
142
+ continue;
143
+ }
144
+ if (token === "--verify") {
145
+ options.verify = pushList(options.verify, next());
146
+ continue;
147
+ }
148
+ if (token === "--verify-timeout") {
149
+ options.verifyTimeout = pushList(options.verifyTimeout, next());
150
+ continue;
151
+ }
152
+ if (token === "--request-id") {
153
+ options.requestId = next();
154
+ continue;
155
+ }
156
+ if (token.startsWith("-")) {
157
+ throw new Error(`unknown flag ${token}\n${USAGE}`);
158
+ }
159
+ positional.push(token);
160
+ }
161
+ const taskId = positional[0];
162
+ if (!taskId) {
163
+ throw new Error(USAGE);
164
+ }
165
+ if (positional.length > 1) {
166
+ throw new Error(`unexpected arguments: ${positional.slice(1).join(" ")}\n${USAGE}`);
167
+ }
168
+ return { taskId, options };
169
+ }
170
+ function validateOptionCombinations(options) {
171
+ if (options.dryRun && options.apply) {
172
+ throw new Error(`--dry-run and --apply are mutually exclusive\n${USAGE}`);
173
+ }
174
+ const textSources = [
175
+ options.fromText !== undefined,
176
+ options.fromFile !== undefined,
177
+ Boolean(options.fromStdin),
178
+ ].filter(Boolean).length;
179
+ if (textSources > 1) {
180
+ throw new Error(`--from-text / --from-file / --from-stdin are mutually exclusive\n${USAGE}`);
181
+ }
182
+ if (options.fromDraft) {
183
+ if (options.useImportedPrd ||
184
+ options.fromText !== undefined ||
185
+ options.fromFile !== undefined ||
186
+ options.fromStdin) {
187
+ throw new Error(`--from-draft is mutually exclusive with --use-imported-prd / --from-text / --from-file / --from-stdin\n${USAGE}`);
188
+ }
189
+ }
190
+ const hasIntentSource = Boolean(options.fromDraft) ||
191
+ Boolean(options.useImportedPrd) ||
192
+ options.fromText !== undefined ||
193
+ options.fromFile !== undefined ||
194
+ Boolean(options.fromStdin) ||
195
+ Boolean(options.title) ||
196
+ Boolean(options.objective) ||
197
+ Boolean(options.acceptanceCriteria?.length) ||
198
+ Boolean(options.allowedPaths?.length);
199
+ if (!hasIntentSource) {
200
+ 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}`);
201
+ }
202
+ }
203
+ async function readStdinText() {
204
+ if (stdinStream.isTTY) {
205
+ throw new Error("--from-stdin refused on TTY; pipe input or use --from-file/--from-text");
206
+ }
207
+ const chunks = [];
208
+ let total = 0;
209
+ for await (const chunk of stdinStream) {
210
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
211
+ total += buf.byteLength;
212
+ if (total > PREPARE_MAX_FILE_BYTES) {
213
+ throw new Error(`stdin input exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
214
+ }
215
+ chunks.push(buf);
216
+ }
217
+ return Buffer.concat(chunks).toString("utf-8");
218
+ }
219
+ async function loadTextInput(repoRoot, options) {
220
+ if (options.fromText !== undefined) {
221
+ if (Buffer.byteLength(options.fromText, "utf-8") > PREPARE_MAX_FILE_BYTES) {
222
+ throw new Error(`--from-text exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
223
+ }
224
+ return { text: options.fromText, label: "from-text" };
225
+ }
226
+ if (options.fromFile) {
227
+ const absolute = path.isAbsolute(options.fromFile)
228
+ ? options.fromFile
229
+ : path.resolve(repoRoot, options.fromFile);
230
+ const raw = await readFile(absolute);
231
+ if (raw.byteLength > PREPARE_MAX_FILE_BYTES) {
232
+ throw new Error(`--from-file exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
233
+ }
234
+ return { text: raw.toString("utf-8"), label: options.fromFile };
235
+ }
236
+ if (options.fromStdin) {
237
+ const text = await readStdinText();
238
+ return { text, label: "stdin" };
239
+ }
240
+ return {};
241
+ }
242
+ async function loadDraftInput(repoRoot, draftPath) {
243
+ const absolute = path.isAbsolute(draftPath)
244
+ ? draftPath
245
+ : path.resolve(repoRoot, draftPath);
246
+ const raw = await readFile(absolute);
247
+ if (raw.byteLength > PREPARE_MAX_FILE_BYTES) {
248
+ throw new Error(`--from-draft exceeds ${PREPARE_MAX_FILE_BYTES} bytes (INPUT_TOO_LARGE)`);
249
+ }
250
+ let json;
251
+ try {
252
+ json = JSON.parse(raw.toString("utf-8"));
253
+ }
254
+ catch (error) {
255
+ throw new Error(`--from-draft is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
256
+ }
257
+ return parseTaskContractDraft(json);
258
+ }
259
+ function buildFlags(options) {
260
+ const flags = {
261
+ title: options.title,
262
+ objective: options.objective,
263
+ scope: options.scope,
264
+ taskKind: options.taskKind,
265
+ featureId: options.featureId,
266
+ allowedPaths: options.allowedPaths,
267
+ forbiddenPaths: options.forbiddenPaths,
268
+ noDefaultForbiddenPaths: options.noDefaultForbiddenPaths,
269
+ invariants: options.invariants,
270
+ acceptanceCriteria: options.acceptanceCriteria,
271
+ nonGoals: options.nonGoals,
272
+ assumptions: options.assumptions,
273
+ openQuestions: options.openQuestions,
274
+ };
275
+ if (options.verify || options.verifyTimeout) {
276
+ const timeouts = new Map();
277
+ for (const raw of options.verifyTimeout ?? []) {
278
+ const parsed = parseVerifyTimeoutFlag(raw);
279
+ timeouts.set(parsed.label, parsed.timeoutMs);
280
+ }
281
+ const commands = (options.verify ?? []).map((raw) => {
282
+ const parsed = parseVerifyFlag(raw);
283
+ const timeoutMs = timeouts.get(parsed.label);
284
+ return {
285
+ label: parsed.label,
286
+ command: parsed.command,
287
+ ...(timeoutMs !== undefined ? { timeoutMs } : {}),
288
+ };
289
+ });
290
+ // If only timeouts provided without verify, treat as empty explicit list? No — only set when --verify appears.
291
+ if (options.verify) {
292
+ flags.verifyCommands = commands;
293
+ }
294
+ }
295
+ return flags;
296
+ }
297
+ function toOperatorEnvelope(result) {
298
+ const payload = {
299
+ taskId: result.taskId,
300
+ mode: result.mode,
301
+ taskKind: result.taskKind,
302
+ title: result.title,
303
+ draft: result.draft,
304
+ gaps: result.gaps,
305
+ risks: result.risks,
306
+ projected: result.projected,
307
+ apply: result.apply ?? null,
308
+ next: result.next,
309
+ contractStatus: result.contractStatus,
310
+ };
311
+ if (result.ok) {
312
+ return buildOperatorResult({
313
+ command: COMMAND,
314
+ ok: true,
315
+ outcome: result.outcome,
316
+ result: payload,
317
+ warnings: result.warnings ?? [],
318
+ });
319
+ }
320
+ return buildOperatorResult({
321
+ command: COMMAND,
322
+ ok: false,
323
+ outcome: result.outcome,
324
+ result: payload,
325
+ warnings: result.warnings ?? [],
326
+ error: result.error ?? {
327
+ code: "INVALID_INPUT",
328
+ message: "task source prepare failed",
329
+ },
330
+ });
331
+ }
332
+ function printHuman(result) {
333
+ const lines = [
334
+ `task source prepare: ${result.taskId}`,
335
+ ` mode: ${result.mode}`,
336
+ ` outcome: ${result.outcome}`,
337
+ ` ok: ${result.ok}`,
338
+ ];
339
+ if (result.title)
340
+ lines.push(` title: ${result.title}`);
341
+ if (result.taskKind)
342
+ lines.push(` taskKind: ${result.taskKind}`);
343
+ if (result.contractStatus) {
344
+ lines.push(` contractStatus: ${result.contractStatus}`);
345
+ }
346
+ if (result.gaps.length > 0) {
347
+ lines.push(" gaps:");
348
+ for (const gap of result.gaps) {
349
+ lines.push(` - [${gap.level}] ${gap.code}: ${gap.message}`);
350
+ }
351
+ }
352
+ if (result.risks.length > 0) {
353
+ lines.push(" risks:");
354
+ for (const risk of result.risks) {
355
+ lines.push(` - [${risk.level}] ${risk.code}: ${risk.message}`);
356
+ }
357
+ }
358
+ if (result.projected) {
359
+ lines.push(` projected: ${result.projected.requirementPath}, ${result.projected.constraintsPath}`);
360
+ if (result.draft) {
361
+ lines.push(` allowedPaths: ${result.draft.constraints.allowedPaths.join(", ") || "(none)"}`);
362
+ lines.push(` forbiddenPaths: ${result.draft.constraints.forbiddenPaths.join(", ") || "(none)"}`);
363
+ lines.push(` verifyCommands: ${result.draft.verification.commands
364
+ .map((c) => c.label)
365
+ .join(", ") || "(none)"}`);
366
+ }
367
+ }
368
+ if (result.apply) {
369
+ lines.push(` apply: revision=${result.apply.ref.revision} txId=${result.apply.txId}`);
370
+ }
371
+ if (result.error) {
372
+ lines.push(` error: ${result.error.code}: ${result.error.message}`);
373
+ }
374
+ if (result.next.length > 0) {
375
+ lines.push(" next:");
376
+ for (const step of result.next) {
377
+ lines.push(` - ${step}`);
378
+ }
379
+ }
380
+ console.log(lines.join("\n"));
381
+ }
382
+ export async function runTaskSourcePrepare(repoRoot, args) {
383
+ let taskId;
384
+ let options = {};
385
+ try {
386
+ ({ taskId, options } = parseTaskSourcePrepareArgs(args));
387
+ validateOptionCombinations(options);
388
+ }
389
+ catch (error) {
390
+ const message = error instanceof Error ? error.message : String(error);
391
+ const envelope = operatorFailed({
392
+ command: COMMAND,
393
+ outcome: "invalid",
394
+ code: "INVALID_INPUT",
395
+ message,
396
+ });
397
+ if (options.json || args.includes("--json")) {
398
+ writeOperatorJson(envelope);
399
+ }
400
+ else {
401
+ console.error(message);
402
+ }
403
+ process.exitCode = processExitCodeForOutcome(envelope.outcome);
404
+ return;
405
+ }
406
+ try {
407
+ const flags = buildFlags(options);
408
+ const mode = options.apply ? "apply" : "dry-run";
409
+ let prepareInput;
410
+ if (options.fromDraft) {
411
+ const draft = await loadDraftInput(repoRoot, options.fromDraft);
412
+ prepareInput = {
413
+ repoRoot,
414
+ taskId,
415
+ mode,
416
+ intent: { kind: "draft", draft, sourcePath: options.fromDraft },
417
+ flags,
418
+ requestId: options.requestId,
419
+ forceOverwriteSource: options.forceOverwriteSource,
420
+ };
421
+ }
422
+ else {
423
+ const textInput = await loadTextInput(repoRoot, options);
424
+ prepareInput = {
425
+ repoRoot,
426
+ taskId,
427
+ mode,
428
+ intent: {
429
+ kind: "facts",
430
+ useImportedPrd: Boolean(options.useImportedPrd),
431
+ text: textInput.text,
432
+ textLabel: textInput.label,
433
+ },
434
+ flags,
435
+ requestId: options.requestId,
436
+ forceOverwriteSource: options.forceOverwriteSource,
437
+ };
438
+ }
439
+ const result = await prepareTaskSource(prepareInput);
440
+ const envelope = toOperatorEnvelope(result);
441
+ if (options.json) {
442
+ writeOperatorJson(envelope);
443
+ }
444
+ else {
445
+ printHuman(result);
446
+ }
447
+ process.exitCode = processExitCodeForOutcome(result.outcome);
448
+ }
449
+ catch (error) {
450
+ const message = error instanceof Error ? error.message : String(error);
451
+ const code = message.includes("INPUT_TOO_LARGE")
452
+ ? "INPUT_TOO_LARGE"
453
+ : "INVALID_INPUT";
454
+ const envelope = operatorFailed({
455
+ command: COMMAND,
456
+ outcome: "invalid",
457
+ code,
458
+ message,
459
+ });
460
+ if (options.json) {
461
+ writeOperatorJson(envelope);
462
+ }
463
+ else {
464
+ console.error(message);
465
+ }
466
+ process.exitCode = processExitCodeForOutcome(envelope.outcome);
467
+ }
468
+ }
@@ -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