@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.
- package/CHANGELOG.md +1032 -1020
- package/README.md +8 -3
- package/bin/loop-agent.js +21 -21
- package/dist/cli/command-definitions.js +25 -10
- package/dist/cli/help.js +4 -3
- package/dist/cli/program.js +43 -17
- package/dist/commands/cursor-prompt.js +6 -6
- package/dist/commands/import-prd.js +7 -2
- package/dist/commands/init.js +7 -5
- package/dist/commands/loop-benchmark.js +11 -11
- package/dist/commands/pi-reuse-benchmark.js +16 -16
- package/dist/commands/task-source-prepare.js +468 -0
- package/dist/executors/dag-pi-executor.js +40 -5
- package/dist/executors/shell-write-guard.js +161 -25
- package/dist/sidecars/cursor-prompt/executor.js +1 -1
- package/dist/task/source-prepare/build-draft.js +215 -0
- package/dist/task/source-prepare/completeness.js +195 -0
- package/dist/task/source-prepare/index.js +7 -0
- package/dist/task/source-prepare/parse-intent.js +373 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/prepare.js +506 -0
- package/dist/task/source-prepare/reference-integrity.js +274 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/worker/observe/static/copy.js +67 -67
- package/dist/worker/observe/static/dag-layout.d.ts +31 -31
- package/dist/worker/observe/static/dag-layout.js +83 -83
- package/dist/worker/observe/static/dom.js +220 -220
- package/dist/worker/observe/static/relations.js +133 -133
- package/dist/worker/observe/static/router.js +93 -93
- package/dist/worker/observe/static/run-processing.js +148 -148
- package/dist/worker/observe/static/views/batch.js +227 -227
- package/dist/worker/observe/static/views/dag-graph.js +172 -172
- package/dist/worker/observe/static/views/failures.js +143 -143
- package/dist/worker/observe/static/views/feature.js +492 -492
- package/dist/worker/observe/static/views/run.js +453 -453
- package/dist/worker/observe/static/views/shell.js +7 -7
- package/dist/worker/observe/static/views/timeline.js +163 -163
- package/dist/workflows/dag/canvas-observer.js +275 -275
- package/docs/skills/README.md +7 -7
- package/docs/templates/adr.md +60 -60
- package/docs/templates/agent-dag-authority-surface-audit.prompt.md +94 -94
- package/docs/templates/agent-dag-decision-envelope.schema.json +213 -213
- package/docs/templates/agent-dag-decision-gate.prompt.md +246 -246
- package/docs/templates/agent-dag-process-supervisor.prompt.md +98 -98
- package/docs/templates/agent-dag-report.schema.json +473 -473
- package/docs/templates/agent-dag-review-verdict.prompt.md +68 -68
- package/docs/templates/backend-test-result.schema.json +99 -99
- package/docs/templates/feature-spec.md +53 -53
- package/docs/templates/frontend-design-contract.md +42 -42
- package/docs/templates/frontend-eval/fixtures/failures/01-type-build-error.md +17 -17
- package/docs/templates/frontend-eval/fixtures/failures/02-unit-component-test-fail.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/03-fixture-schema-drift.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/04-missing-loading-empty-error-state.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/05-forbidden-write-writeset-expansion.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/06-unapproved-dependency-add.md +16 -16
- package/docs/templates/frontend-eval/fixtures/failures/07-mock-production-on.md +21 -21
- package/docs/templates/frontend-eval/fixtures/functional/01-simple-component-style.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/02-form-validation.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/03-list-detail-page.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/04-api-mock.md +29 -29
- package/docs/templates/frontend-eval/fixtures/functional/05-permission-auth-gated-ui.md +27 -27
- package/docs/templates/frontend-eval/fixtures/functional/06-ssr-server-client-boundary.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/07-shared-public-component-api.md +28 -28
- package/docs/templates/frontend-eval/fixtures/functional/08-pure-local-no-remote.md +27 -27
- package/docs/templates/frontend-eval/metrics.md +138 -138
- package/docs/templates/frontend-eval/smoke-targets.md +53 -53
- package/docs/templates/frontend-task-constraints.md +35 -35
- package/docs/templates/frontend-task-requirement.md +70 -70
- package/docs/templates/init-evolution-review.md +35 -35
- package/docs/templates/init-managed-agents.md +10 -5
- package/docs/templates/interactive-ui-round2-experiment.md +66 -66
- package/docs/templates/knowledge-graph-bootstrap-dag.json +118 -118
- package/docs/templates/knowledge-sync-dag.json +178 -178
- package/docs/templates/knowledge-sync-draft.schema.json +71 -71
- package/docs/templates/product-line/closeout.yaml +9 -9
- package/docs/templates/product-line/design.md +13 -13
- package/docs/templates/product-line/links.md +10 -10
- package/docs/templates/product-line/requirement.md +17 -17
- package/docs/templates/product-line/test-plan.md +7 -7
- package/docs/templates/production-readiness-checklist.md +57 -57
- package/docs/templates/project-start-checklist.md +9 -9
- package/docs/templates/qa-report.md +48 -48
- package/docs/templates/sprint-contract.md +29 -29
- package/docs/templates/worker-dogfood-evidence.md +80 -80
- package/docs/templates/worker-dogfood-setup.md +68 -68
- package/package.json +1 -1
- package/scripts/kb-bootstrap-init-skeleton.sh +0 -0
- package/scripts/kb-graph-incremental-prepare.mjs +386 -386
- package/scripts/kb-graph-materialize.mjs +105 -105
- package/scripts/kb-graph-promote.mjs +164 -164
- package/scripts/kb-query.mjs +554 -554
- package/skills/ai-engineering-context/SKILL.md +48 -48
- package/skills/analyze-product-dependencies/SKILL.md +67 -67
- package/skills/analyze-product-dependencies/agents/openai.yaml +4 -4
- package/skills/analyze-product-dependencies/references/api-documentation-schema.md +30 -30
- package/skills/analyze-product-dependencies/references/dependency-analysis-schema.md +28 -28
- package/skills/analyze-product-dependencies/references/example.md +76 -76
- package/skills/analyze-product-dependencies/references/forward-test-cases.md +35 -35
- package/skills/analyze-product-dependencies/references/input-contract.md +11 -11
- package/skills/analyze-product-dependencies/references/scouting-rules.md +61 -61
- package/skills/analyze-product-dependencies/scripts/test-validators.mjs +267 -267
- package/skills/analyze-product-dependencies/scripts/validate-api-documentation.mjs +101 -101
- package/skills/analyze-product-dependencies/scripts/validate-dependency-analysis.mjs +142 -142
- package/skills/analyze-product-dependencies/scripts/validate-product-requirement-input.mjs +76 -76
- package/skills/analyze-product-dependencies/scripts/validation-helpers.mjs +146 -146
- package/skills/analyze-product-requirements/SKILL.md +90 -90
- package/skills/analyze-product-requirements/agents/openai.yaml +4 -4
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +91 -91
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +56 -56
- package/skills/analyze-product-requirements/references/example.md +86 -86
- package/skills/analyze-product-requirements/references/forward-test-cases.md +66 -66
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +32 -32
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +33 -33
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +35 -35
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +193 -193
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +69 -69
- package/skills/analyze-product-requirements/scripts/validate-product-requirement.mjs +97 -97
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +98 -98
- package/skills/analyze-product-requirements/scripts/validation-helpers.mjs +156 -156
- package/skills/browser-tools/browser-content.js +103 -103
- package/skills/browser-tools/browser-cookies.js +35 -35
- package/skills/browser-tools/browser-eval.js +53 -53
- package/skills/browser-tools/browser-hn-scraper.js +108 -108
- package/skills/browser-tools/browser-nav.js +44 -44
- package/skills/browser-tools/browser-pick.js +162 -162
- package/skills/browser-tools/browser-screenshot.js +34 -34
- package/skills/browser-tools/browser-start.js +86 -86
- package/skills/browser-tools/package-lock.json +2556 -2556
- package/skills/browser-tools/package.json +19 -19
- package/skills/code-review-core/SKILL.md +20 -20
- package/skills/codebase-scout/SKILL.md +19 -19
- package/skills/grill-me/SKILL.md +10 -10
- package/skills/loop-agent/SKILL.md +5 -2
- package/skills/loop-agent/references/README.md +67 -67
- package/skills/loop-agent/references/command-reference.md +17 -15
- package/skills/loop-agent/references/docs-converge.md +126 -126
- package/skills/loop-agent/references/harness-policy.md +3 -4
- package/skills/loop-agent/references/learned/README.md +21 -21
- package/skills/loop-agent/references/long-running-loop.md +57 -57
- package/skills/loop-agent/references/one-shot-runs.md +85 -85
- package/skills/loop-agent/references/pi-prompt.md +23 -23
- package/skills/loop-agent/references/pi-subagent-assisted-mode.md +84 -84
- package/skills/loop-agent/references/post-implementation-and-patterns.md +1 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +3 -2
- package/skills/loop-agent/references/task-workflow.md +7 -5
- package/skills/playwright-cli/SKILL.md +420 -420
- package/skills/playwright-cli/references/element-attributes.md +23 -23
- package/skills/playwright-cli/references/playwright-tests.md +39 -39
- package/skills/playwright-cli/references/request-mocking.md +87 -87
- package/skills/playwright-cli/references/running-code.md +241 -241
- package/skills/playwright-cli/references/session-management.md +225 -225
- package/skills/playwright-cli/references/storage-state.md +275 -275
- package/skills/playwright-cli/references/test-generation.md +433 -433
- package/skills/playwright-cli/references/tracing.md +139 -139
- package/skills/playwright-cli/references/video-recording.md +143 -143
- package/skills/requesting-code-review/SKILL.md +101 -101
- package/skills/requesting-code-review/code-reviewer.md +168 -168
- package/skills/systematic-debugging/CREATION-LOG.md +119 -119
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -158
- package/skills/systematic-debugging/condition-based-waiting.md +115 -115
- package/skills/systematic-debugging/defense-in-depth.md +122 -122
- package/skills/systematic-debugging/find-polluter.sh +63 -63
- package/skills/systematic-debugging/root-cause-tracing.md +169 -169
- package/skills/systematic-debugging/test-academic.md +14 -14
- package/skills/systematic-debugging/test-pressure-1.md +58 -58
- package/skills/systematic-debugging/test-pressure-2.md +68 -68
- package/skills/systematic-debugging/test-pressure-3.md +69 -69
- package/skills/using-git-worktrees/SKILL.md +215 -215
- package/skills/verification-before-completion/SKILL.md +154 -154
- 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
|
-
|
|
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
|
-
|
|
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
|