@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,506 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstat } from "node:fs/promises";
|
|
3
|
+
import { ContractMutationError, applyTaskContract, logicalRevisionForState, observeTaskContractState, projectConstraintsMarkdown, projectRequirementMarkdown, sha256OfCanonicalJson, validateDraftForTask, } from "../contract/index.js";
|
|
4
|
+
import { getTaskContractPaths } from "../contract/paths.js";
|
|
5
|
+
import { loadTaskConfig } from "../runtime.js";
|
|
6
|
+
import { buildPrepareDraft } from "./build-draft.js";
|
|
7
|
+
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, } from "./completeness.js";
|
|
8
|
+
import { extractRequirementFactsFromMarkdown, fillMissingRequirementFacts, mergeRequirementFacts, } from "./parse-intent.js";
|
|
9
|
+
import { draftReferencesFromManifest, validateImportedPrdReferences, } from "./reference-integrity.js";
|
|
10
|
+
import { readSourceManifest } from "../source-references.js";
|
|
11
|
+
import { PREPARE_MAX_FILE_BYTES } from "./types.js";
|
|
12
|
+
async function fileNonEmpty(filePath) {
|
|
13
|
+
try {
|
|
14
|
+
const stats = await lstat(filePath);
|
|
15
|
+
return stats.isFile() && stats.size > 0;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function loadExistingTaskConfig(repoRoot, taskId) {
|
|
22
|
+
try {
|
|
23
|
+
return await loadTaskConfig(repoRoot, taskId);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function mutationOutcome(code) {
|
|
30
|
+
switch (code) {
|
|
31
|
+
case "REVISION_CONFLICT":
|
|
32
|
+
case "OBSERVED_HASH_CONFLICT":
|
|
33
|
+
case "REQUEST_ID_REUSE_CONFLICT":
|
|
34
|
+
case "LEASE_HELD":
|
|
35
|
+
return "conflict";
|
|
36
|
+
case "NOT_FOUND":
|
|
37
|
+
return "not-found";
|
|
38
|
+
case "TRANSACTION_INCOMPLETE":
|
|
39
|
+
case "OPERATION_NEEDS_RECONCILE":
|
|
40
|
+
return "needs-reconcile";
|
|
41
|
+
case "EXTERNALLY_MODIFIED":
|
|
42
|
+
case "NOT_MANAGED":
|
|
43
|
+
case "BINDING_DRIFT":
|
|
44
|
+
return "blocked";
|
|
45
|
+
case "INVALID_INPUT":
|
|
46
|
+
case "PATH_OUTSIDE_REPO":
|
|
47
|
+
case "UNSUPPORTED_SCHEMA_VERSION":
|
|
48
|
+
return "invalid";
|
|
49
|
+
default:
|
|
50
|
+
return "rejected";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function buildNextSteps(input) {
|
|
54
|
+
const next = [];
|
|
55
|
+
if (!input.hasImportedPrd) {
|
|
56
|
+
next.push(`loop-agent import-prd ${input.taskId} --file <prd.md> --role requirement`);
|
|
57
|
+
}
|
|
58
|
+
if (input.gaps.some((g) => g.code === "EMPTY_ALLOWED_PATHS")) {
|
|
59
|
+
next.push("补 --allowed-path <glob>");
|
|
60
|
+
}
|
|
61
|
+
if (input.gaps.some((g) => g.code === "EMPTY_ACCEPTANCE")) {
|
|
62
|
+
next.push("补 --ac <id:text> 或完善 PRD 验收标准章节");
|
|
63
|
+
}
|
|
64
|
+
if (input.mode === "dry-run" && input.ok) {
|
|
65
|
+
next.push(`loop-agent task source prepare ${input.taskId} --use-imported-prd ... --apply --json`);
|
|
66
|
+
}
|
|
67
|
+
if (input.ok && input.mode === "apply") {
|
|
68
|
+
next.push(`loop-agent task contract show ${input.taskId} --json`);
|
|
69
|
+
next.push(`loop-agent dag run-task ${input.taskId} --profile auto --strict-models`);
|
|
70
|
+
next.push("审查 DAG writeSet 后再 run-dag");
|
|
71
|
+
}
|
|
72
|
+
if (input.gaps.some((g) => g.code === "DIRTY_SOURCE")) {
|
|
73
|
+
next.push("loop-agent task contract show|diff|adopt|recover");
|
|
74
|
+
next.push("或显式 --force-overwrite-source 后重跑 prepare");
|
|
75
|
+
}
|
|
76
|
+
if (input.gaps.some((g) => g.code === "TRANSACTION_INCOMPLETE")) {
|
|
77
|
+
next.push(`loop-agent task contract recover --task ${input.taskId}`);
|
|
78
|
+
}
|
|
79
|
+
return next;
|
|
80
|
+
}
|
|
81
|
+
function emptyObservedHashPlaceholder() {
|
|
82
|
+
return "0".repeat(64);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Core prepare orchestration: facts → thin Draft → gates → optional apply.
|
|
86
|
+
* No LLM. Production md writes only via applyTaskContract projection.
|
|
87
|
+
*/
|
|
88
|
+
export async function prepareTaskSource(input) {
|
|
89
|
+
const { repoRoot, taskId } = input;
|
|
90
|
+
const contractPaths = getTaskContractPaths(repoRoot, taskId);
|
|
91
|
+
const state = await observeTaskContractState(repoRoot, taskId);
|
|
92
|
+
if (state.observationErrors.some((e) => e.code === "NOT_FOUND")) {
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
outcome: "not-found",
|
|
96
|
+
mode: input.mode,
|
|
97
|
+
taskId,
|
|
98
|
+
gaps: [
|
|
99
|
+
{
|
|
100
|
+
code: "TASK_NOT_FOUND",
|
|
101
|
+
level: "blocking",
|
|
102
|
+
message: `task not found: ${taskId}`,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
risks: [],
|
|
106
|
+
next: [`loop-agent new-task ${taskId} "<title>"`],
|
|
107
|
+
error: {
|
|
108
|
+
code: "NOT_FOUND",
|
|
109
|
+
message: `task not found: ${taskId}`,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const existingTaskConfig = await loadExistingTaskConfig(repoRoot, taskId);
|
|
114
|
+
const sourceFilesPresent = {
|
|
115
|
+
requirement: await fileNonEmpty(contractPaths.requirementPath),
|
|
116
|
+
constraints: await fileNonEmpty(contractPaths.constraintsPath),
|
|
117
|
+
};
|
|
118
|
+
// Contract may record referenceManifestSha256 while observe only reports
|
|
119
|
+
// EXTERNALLY_MODIFIED after the manifest file disappears (hash already drifts).
|
|
120
|
+
// That is a non-fresh observation condition force must not bypass.
|
|
121
|
+
if (state.ref?.sourceHashes.referenceManifestSha256) {
|
|
122
|
+
const manifest = await readSourceManifest(contractPaths.sourceDir);
|
|
123
|
+
if (!manifest) {
|
|
124
|
+
state.observationErrors = [
|
|
125
|
+
...state.observationErrors,
|
|
126
|
+
{
|
|
127
|
+
code: "MANIFEST_MISSING",
|
|
128
|
+
path: contractPaths.sourceManifestPath,
|
|
129
|
+
message: "contract records referenceManifestSha256 but source-manifest.json is missing",
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
let baseDraft;
|
|
135
|
+
let facts;
|
|
136
|
+
let sourceIntegrity = null;
|
|
137
|
+
let hasImportedPrd = false;
|
|
138
|
+
let references;
|
|
139
|
+
if (input.intent.kind === "draft") {
|
|
140
|
+
baseDraft = input.intent.draft;
|
|
141
|
+
if (baseDraft.taskId !== taskId) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
outcome: "invalid",
|
|
145
|
+
mode: input.mode,
|
|
146
|
+
taskId,
|
|
147
|
+
gaps: [
|
|
148
|
+
{
|
|
149
|
+
code: "DRAFT_TASK_ID_MISMATCH",
|
|
150
|
+
level: "blocking",
|
|
151
|
+
message: `draft.taskId "${baseDraft.taskId}" does not match ${taskId}`,
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
risks: [],
|
|
155
|
+
next: [],
|
|
156
|
+
error: {
|
|
157
|
+
code: "INVALID_INPUT",
|
|
158
|
+
message: `draft.taskId "${baseDraft.taskId}" does not match ${taskId}`,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
// Still verify any references in draft against manifest when present.
|
|
163
|
+
if (baseDraft.references?.length) {
|
|
164
|
+
sourceIntegrity = await validateImportedPrdReferences({
|
|
165
|
+
repoRoot,
|
|
166
|
+
taskId,
|
|
167
|
+
requireParseableRequirement: false,
|
|
168
|
+
});
|
|
169
|
+
hasImportedPrd = sourceIntegrity.documents.length > 0;
|
|
170
|
+
const allowedRefs = new Set(sourceIntegrity.documents.map((document) => document.materializedPath));
|
|
171
|
+
for (const ref of baseDraft.references) {
|
|
172
|
+
const normalizedRef = ref.ref.replace(/\\/g, "/");
|
|
173
|
+
if (!allowedRefs.has(normalizedRef)) {
|
|
174
|
+
sourceIntegrity.issues.push({
|
|
175
|
+
code: "REFERENCE_NOT_IN_MANIFEST",
|
|
176
|
+
level: "blocking",
|
|
177
|
+
message: `draft reference not found in source-manifest: ${ref.ref}`,
|
|
178
|
+
path: ref.ref,
|
|
179
|
+
});
|
|
180
|
+
sourceIntegrity.ok = false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const importedFactParts = [];
|
|
187
|
+
let supplementalFacts;
|
|
188
|
+
if (input.intent.useImportedPrd) {
|
|
189
|
+
sourceIntegrity = await validateImportedPrdReferences({
|
|
190
|
+
repoRoot,
|
|
191
|
+
taskId,
|
|
192
|
+
requireParseableRequirement: false,
|
|
193
|
+
});
|
|
194
|
+
hasImportedPrd = true;
|
|
195
|
+
if (sourceIntegrity.ok) {
|
|
196
|
+
references = draftReferencesFromManifest(sourceIntegrity.documents);
|
|
197
|
+
let acStart = 1;
|
|
198
|
+
for (const doc of sourceIntegrity.parseableDocuments) {
|
|
199
|
+
if (!doc.content)
|
|
200
|
+
continue;
|
|
201
|
+
const extracted = extractRequirementFactsFromMarkdown(doc.content, {
|
|
202
|
+
sourceLabel: doc.materializedPath,
|
|
203
|
+
acCounterStart: acStart,
|
|
204
|
+
});
|
|
205
|
+
acStart += extracted.acceptanceCriteria.filter((ac) => /^AC-\d+$/i.test(ac.id)).length;
|
|
206
|
+
// Better: count all auto ids; keep simple sequential across docs.
|
|
207
|
+
const maxAuto = extracted.acceptanceCriteria.reduce((max, ac) => {
|
|
208
|
+
const m = /^AC-(\d+)$/i.exec(ac.id);
|
|
209
|
+
if (!m)
|
|
210
|
+
return max;
|
|
211
|
+
return Math.max(max, Number(m[1]));
|
|
212
|
+
}, acStart - 1);
|
|
213
|
+
acStart = maxAuto + 1;
|
|
214
|
+
importedFactParts.push(extracted);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (input.intent.text) {
|
|
219
|
+
if (Buffer.byteLength(input.intent.text, "utf-8") > PREPARE_MAX_FILE_BYTES) {
|
|
220
|
+
return {
|
|
221
|
+
ok: false,
|
|
222
|
+
outcome: "invalid",
|
|
223
|
+
mode: input.mode,
|
|
224
|
+
taskId,
|
|
225
|
+
gaps: [
|
|
226
|
+
{
|
|
227
|
+
code: "INPUT_TOO_LARGE",
|
|
228
|
+
level: "blocking",
|
|
229
|
+
message: `text input exceeds ${PREPARE_MAX_FILE_BYTES} bytes`,
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
risks: [],
|
|
233
|
+
next: [],
|
|
234
|
+
error: {
|
|
235
|
+
code: "INPUT_TOO_LARGE",
|
|
236
|
+
message: `text input exceeds ${PREPARE_MAX_FILE_BYTES} bytes`,
|
|
237
|
+
},
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
supplementalFacts = extractRequirementFactsFromMarkdown(input.intent.text, {
|
|
241
|
+
sourceLabel: input.intent.textLabel ?? "supplemental-text",
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const importedFacts = importedFactParts.length > 0
|
|
245
|
+
? mergeRequirementFacts(importedFactParts)
|
|
246
|
+
: undefined;
|
|
247
|
+
const mergedSupplemental = supplementalFacts
|
|
248
|
+
? mergeRequirementFacts([supplementalFacts])
|
|
249
|
+
: undefined;
|
|
250
|
+
if (importedFacts && mergedSupplemental) {
|
|
251
|
+
facts = fillMissingRequirementFacts(importedFacts, mergedSupplemental);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
facts = importedFacts ?? mergedSupplemental;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const built = buildPrepareDraft({
|
|
258
|
+
taskId,
|
|
259
|
+
existingTaskConfig,
|
|
260
|
+
baseDraft,
|
|
261
|
+
facts,
|
|
262
|
+
flags: input.flags,
|
|
263
|
+
references,
|
|
264
|
+
});
|
|
265
|
+
const { gaps, risks } = listPrepareGaps({
|
|
266
|
+
draft: built.draft,
|
|
267
|
+
state,
|
|
268
|
+
sourceIntegrity,
|
|
269
|
+
pathAssessment: built.pathAssessment,
|
|
270
|
+
hasImportedPrd,
|
|
271
|
+
acceptanceConflicts: built.acceptanceConflicts,
|
|
272
|
+
sourceFilesPresent,
|
|
273
|
+
forceOverwriteSource: input.forceOverwriteSource,
|
|
274
|
+
});
|
|
275
|
+
// If --use-imported-prd and no parseable requirement and no AC from flags/text → blocking
|
|
276
|
+
if (input.intent.kind === "facts" &&
|
|
277
|
+
input.intent.useImportedPrd &&
|
|
278
|
+
sourceIntegrity &&
|
|
279
|
+
!sourceIntegrity.parseableDocuments.some((d) => d.role === "requirement") &&
|
|
280
|
+
built.draft.requirement.acceptanceCriteria.length === 0 &&
|
|
281
|
+
!input.flags.acceptanceCriteria?.length &&
|
|
282
|
+
!input.intent.text) {
|
|
283
|
+
gaps.push({
|
|
284
|
+
code: "NO_PARSEABLE_REQUIREMENT",
|
|
285
|
+
level: "blocking",
|
|
286
|
+
message: "no parseable requirement document and no --ac / supplemental text; import a requirement-role PRD or pass flags",
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const validation = await validateDraftForTask({
|
|
290
|
+
repoRoot,
|
|
291
|
+
taskId,
|
|
292
|
+
draft: built.draft,
|
|
293
|
+
});
|
|
294
|
+
if (!validation.ok) {
|
|
295
|
+
for (const message of validation.errors) {
|
|
296
|
+
gaps.push({
|
|
297
|
+
code: "DRAFT_VALIDATION",
|
|
298
|
+
level: "blocking",
|
|
299
|
+
message,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const requirementPreview = projectRequirementMarkdown(built.draft);
|
|
304
|
+
const constraintsPreview = projectConstraintsMarkdown(built.draft);
|
|
305
|
+
const projected = {
|
|
306
|
+
requirementPath: "source/需求.md",
|
|
307
|
+
constraintsPath: "source/执行约束.md",
|
|
308
|
+
requirementPreview,
|
|
309
|
+
constraintsPreview,
|
|
310
|
+
};
|
|
311
|
+
const blocking = hasBlockingGaps(gaps) ||
|
|
312
|
+
hasBlockingRisks(risks) ||
|
|
313
|
+
!validation.ok;
|
|
314
|
+
const warnings = gaps
|
|
315
|
+
.filter((g) => g.level === "warning")
|
|
316
|
+
.map((g) => ({ code: g.code, message: g.message }));
|
|
317
|
+
const next = buildNextSteps({
|
|
318
|
+
taskId,
|
|
319
|
+
hasImportedPrd,
|
|
320
|
+
mode: input.mode,
|
|
321
|
+
ok: !blocking,
|
|
322
|
+
gaps,
|
|
323
|
+
});
|
|
324
|
+
if (input.mode !== "apply") {
|
|
325
|
+
return {
|
|
326
|
+
ok: !blocking,
|
|
327
|
+
outcome: blocking ? "invalid" : "succeeded",
|
|
328
|
+
mode: "dry-run",
|
|
329
|
+
taskId,
|
|
330
|
+
taskKind: built.draft.taskKind,
|
|
331
|
+
title: built.draft.title,
|
|
332
|
+
draft: built.draft,
|
|
333
|
+
gaps,
|
|
334
|
+
risks,
|
|
335
|
+
projected,
|
|
336
|
+
next,
|
|
337
|
+
contractStatus: state.effectiveStatus,
|
|
338
|
+
warnings,
|
|
339
|
+
...(blocking
|
|
340
|
+
? {
|
|
341
|
+
error: {
|
|
342
|
+
code: "INVALID_INPUT",
|
|
343
|
+
message: "task source draft has blocking gaps",
|
|
344
|
+
details: {
|
|
345
|
+
gaps: gaps
|
|
346
|
+
.filter((g) => g.level === "blocking")
|
|
347
|
+
.map((g) => g.code),
|
|
348
|
+
},
|
|
349
|
+
},
|
|
350
|
+
}
|
|
351
|
+
: {}),
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
// apply path — prioritize state-machine outcomes over generic invalid gates
|
|
355
|
+
if (state.effectiveStatus === "transaction-incomplete" ||
|
|
356
|
+
gaps.some((g) => g.code === "TRANSACTION_INCOMPLETE")) {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
outcome: "needs-reconcile",
|
|
360
|
+
mode: "apply",
|
|
361
|
+
taskId,
|
|
362
|
+
taskKind: built.draft.taskKind,
|
|
363
|
+
title: built.draft.title,
|
|
364
|
+
draft: built.draft,
|
|
365
|
+
gaps,
|
|
366
|
+
risks,
|
|
367
|
+
projected,
|
|
368
|
+
next: [`loop-agent task contract recover --task ${taskId}`],
|
|
369
|
+
contractStatus: state.effectiveStatus,
|
|
370
|
+
warnings,
|
|
371
|
+
error: {
|
|
372
|
+
code: "TRANSACTION_INCOMPLETE",
|
|
373
|
+
message: "unfinished transaction; run task contract recover first",
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if (blocking) {
|
|
378
|
+
const blockingCodes = gaps
|
|
379
|
+
.filter((g) => g.level === "blocking")
|
|
380
|
+
.map((g) => g.code);
|
|
381
|
+
const outcome = blockingCodes.includes("DIRTY_SOURCE")
|
|
382
|
+
? "blocked"
|
|
383
|
+
: "invalid";
|
|
384
|
+
return {
|
|
385
|
+
ok: false,
|
|
386
|
+
outcome,
|
|
387
|
+
mode: "apply",
|
|
388
|
+
taskId,
|
|
389
|
+
taskKind: built.draft.taskKind,
|
|
390
|
+
title: built.draft.title,
|
|
391
|
+
draft: built.draft,
|
|
392
|
+
gaps,
|
|
393
|
+
risks,
|
|
394
|
+
projected,
|
|
395
|
+
next,
|
|
396
|
+
contractStatus: state.effectiveStatus,
|
|
397
|
+
warnings,
|
|
398
|
+
error: {
|
|
399
|
+
code: outcome === "blocked" ? "DIRTY_SOURCE" : "INVALID_INPUT",
|
|
400
|
+
message: "task source draft has blocking gaps",
|
|
401
|
+
details: {
|
|
402
|
+
gaps: blockingCodes,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const expectedRevision = logicalRevisionForState(state);
|
|
408
|
+
const expectedObservedHash = state.observedCanonicalHash ??
|
|
409
|
+
(expectedRevision === 0 ? emptyObservedHashPlaceholder() : null);
|
|
410
|
+
if (!expectedObservedHash) {
|
|
411
|
+
return {
|
|
412
|
+
ok: false,
|
|
413
|
+
outcome: "conflict",
|
|
414
|
+
mode: "apply",
|
|
415
|
+
taskId,
|
|
416
|
+
draft: built.draft,
|
|
417
|
+
gaps,
|
|
418
|
+
risks,
|
|
419
|
+
projected,
|
|
420
|
+
next: [`loop-agent task contract doctor --task ${taskId}`],
|
|
421
|
+
error: {
|
|
422
|
+
code: "OBSERVED_HASH_CONFLICT",
|
|
423
|
+
message: "unable to determine expectedObservedHash",
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
// Dirty without force already blocked above. Fresh legacy is allowed.
|
|
428
|
+
void isFreshLegacyUnversioned;
|
|
429
|
+
const requestId = input.requestId?.trim() || `prepare-${taskId}-${randomUUID()}`;
|
|
430
|
+
const requestPayloadSha256 = sha256OfCanonicalJson(built.draft);
|
|
431
|
+
try {
|
|
432
|
+
const applyResult = await applyTaskContract({
|
|
433
|
+
repoRoot,
|
|
434
|
+
taskId,
|
|
435
|
+
draft: built.draft,
|
|
436
|
+
expectedRevision,
|
|
437
|
+
expectedObservedHash,
|
|
438
|
+
requestId,
|
|
439
|
+
requestPayloadSha256,
|
|
440
|
+
payloadSha256Trusted: true,
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
ok: true,
|
|
444
|
+
outcome: "succeeded",
|
|
445
|
+
mode: "apply",
|
|
446
|
+
taskId,
|
|
447
|
+
taskKind: built.draft.taskKind,
|
|
448
|
+
title: built.draft.title,
|
|
449
|
+
draft: built.draft,
|
|
450
|
+
gaps,
|
|
451
|
+
risks,
|
|
452
|
+
projected,
|
|
453
|
+
apply: applyResult,
|
|
454
|
+
next: buildNextSteps({
|
|
455
|
+
taskId,
|
|
456
|
+
hasImportedPrd,
|
|
457
|
+
mode: "apply",
|
|
458
|
+
ok: true,
|
|
459
|
+
gaps,
|
|
460
|
+
}),
|
|
461
|
+
contractStatus: "managed",
|
|
462
|
+
warnings,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
if (error instanceof ContractMutationError) {
|
|
467
|
+
const outcome = mutationOutcome(error.code);
|
|
468
|
+
return {
|
|
469
|
+
ok: false,
|
|
470
|
+
outcome,
|
|
471
|
+
mode: "apply",
|
|
472
|
+
taskId,
|
|
473
|
+
taskKind: built.draft.taskKind,
|
|
474
|
+
title: built.draft.title,
|
|
475
|
+
draft: built.draft,
|
|
476
|
+
gaps,
|
|
477
|
+
risks,
|
|
478
|
+
projected,
|
|
479
|
+
next: [
|
|
480
|
+
`loop-agent task contract show ${taskId} --json`,
|
|
481
|
+
`loop-agent task contract doctor --task ${taskId}`,
|
|
482
|
+
],
|
|
483
|
+
error: {
|
|
484
|
+
code: error.code,
|
|
485
|
+
message: error.message,
|
|
486
|
+
details: error.details,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
ok: false,
|
|
492
|
+
outcome: "rejected",
|
|
493
|
+
mode: "apply",
|
|
494
|
+
taskId,
|
|
495
|
+
draft: built.draft,
|
|
496
|
+
gaps,
|
|
497
|
+
risks,
|
|
498
|
+
projected,
|
|
499
|
+
next: [],
|
|
500
|
+
error: {
|
|
501
|
+
code: "INTERNAL_ERROR",
|
|
502
|
+
message: error instanceof Error ? error.message : String(error),
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
}
|