@tea-agent/loop-agent 0.33.7-beta.0 → 0.34.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 +67 -16
- package/dist/application/task-lifecycle/advance.js +309 -9
- package/dist/application/task-lifecycle/gates.js +50 -0
- package/dist/application/task-lifecycle/observe.js +11 -2
- package/dist/application/task-lifecycle/plan-transitions.js +13 -21
- package/dist/commands/init-upgrade.js +32 -1
- package/dist/commands/init.js +94 -3
- package/dist/executors/dag-pi-executor.js +18 -3
- package/dist/executors/shell-executor.js +4 -91
- package/dist/executors/shell-write-guard.js +26 -8
- package/dist/shared/operator/capabilities.js +98 -44
- package/dist/shared/resilient-git.js +133 -0
- package/dist/task/source-prepare/artifact-meta.js +137 -0
- package/dist/task/source-prepare/index.js +2 -0
- package/dist/task/source-prepare/parse-intent.js +58 -10
- package/dist/task/source-prepare/prepare.js +229 -18
- package/dist/task/source-prepare/reference-integrity.js +18 -2
- package/dist/task/source-prepare/semantic-intake.js +404 -0
- package/dist/worker/console/app-data.js +2 -0
- package/dist/worker/console/chat/chat-event-store.js +190 -25
- package/dist/worker/console/chat/model-resolver.js +17 -0
- package/dist/worker/console/chat/pi-console-config.js +250 -32
- package/dist/worker/console/chat/pi-runtime.js +1007 -188
- package/dist/worker/console/chat/resource-loader.js +5 -4
- package/dist/worker/console/chat/routes.js +495 -157
- package/dist/worker/console/chat/runtime-context.js +48 -12
- package/dist/worker/console/chat/runtime-selection.js +59 -0
- package/dist/worker/console/chat/session-store.js +39 -0
- package/dist/worker/console/chat/shortcuts.js +1 -0
- package/dist/worker/console/chat/tool-adapter.js +9 -3
- package/dist/worker/console/chat/tools.js +5 -1
- package/dist/worker/console/dag-execution-receipt.js +380 -0
- package/dist/worker/console/interview/grill-me.js +7 -6
- package/dist/worker/console/operator-actions.js +785 -85
- package/dist/worker/console/prd-intake-bridge.js +393 -0
- package/dist/worker/console/recovery-cta.js +35 -6
- package/dist/worker/console/server.js +8 -15
- package/dist/worker/console/static/assets/index-BQkhJpV8.css +1 -0
- package/dist/worker/console/static/assets/index-BpuHmlSP.js +29 -0
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +1 -0
- package/dist/worker/console/static-src/app/usePrdImport.js +2 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +24 -1
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +19 -1
- package/dist/worker/console/static-src/app/useTaskWizard.js +57 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +45 -8
- package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
- package/dist/worker/console/static-src/operator-chat/refs.js +9 -0
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
- package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
- package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
- package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +37 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +125 -22
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +215 -184
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +49 -5
- package/dist/worker/console/static-src/operator-chat/useComposer.js +17 -0
- package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +225 -74
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
- package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
- package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +31 -0
- package/dist/worker/delivery/final-verification.js +13 -5
- package/dist/worker/delivery/package.js +31 -19
- package/dist/worker/delivery/verification-bundle.js +6 -4
- package/dist/worker/observability/read-model.js +3 -0
- package/dist/worker/observe/static/operator-chrome.css +5 -2
- package/dist/worker/observe/static/operator-chrome.js +6 -1
- package/dist/worker/observe/static/styles.css +39 -9
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +33 -462
- package/dist/workflows/dag/backend-test-case-manifest.js +0 -4
- package/dist/workflows/dag/backend-test-markdown-workflow.js +1 -25
- package/dist/workflows/dag/backend-test-module-stem.js +0 -5
- package/dist/workflows/dag/backend-test-pytest-collection.js +24 -345
- package/dist/workflows/dag/backend-test-scenario-param.js +82 -269
- package/dist/workflows/dag/backend-test-writer-completeness.js +16 -47
- package/dist/workflows/dag/dynamic-runtime/map.js +8 -24
- package/dist/workflows/dag/failure-category.js +3 -0
- package/dist/workflows/dag/frontend-worktree-diff.js +12 -27
- package/dist/workflows/dag/init-hybrid.js +49 -55
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/retry-policy.js +44 -11
- package/dist/workflows/dag/runner.js +6 -0
- package/dist/workflows/dag/scheduler.js +49 -1
- package/dist/workflows/dag/types.js +0 -7
- package/dist/workflows/dag/validate.js +16 -2
- package/dist/workflows/dag/workspace-checkpoint.js +8 -27
- package/docs/templates/agent-dag.schema.json +4 -4
- package/docs/templates/backend-test-dag.json +29 -32
- package/harness.json +1 -1
- package/package.json +1 -1
- package/skills/local-jacoco-coverage/SKILL.md +281 -0
- package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
- package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
- package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
- package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
- package/skills/loop-agent/references/command-reference.md +3 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +13 -0
- package/skills/loop-agent/references/task-workflow.md +4 -0
- package/dist/worker/console/chat/instruction-skills.js +0 -217
- package/dist/worker/console/static/assets/index-CnUXAqxG.css +0 -1
- package/dist/worker/console/static/assets/index-CteJFFL2.js +0 -29
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
const HEADING_ALIASES = {
|
|
2
|
-
objective: new Set([
|
|
3
|
-
|
|
2
|
+
objective: new Set([
|
|
3
|
+
"目标",
|
|
4
|
+
"objective",
|
|
5
|
+
"问题与目标",
|
|
6
|
+
"业务目标",
|
|
7
|
+
"需求概述",
|
|
8
|
+
]),
|
|
9
|
+
scope: new Set([
|
|
10
|
+
"范围",
|
|
11
|
+
"scope",
|
|
12
|
+
"in scope",
|
|
13
|
+
"需求范围",
|
|
14
|
+
"已确认范围",
|
|
15
|
+
"已确认需求",
|
|
16
|
+
]),
|
|
4
17
|
nonGoals: new Set(["非目标", "out of scope", "non-goals", "non goals"]),
|
|
5
18
|
acceptance: new Set(["验收标准", "acceptance criteria", "acceptance"]),
|
|
6
19
|
constraints: new Set(["约束", "constraints", "不变式", "invariants"]),
|
|
@@ -8,6 +21,8 @@ const HEADING_ALIASES = {
|
|
|
8
21
|
assumptions: new Set(["假设", "assumptions"]),
|
|
9
22
|
};
|
|
10
23
|
const OBJECTIVE_MAX_CHARS = 1200;
|
|
24
|
+
/** Product Requirement V3/V4 style embedded AC heading, e.g. AC-FE-001 / AC-BE-12 / AC-001. */
|
|
25
|
+
const EMBEDDED_AC_HEADING = /^(AC(?:[-_](?:FE|BE|UI|API))?[-_]?\d+(?:[-_]\d+)?)\s*[::]?\s*(.*)$/iu;
|
|
11
26
|
function stripFencedAndComments(text) {
|
|
12
27
|
// Remove fenced code blocks and HTML comments so headings inside them are ignored.
|
|
13
28
|
return text
|
|
@@ -18,10 +33,28 @@ function stripFencedAndComments(text) {
|
|
|
18
33
|
function normalizeHeadingText(raw) {
|
|
19
34
|
return raw
|
|
20
35
|
.trim()
|
|
36
|
+
// Strip leading section numbers: "2. 业务目标" / "3.1 已确认范围" / "3.2、非目标"
|
|
37
|
+
.replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "")
|
|
21
38
|
.toLowerCase()
|
|
22
39
|
.replace(/[::]\s*$/u, "")
|
|
23
40
|
.replace(/\s+/g, " ");
|
|
24
41
|
}
|
|
42
|
+
function normalizeAcceptanceId(raw) {
|
|
43
|
+
return raw
|
|
44
|
+
.trim()
|
|
45
|
+
.toUpperCase()
|
|
46
|
+
.replace(/_/g, "-")
|
|
47
|
+
.replace(/^(AC)(FE|BE|UI|API)(?=-|\d)/u, "$1-$2");
|
|
48
|
+
}
|
|
49
|
+
function parseEmbeddedAcHeading(headingText) {
|
|
50
|
+
const normalized = headingText.trim().replace(/^(?:\d+(?:\.\d+)*[..、]?\s*)+/u, "");
|
|
51
|
+
const match = EMBEDDED_AC_HEADING.exec(normalized);
|
|
52
|
+
if (!match)
|
|
53
|
+
return null;
|
|
54
|
+
const id = normalizeAcceptanceId(match[1]);
|
|
55
|
+
const text = match[2]?.trim() || id;
|
|
56
|
+
return { id, text };
|
|
57
|
+
}
|
|
25
58
|
function classifyHeading(text) {
|
|
26
59
|
const normalized = normalizeHeadingText(text);
|
|
27
60
|
for (const [key, aliases] of Object.entries(HEADING_ALIASES)) {
|
|
@@ -48,14 +81,17 @@ function parseAcceptanceLine(line, nextId) {
|
|
|
48
81
|
const body = (checklist?.[1] ?? bullet?.[1] ?? line.trim()).trim();
|
|
49
82
|
if (!body)
|
|
50
83
|
return null;
|
|
84
|
+
const embedded = parseEmbeddedAcHeading(body);
|
|
85
|
+
if (embedded)
|
|
86
|
+
return embedded;
|
|
51
87
|
const withId = /^(AC[-_]?\d+)\s*[::]\s*(.+)$/iu.exec(body);
|
|
52
88
|
if (withId) {
|
|
53
|
-
return { id: withId[1]
|
|
89
|
+
return { id: normalizeAcceptanceId(withId[1]), text: withId[2].trim() };
|
|
54
90
|
}
|
|
55
91
|
const bareAc = /^(AC[-_]?\d+)\s+(.+)$/iu.exec(body);
|
|
56
92
|
if (bareAc) {
|
|
57
93
|
return {
|
|
58
|
-
id: bareAc[1]
|
|
94
|
+
id: normalizeAcceptanceId(bareAc[1]),
|
|
59
95
|
text: bareAc[2].trim(),
|
|
60
96
|
};
|
|
61
97
|
}
|
|
@@ -114,9 +150,27 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
114
150
|
const sections = [];
|
|
115
151
|
let current = { key: "other", lines: [] };
|
|
116
152
|
sections.push(current);
|
|
153
|
+
let acCounter = options?.acCounterStart ?? 1;
|
|
154
|
+
const nextId = () => {
|
|
155
|
+
const id = `AC-${String(acCounter).padStart(3, "0")}`;
|
|
156
|
+
acCounter += 1;
|
|
157
|
+
return id;
|
|
158
|
+
};
|
|
117
159
|
for (const line of lines) {
|
|
118
160
|
const heading = isAtxHeading(line);
|
|
119
161
|
if (heading) {
|
|
162
|
+
// Product Requirement V3/V4 embeds formal AC as headings (#### AC-FE-001 ...).
|
|
163
|
+
// Capture the heading as a single AC; GWT body lists under it stay non-AC.
|
|
164
|
+
const embeddedAc = parseEmbeddedAcHeading(heading.text);
|
|
165
|
+
if (embeddedAc) {
|
|
166
|
+
sections.push({
|
|
167
|
+
key: "acceptance",
|
|
168
|
+
lines: [`- ${embeddedAc.id}: ${embeddedAc.text}`],
|
|
169
|
+
});
|
|
170
|
+
current = { key: "other", lines: [] };
|
|
171
|
+
sections.push(current);
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
120
174
|
const key = classifyHeading(heading.text);
|
|
121
175
|
current = { key, lines: [] };
|
|
122
176
|
sections.push(current);
|
|
@@ -124,12 +178,6 @@ export function extractRequirementFactsFromMarkdown(markdown, options) {
|
|
|
124
178
|
}
|
|
125
179
|
current.lines.push(line);
|
|
126
180
|
}
|
|
127
|
-
let acCounter = options?.acCounterStart ?? 1;
|
|
128
|
-
const nextId = () => {
|
|
129
|
-
const id = `AC-${String(acCounter).padStart(3, "0")}`;
|
|
130
|
-
acCounter += 1;
|
|
131
|
-
return id;
|
|
132
|
-
};
|
|
133
181
|
let objective;
|
|
134
182
|
const scope = [];
|
|
135
183
|
const nonGoals = [];
|
|
@@ -6,6 +6,7 @@ import { getTaskContractPaths } from "../contract/paths.js";
|
|
|
6
6
|
import { loadTaskConfig } from "../runtime.js";
|
|
7
7
|
import { buildPrepareDraft } from "./build-draft.js";
|
|
8
8
|
import { hasBlockingGaps, hasBlockingRisks, isFreshLegacyUnversioned, listPrepareGaps, } from "./completeness.js";
|
|
9
|
+
import { detectProductArtifactMeta, isIntakeSoftGapCode, } from "./artifact-meta.js";
|
|
9
10
|
import { extractRequirementFactsFromMarkdown, fillMissingRequirementFacts, mergeRequirementFacts, } from "./parse-intent.js";
|
|
10
11
|
import { draftReferencesFromManifest, validateImportedPrdReferences, } from "./reference-integrity.js";
|
|
11
12
|
import { readSourceManifest } from "../source-references.js";
|
|
@@ -131,31 +132,139 @@ function mutationOutcome(code) {
|
|
|
131
132
|
}
|
|
132
133
|
function buildNextSteps(input) {
|
|
133
134
|
const next = [];
|
|
135
|
+
const codes = new Set(input.gaps.map((g) => g.code));
|
|
134
136
|
if (!input.hasImportedPrd) {
|
|
135
|
-
next.push(`loop-agent
|
|
137
|
+
next.push(`loop-agent task advance ${input.taskId} --prd <product-requirement.md> --allowed-path <glob> --json`);
|
|
136
138
|
}
|
|
137
|
-
if (
|
|
138
|
-
next.push("
|
|
139
|
+
if (codes.has("PRODUCT_ANALYSIS_NOT_EXECUTABLE")) {
|
|
140
|
+
next.push("使用同目录 product-requirement.md 作为 --prd(role=requirement);product-analysis.md 仅作引用(--role analysis)");
|
|
141
|
+
next.push("禁止改写原始 product-analysis / product-requirement 产物,禁止在原 PRD 目录生成兼容 Markdown 或 reviewed-draft.json");
|
|
139
142
|
}
|
|
140
|
-
if (
|
|
141
|
-
next.push("
|
|
143
|
+
if (codes.has("PRODUCT_CLARIFICATION_NOT_EXECUTABLE")) {
|
|
144
|
+
next.push("requirement-clarification.md 是决策追溯,不是可执行 PRD;请导入 complete 的 product-requirement.md");
|
|
145
|
+
}
|
|
146
|
+
if (codes.has("PRODUCT_REQUIREMENT_PENDING")) {
|
|
147
|
+
next.push("先完成 analyze-product-requirements 澄清并将 product-requirement.md 标为 complete,再 task advance --prd");
|
|
148
|
+
}
|
|
149
|
+
if (codes.has("EMPTY_ALLOWED_PATHS")) {
|
|
150
|
+
next.push("补 --allowed-path <glob>(工程边界必须显式给出)");
|
|
151
|
+
}
|
|
152
|
+
if (codes.has("EMPTY_ACCEPTANCE") || codes.has("EMPTY_OBJECTIVE")) {
|
|
153
|
+
next.push("PRD 真缺验收标准/目标时:补完整 product-requirement(Given/When/Then 或 AC 标题),或显式 --from-text;不要自动生成 sidecar draft JSON");
|
|
154
|
+
}
|
|
155
|
+
if (codes.has("SEMANTIC_INTAKE_RECOMMENDED")) {
|
|
156
|
+
next.push("文档语义完整但结构非标准:整理为 product-requirement 或标准「目标/范围/非目标/验收标准」Markdown;不要在原 PRD 目录生成兼容 md/json");
|
|
157
|
+
}
|
|
158
|
+
if (codes.has("SEMANTIC_INTAKE_FAILED") || codes.has("SEMANTIC_INTAKE_INVALID_OUTPUT")) {
|
|
159
|
+
next.push("semantic intake 未能产出可接受 draft:补全 product-requirement 或标准验收标准后重试;禁止在原 PRD 目录生成 sidecar JSON");
|
|
142
160
|
}
|
|
143
161
|
if (input.mode === "dry-run" && input.ok) {
|
|
144
|
-
next.push(`loop-agent task
|
|
162
|
+
next.push(`loop-agent task advance ${input.taskId} --allowed-path <glob> --json`);
|
|
145
163
|
}
|
|
146
164
|
if (input.ok && input.mode === "apply") {
|
|
147
165
|
next.push(`loop-agent task status ${input.taskId} --json`);
|
|
148
166
|
next.push(`loop-agent task advance ${input.taskId} --profile auto --json`);
|
|
149
167
|
next.push("审查 DAG writeSet 后再 task advance --approve-gate ...");
|
|
150
168
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
169
|
+
if (codes.has("DIRTY_SOURCE") ||
|
|
170
|
+
codes.has("MANAGED_PROJECTION_INCOMPLETE")) {
|
|
171
|
+
next.push(`高级恢复(仅人工 reviewed draft):loop-agent task advance ${input.taskId} --from-draft <reviewed-draft.json> --json`);
|
|
172
|
+
next.push("--from-draft 不是 EMPTY_ACCEPTANCE 的默认恢复路径;禁止在原 PRD 目录自动生成 draft JSON");
|
|
153
173
|
}
|
|
154
|
-
if (
|
|
174
|
+
if (codes.has("TRANSACTION_INCOMPLETE")) {
|
|
155
175
|
next.push(`loop-agent task advance ${input.taskId} --json`);
|
|
156
176
|
}
|
|
157
177
|
return next;
|
|
158
178
|
}
|
|
179
|
+
function appendProductArtifactGaps(input) {
|
|
180
|
+
if (!input.sourceIntegrity)
|
|
181
|
+
return;
|
|
182
|
+
const docs = input.sourceIntegrity.documents.filter((d) => d.content);
|
|
183
|
+
if (docs.length === 0)
|
|
184
|
+
return;
|
|
185
|
+
let sawAnalysisOnly = false;
|
|
186
|
+
let sawClarificationOnly = false;
|
|
187
|
+
let sawPendingRequirement = false;
|
|
188
|
+
let sawExecutableRequirement = false;
|
|
189
|
+
let sawUnknownNarrative = false;
|
|
190
|
+
for (const doc of docs) {
|
|
191
|
+
const meta = detectProductArtifactMeta(doc.content);
|
|
192
|
+
if (meta.artifactType === "product-analysis") {
|
|
193
|
+
sawAnalysisOnly = true;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (meta.artifactType === "requirement-clarification") {
|
|
197
|
+
sawClarificationOnly = true;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (meta.artifactType === "product-requirement") {
|
|
201
|
+
if (meta.requirementStatus &&
|
|
202
|
+
meta.requirementStatus.trim().toLowerCase() === "pending") {
|
|
203
|
+
sawPendingRequirement = true;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
sawExecutableRequirement = true;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (doc.role === "requirement" &&
|
|
211
|
+
meta.artifactType === "unknown" &&
|
|
212
|
+
!input.hasAcceptance) {
|
|
213
|
+
sawUnknownNarrative = true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const hasRequirementRole = input.sourceIntegrity.parseableDocuments.some((d) => d.role === "requirement" || d.role === "acceptance");
|
|
217
|
+
if (!hasRequirementRole && sawAnalysisOnly) {
|
|
218
|
+
input.gaps.push({
|
|
219
|
+
code: "PRODUCT_ANALYSIS_NOT_EXECUTABLE",
|
|
220
|
+
level: "blocking",
|
|
221
|
+
field: "acceptanceCriteria",
|
|
222
|
+
message: "imported document is product-analysis (no formal AC by design); use product-requirement.md as --prd requirement",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
if (!hasRequirementRole &&
|
|
226
|
+
sawClarificationOnly &&
|
|
227
|
+
!sawExecutableRequirement) {
|
|
228
|
+
input.gaps.push({
|
|
229
|
+
code: "PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
|
|
230
|
+
level: "blocking",
|
|
231
|
+
field: "acceptanceCriteria",
|
|
232
|
+
message: "imported document is requirement-clarification (decision log only); use complete product-requirement.md",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (sawPendingRequirement && !input.hasAcceptance) {
|
|
236
|
+
input.gaps.push({
|
|
237
|
+
code: "PRODUCT_REQUIREMENT_PENDING",
|
|
238
|
+
level: "blocking",
|
|
239
|
+
field: "acceptanceCriteria",
|
|
240
|
+
message: "product-requirement is still pending clarification; complete analyze-product-requirements before task advance",
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
if (sawUnknownNarrative &&
|
|
244
|
+
!input.hasAcceptance &&
|
|
245
|
+
!sawPendingRequirement &&
|
|
246
|
+
!sawAnalysisOnly) {
|
|
247
|
+
input.gaps.push({
|
|
248
|
+
code: "SEMANTIC_INTAKE_RECOMMENDED",
|
|
249
|
+
level: "warning",
|
|
250
|
+
message: "requirement body lacks structured AC headings; use product-requirement or standard Markdown sections — do not auto-write sidecar drafts",
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
const precise = new Set([
|
|
254
|
+
"PRODUCT_ANALYSIS_NOT_EXECUTABLE",
|
|
255
|
+
"PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
|
|
256
|
+
"PRODUCT_REQUIREMENT_PENDING",
|
|
257
|
+
]);
|
|
258
|
+
if (input.gaps.some((g) => precise.has(g.code))) {
|
|
259
|
+
for (const gap of input.gaps) {
|
|
260
|
+
if (gap.code === "EMPTY_ACCEPTANCE") {
|
|
261
|
+
gap.level = "warning";
|
|
262
|
+
gap.message =
|
|
263
|
+
"acceptance criteria empty (superseded by product-artifact diagnostic)";
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
159
268
|
function emptyObservedHashPlaceholder() {
|
|
160
269
|
return "0".repeat(64);
|
|
161
270
|
}
|
|
@@ -222,7 +331,19 @@ export async function prepareTaskSource(input) {
|
|
|
222
331
|
constraintsPath: contractPaths.constraintsPath,
|
|
223
332
|
});
|
|
224
333
|
if (projection.ok) {
|
|
225
|
-
baseDraft =
|
|
334
|
+
baseDraft = input.intent.replaceManagedRequirement
|
|
335
|
+
? {
|
|
336
|
+
...projection.draft,
|
|
337
|
+
requirement: {
|
|
338
|
+
objective: "",
|
|
339
|
+
scope: [],
|
|
340
|
+
nonGoals: [],
|
|
341
|
+
acceptanceCriteria: [],
|
|
342
|
+
},
|
|
343
|
+
openQuestions: [],
|
|
344
|
+
assumptions: [],
|
|
345
|
+
}
|
|
346
|
+
: projection.draft;
|
|
226
347
|
}
|
|
227
348
|
else {
|
|
228
349
|
managedProjectionError = projection.message;
|
|
@@ -287,7 +408,30 @@ export async function prepareTaskSource(input) {
|
|
|
287
408
|
taskId,
|
|
288
409
|
requireParseableRequirement: false,
|
|
289
410
|
});
|
|
290
|
-
|
|
411
|
+
const requestedPaths = new Set((input.intent.importedPrdPaths ?? [])
|
|
412
|
+
.map((value) => value.replace(/\\/g, "/").trim())
|
|
413
|
+
.filter(Boolean));
|
|
414
|
+
if (requestedPaths.size > 0) {
|
|
415
|
+
const selectedDocuments = sourceIntegrity.documents.filter((document) => requestedPaths.has(document.materializedPath.replace(/\\/g, "/")));
|
|
416
|
+
const selectedPaths = new Set(selectedDocuments.map((document) => document.materializedPath.replace(/\\/g, "/")));
|
|
417
|
+
for (const requestedPath of requestedPaths) {
|
|
418
|
+
if (selectedPaths.has(requestedPath))
|
|
419
|
+
continue;
|
|
420
|
+
sourceIntegrity.issues.push({
|
|
421
|
+
code: "REFERENCE_NOT_IN_MANIFEST",
|
|
422
|
+
level: "blocking",
|
|
423
|
+
message: `current import batch reference not found in source-manifest: ${requestedPath}`,
|
|
424
|
+
path: requestedPath,
|
|
425
|
+
});
|
|
426
|
+
sourceIntegrity.ok = false;
|
|
427
|
+
}
|
|
428
|
+
sourceIntegrity = {
|
|
429
|
+
...sourceIntegrity,
|
|
430
|
+
documents: selectedDocuments,
|
|
431
|
+
parseableDocuments: selectedDocuments.filter((document) => document.parseable),
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
hasImportedPrd = sourceIntegrity.documents.length > 0;
|
|
291
435
|
if (sourceIntegrity.ok) {
|
|
292
436
|
references = draftReferencesFromManifest(sourceIntegrity.documents);
|
|
293
437
|
let acStart = 1;
|
|
@@ -389,13 +533,48 @@ export async function prepareTaskSource(input) {
|
|
|
389
533
|
message: "no parseable requirement document and no --ac / supplemental text; import a requirement-role PRD or pass flags",
|
|
390
534
|
});
|
|
391
535
|
}
|
|
536
|
+
// Product Analysis / pending Product Requirement diagnostics (precise gaps).
|
|
537
|
+
if (input.intent.kind === "facts" && input.intent.useImportedPrd) {
|
|
538
|
+
appendProductArtifactGaps({
|
|
539
|
+
gaps,
|
|
540
|
+
sourceIntegrity,
|
|
541
|
+
hasAcceptance: built.draft.requirement.acceptanceCriteria.length > 0,
|
|
542
|
+
});
|
|
543
|
+
}
|
|
392
544
|
const validation = await validateDraftForTask({
|
|
393
545
|
repoRoot,
|
|
394
546
|
taskId,
|
|
395
547
|
draft: built.draft,
|
|
396
548
|
});
|
|
397
549
|
if (!validation.ok) {
|
|
550
|
+
const productArtifactBlocking = gaps.some((gap) => [
|
|
551
|
+
"PRODUCT_ANALYSIS_NOT_EXECUTABLE",
|
|
552
|
+
"PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
|
|
553
|
+
"PRODUCT_REQUIREMENT_PENDING",
|
|
554
|
+
].includes(gap.code));
|
|
555
|
+
const hasEmptyAcceptance = gaps.some((gap) => gap.code === "EMPTY_ACCEPTANCE");
|
|
398
556
|
for (const message of validation.errors) {
|
|
557
|
+
const isAcceptanceSchema = /acceptance criterion|acceptanceCriteria/i.test(message);
|
|
558
|
+
// Prefer product-artifact diagnostics over generic schema AC errors.
|
|
559
|
+
if (productArtifactBlocking && isAcceptanceSchema) {
|
|
560
|
+
gaps.push({
|
|
561
|
+
code: "DRAFT_VALIDATION",
|
|
562
|
+
level: "warning",
|
|
563
|
+
message: `${message} (superseded by product-artifact diagnostic)`,
|
|
564
|
+
});
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
// Soft-intake path: completeness already has EMPTY_ACCEPTANCE (soft).
|
|
568
|
+
// Do not add a hard DRAFT_VALIDATION twin that flips softIntake=false
|
|
569
|
+
// and blocks one-shot Semantic Intake for free-form PRDs.
|
|
570
|
+
if (hasEmptyAcceptance && isAcceptanceSchema) {
|
|
571
|
+
gaps.push({
|
|
572
|
+
code: "DRAFT_VALIDATION",
|
|
573
|
+
level: "warning",
|
|
574
|
+
message: `${message} (soft intake; EMPTY_ACCEPTANCE primary)`,
|
|
575
|
+
});
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
399
578
|
gaps.push({
|
|
400
579
|
code: "DRAFT_VALIDATION",
|
|
401
580
|
level: "blocking",
|
|
@@ -428,13 +607,32 @@ export async function prepareTaskSource(input) {
|
|
|
428
607
|
.filter((gap) => gap.level === "blocking")
|
|
429
608
|
.map((gap) => gap.code);
|
|
430
609
|
const transactionIncomplete = blockingCodes.includes("TRANSACTION_INCOMPLETE");
|
|
610
|
+
const productArtifactCodes = [
|
|
611
|
+
"PRODUCT_ANALYSIS_NOT_EXECUTABLE",
|
|
612
|
+
"PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
|
|
613
|
+
"PRODUCT_REQUIREMENT_PENDING",
|
|
614
|
+
"PRODUCT_ARTIFACT_NOT_EXECUTABLE",
|
|
615
|
+
];
|
|
616
|
+
const primaryBlockingCode = blockingCodes.find((code) => productArtifactCodes.includes(code)) ??
|
|
617
|
+
blockingCodes.find((code) => code !== "EMPTY_ACCEPTANCE") ??
|
|
618
|
+
blockingCodes[0] ??
|
|
619
|
+
"INVALID_INPUT";
|
|
620
|
+
// Soft intake only when PRD was imported (or product-artifact diagnostics apply).
|
|
621
|
+
// Bare missing flags without import stay hard-invalid.
|
|
622
|
+
const softIntakeOnly = blocking &&
|
|
623
|
+
blockingCodes.length > 0 &&
|
|
624
|
+
blockingCodes.every((code) => isIntakeSoftGapCode(code)) &&
|
|
625
|
+
(hasImportedPrd ||
|
|
626
|
+
blockingCodes.some((code) => productArtifactCodes.includes(code)));
|
|
431
627
|
if (input.mode !== "apply") {
|
|
432
628
|
return {
|
|
433
629
|
ok: !blocking,
|
|
434
630
|
outcome: blocking
|
|
435
631
|
? transactionIncomplete
|
|
436
632
|
? "needs-reconcile"
|
|
437
|
-
:
|
|
633
|
+
: softIntakeOnly
|
|
634
|
+
? "blocked"
|
|
635
|
+
: "invalid"
|
|
438
636
|
: "succeeded",
|
|
439
637
|
mode: "dry-run",
|
|
440
638
|
taskId,
|
|
@@ -452,9 +650,13 @@ export async function prepareTaskSource(input) {
|
|
|
452
650
|
error: {
|
|
453
651
|
code: transactionIncomplete
|
|
454
652
|
? "TRANSACTION_INCOMPLETE"
|
|
455
|
-
:
|
|
456
|
-
|
|
457
|
-
|
|
653
|
+
: softIntakeOnly
|
|
654
|
+
? primaryBlockingCode
|
|
655
|
+
: "INVALID_INPUT",
|
|
656
|
+
message: softIntakeOnly
|
|
657
|
+
? "PRD imported or facts present but intake incomplete; DAG not generated"
|
|
658
|
+
: "task source draft has blocking gaps",
|
|
659
|
+
details: { gaps: blockingCodes, softIntake: softIntakeOnly },
|
|
458
660
|
},
|
|
459
661
|
}
|
|
460
662
|
: {}),
|
|
@@ -486,7 +688,9 @@ export async function prepareTaskSource(input) {
|
|
|
486
688
|
if (blocking) {
|
|
487
689
|
const outcome = blockingCodes.includes("DIRTY_SOURCE")
|
|
488
690
|
? "blocked"
|
|
489
|
-
:
|
|
691
|
+
: softIntakeOnly
|
|
692
|
+
? "blocked"
|
|
693
|
+
: "invalid";
|
|
490
694
|
return {
|
|
491
695
|
ok: false,
|
|
492
696
|
outcome,
|
|
@@ -502,10 +706,17 @@ export async function prepareTaskSource(input) {
|
|
|
502
706
|
contractStatus: state.effectiveStatus,
|
|
503
707
|
warnings,
|
|
504
708
|
error: {
|
|
505
|
-
code:
|
|
506
|
-
|
|
709
|
+
code: blockingCodes.includes("DIRTY_SOURCE")
|
|
710
|
+
? "DIRTY_SOURCE"
|
|
711
|
+
: softIntakeOnly
|
|
712
|
+
? primaryBlockingCode
|
|
713
|
+
: "INVALID_INPUT",
|
|
714
|
+
message: softIntakeOnly
|
|
715
|
+
? "PRD imported or facts present but intake incomplete; DAG not generated"
|
|
716
|
+
: "task source draft has blocking gaps",
|
|
507
717
|
details: {
|
|
508
718
|
gaps: blockingCodes,
|
|
719
|
+
softIntake: softIntakeOnly,
|
|
509
720
|
},
|
|
510
721
|
},
|
|
511
722
|
};
|
|
@@ -5,7 +5,16 @@ import { getTaskPaths } from "../runtime.js";
|
|
|
5
5
|
import { readSourceManifest, } from "../source-references.js";
|
|
6
6
|
import { PREPARE_MAX_FILE_BYTES, PREPARE_MAX_PARSE_DOCS, PREPARE_MAX_TOTAL_BYTES, } from "./types.js";
|
|
7
7
|
const PARSEABLE_EXTENSIONS = new Set([".md", ".markdown", ".txt"]);
|
|
8
|
+
/** Roles that contribute requirement facts (objective/AC/scope). */
|
|
8
9
|
const PARSEABLE_ROLES = new Set(["requirement", "acceptance"]);
|
|
10
|
+
/** Roles whose body is loaded for artifact meta / diagnostics (not fact extraction). */
|
|
11
|
+
const CONTENT_LOAD_ROLES = new Set([
|
|
12
|
+
"requirement",
|
|
13
|
+
"acceptance",
|
|
14
|
+
"analysis",
|
|
15
|
+
"clarification",
|
|
16
|
+
"design",
|
|
17
|
+
]);
|
|
9
18
|
function isSafeRelativeReferencePath(materializedPath) {
|
|
10
19
|
const normalized = materializedPath.replace(/\\/g, "/").trim();
|
|
11
20
|
if (!normalized)
|
|
@@ -52,6 +61,12 @@ function isParseableDoc(doc) {
|
|
|
52
61
|
return false;
|
|
53
62
|
return PARSEABLE_ROLES.has(doc.role);
|
|
54
63
|
}
|
|
64
|
+
function shouldLoadContent(doc) {
|
|
65
|
+
const ext = path.extname(doc.materializedPath).toLowerCase();
|
|
66
|
+
if (!PARSEABLE_EXTENSIONS.has(ext))
|
|
67
|
+
return false;
|
|
68
|
+
return CONTENT_LOAD_ROLES.has(doc.role) || PARSEABLE_ROLES.has(doc.role);
|
|
69
|
+
}
|
|
55
70
|
/**
|
|
56
71
|
* Validate source-manifest + reference files before deriving a draft.
|
|
57
72
|
* Fail-closed on taskId/path/hash/symlink/size issues.
|
|
@@ -170,6 +185,7 @@ export async function validateImportedPrdReferences(input) {
|
|
|
170
185
|
continue;
|
|
171
186
|
}
|
|
172
187
|
const parseable = isParseableDoc(doc);
|
|
188
|
+
const loadContent = shouldLoadContent(doc);
|
|
173
189
|
if (parseable) {
|
|
174
190
|
parseableCount += 1;
|
|
175
191
|
if (parseableCount > PREPARE_MAX_PARSE_DOCS) {
|
|
@@ -183,7 +199,7 @@ export async function validateImportedPrdReferences(input) {
|
|
|
183
199
|
}
|
|
184
200
|
let hashResult;
|
|
185
201
|
try {
|
|
186
|
-
hashResult = await streamSha256AndBytes(absolutePath,
|
|
202
|
+
hashResult = await streamSha256AndBytes(absolutePath, loadContent ? PREPARE_MAX_FILE_BYTES : undefined);
|
|
187
203
|
}
|
|
188
204
|
catch (error) {
|
|
189
205
|
if (error &&
|
|
@@ -223,7 +239,7 @@ export async function validateImportedPrdReferences(input) {
|
|
|
223
239
|
});
|
|
224
240
|
}
|
|
225
241
|
let content;
|
|
226
|
-
if (
|
|
242
|
+
if (loadContent) {
|
|
227
243
|
totalParseBytes += hashResult.bytes;
|
|
228
244
|
if (totalParseBytes > PREPARE_MAX_TOTAL_BYTES) {
|
|
229
245
|
issues.push({
|