@tea-agent/loop-agent 0.33.7-beta.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +38 -17
  2. package/dist/application/task-lifecycle/advance.js +254 -4
  3. package/dist/application/task-lifecycle/gates.js +50 -0
  4. package/dist/application/task-lifecycle/observe.js +11 -2
  5. package/dist/commands/init-upgrade.js +32 -1
  6. package/dist/commands/init.js +94 -3
  7. package/dist/executors/shell-executor.js +4 -91
  8. package/dist/executors/shell-write-guard.js +26 -8
  9. package/dist/shared/operator/capabilities.js +72 -42
  10. package/dist/shared/resilient-git.js +133 -0
  11. package/dist/task/source-prepare/artifact-meta.js +137 -0
  12. package/dist/task/source-prepare/index.js +2 -0
  13. package/dist/task/source-prepare/parse-intent.js +58 -10
  14. package/dist/task/source-prepare/prepare.js +180 -16
  15. package/dist/task/source-prepare/reference-integrity.js +18 -2
  16. package/dist/task/source-prepare/semantic-intake.js +404 -0
  17. package/dist/worker/console/app-data.js +2 -0
  18. package/dist/worker/console/chat/chat-event-store.js +190 -25
  19. package/dist/worker/console/chat/model-resolver.js +17 -0
  20. package/dist/worker/console/chat/pi-console-config.js +250 -32
  21. package/dist/worker/console/chat/pi-runtime.js +1007 -188
  22. package/dist/worker/console/chat/resource-loader.js +5 -4
  23. package/dist/worker/console/chat/routes.js +495 -157
  24. package/dist/worker/console/chat/runtime-context.js +48 -12
  25. package/dist/worker/console/chat/runtime-selection.js +59 -0
  26. package/dist/worker/console/chat/session-store.js +39 -0
  27. package/dist/worker/console/chat/shortcuts.js +1 -0
  28. package/dist/worker/console/chat/tool-adapter.js +9 -3
  29. package/dist/worker/console/chat/tools.js +5 -1
  30. package/dist/worker/console/dag-execution-receipt.js +380 -0
  31. package/dist/worker/console/operator-actions.js +559 -68
  32. package/dist/worker/console/server.js +8 -15
  33. package/dist/worker/console/static/assets/index-BQkhJpV8.css +1 -0
  34. package/dist/worker/console/static/assets/index-CMHovlqG.js +32 -0
  35. package/dist/worker/console/static/index.html +2 -2
  36. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +45 -8
  37. package/dist/worker/console/static-src/operator-chat/landing-density.js +23 -0
  38. package/dist/worker/console/static-src/operator-chat/refs.js +9 -0
  39. package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +257 -0
  40. package/dist/worker/console/static-src/operator-chat/session-title-watcher.js +128 -0
  41. package/dist/worker/console/static-src/operator-chat/sidebar-split.js +90 -0
  42. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +37 -0
  43. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +125 -22
  44. package/dist/worker/console/static-src/operator-chat/useChatStream.js +215 -184
  45. package/dist/worker/console/static-src/operator-chat/useChatThread.js +49 -5
  46. package/dist/worker/console/static-src/operator-chat/useComposer.js +17 -0
  47. package/dist/worker/console/static-src/operator-chat/useOverlayFocus.js +84 -0
  48. package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +225 -74
  49. package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +196 -0
  50. package/dist/worker/console/static-src/operator-chat/useWorkspaceLayout.js +58 -0
  51. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +31 -0
  52. package/dist/worker/delivery/final-verification.js +13 -5
  53. package/dist/worker/delivery/package.js +31 -19
  54. package/dist/worker/delivery/verification-bundle.js +6 -4
  55. package/dist/worker/observe/static/operator-chrome.css +5 -2
  56. package/dist/worker/observe/static/operator-chrome.js +6 -1
  57. package/dist/worker/observe/static/styles.css +39 -9
  58. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +33 -462
  59. package/dist/workflows/dag/backend-test-case-manifest.js +0 -4
  60. package/dist/workflows/dag/backend-test-markdown-workflow.js +1 -25
  61. package/dist/workflows/dag/backend-test-module-stem.js +0 -5
  62. package/dist/workflows/dag/backend-test-pytest-collection.js +24 -345
  63. package/dist/workflows/dag/backend-test-scenario-param.js +82 -269
  64. package/dist/workflows/dag/backend-test-writer-completeness.js +16 -47
  65. package/dist/workflows/dag/dynamic-runtime/map.js +8 -24
  66. package/dist/workflows/dag/frontend-worktree-diff.js +12 -27
  67. package/dist/workflows/dag/init-hybrid.js +34 -46
  68. package/dist/workflows/dag/types.js +0 -7
  69. package/dist/workflows/dag/workspace-checkpoint.js +8 -27
  70. package/docs/templates/backend-test-dag.json +29 -32
  71. package/harness.json +1 -1
  72. package/package.json +1 -1
  73. package/skills/local-jacoco-coverage/SKILL.md +281 -0
  74. package/skills/local-jacoco-coverage/references/requirement-to-source-mapping.md +85 -0
  75. package/skills/local-jacoco-coverage/references/runtime-alignment.md +106 -0
  76. package/skills/local-jacoco-coverage/scripts/run-coverage-analysis.sh +148 -0
  77. package/skills/local-jacoco-coverage/scripts/start-jacoco-agent.sh +110 -0
  78. package/skills/loop-agent/references/command-reference.md +3 -1
  79. package/skills/loop-agent/references/source-and-plan-practice.md +13 -0
  80. package/skills/loop-agent/references/task-workflow.md +4 -0
  81. package/dist/worker/console/chat/instruction-skills.js +0 -217
  82. package/dist/worker/console/static/assets/index-CnUXAqxG.css +0 -1
  83. package/dist/worker/console/static/assets/index-CteJFFL2.js +0 -29
@@ -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, parseable ? PREPARE_MAX_FILE_BYTES : undefined);
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 (parseable) {
242
+ if (loadContent) {
227
243
  totalParseBytes += hashResult.bytes;
228
244
  if (totalParseBytes > PREPARE_MAX_TOTAL_BYTES) {
229
245
  issues.push({
@@ -0,0 +1,404 @@
1
+ /**
2
+ * P2 Semantic Intake: one-shot, read-only Pi structuring of imported PRDs
3
+ * into TaskContractDraftV1. Does not write source/需求.md directly — caller
4
+ * re-enters prepare with kind:"draft". Engineering paths/verify never come
5
+ * from the model; only from flags / existing task config.
6
+ */
7
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { z } from "zod";
10
+ import { executePiStep } from "../../executors/pi-executor.js";
11
+ import { resolveDagPiModelConfig } from "../../executors/dag-pi-executor.js";
12
+ import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
13
+ import { loadHarnessManifest } from "../../governance/harness.js";
14
+ import { TASK_CONTRACT_DRAFT_SCHEMA_VERSION } from "../contract/constants.js";
15
+ import { getTaskPaths } from "../runtime.js";
16
+ import { detectProductArtifactMeta } from "./artifact-meta.js";
17
+ export const SEMANTIC_INTAKE_ARTIFACT_REL = "artifacts/intake/semantic-draft.json";
18
+ export const SEMANTIC_INTAKE_ATTEMPT_MARKER = "artifacts/intake/semantic-intake-attempted.json";
19
+ const productArtifactBlockers = new Set([
20
+ "PRODUCT_ANALYSIS_NOT_EXECUTABLE",
21
+ "PRODUCT_CLARIFICATION_NOT_EXECUTABLE",
22
+ "PRODUCT_REQUIREMENT_PENDING",
23
+ "PRODUCT_ARTIFACT_NOT_EXECUTABLE",
24
+ ]);
25
+ const requirementDraftSchema = z.object({
26
+ objective: z.string().min(1),
27
+ scope: z.array(z.string()).default([]),
28
+ nonGoals: z.array(z.string()).default([]),
29
+ acceptanceCriteria: z
30
+ .array(z.object({
31
+ id: z.string().min(1),
32
+ text: z.string().min(1),
33
+ provenance: z
34
+ .enum(["explicit", "derived", "inferred"])
35
+ .optional()
36
+ .default("derived"),
37
+ }))
38
+ .min(1),
39
+ openQuestions: z.array(z.string()).optional(),
40
+ assumptions: z.array(z.string()).optional(),
41
+ title: z.string().optional(),
42
+ });
43
+ export function isSemanticIntakeDisabled(env = process.env) {
44
+ const raw = (env.LOOP_AGENT_SEMANTIC_INTAKE ?? "").trim().toLowerCase();
45
+ if (raw === "0" || raw === "false" || raw === "off" || raw === "no") {
46
+ return true;
47
+ }
48
+ return false;
49
+ }
50
+ /**
51
+ * When deterministic parse leaves soft intake gaps that look like "structure
52
+ * problem" (not product-analysis / pending), allow one Pi structuring pass.
53
+ */
54
+ export function evaluateSemanticIntakeEligibility(input) {
55
+ if (input.disabled) {
56
+ return { eligible: false, reason: "semantic intake disabled" };
57
+ }
58
+ if (input.alreadyAttempted && !input.force) {
59
+ return { eligible: false, reason: "semantic intake already attempted" };
60
+ }
61
+ if (!input.hasImportedPrd && !input.force) {
62
+ return { eligible: false, reason: "no imported PRD" };
63
+ }
64
+ const blocking = input.gaps.filter((g) => g.level === "blocking");
65
+ const codes = new Set(input.gaps.map((g) => g.code));
66
+ const blockingCodes = new Set(blocking.map((g) => g.code));
67
+ for (const code of productArtifactBlockers) {
68
+ if (blockingCodes.has(code)) {
69
+ return {
70
+ eligible: false,
71
+ reason: `product-artifact blocker ${code} requires human product work, not LLM structuring`,
72
+ };
73
+ }
74
+ }
75
+ // Engineering-only gaps are not fixed by semantic intake.
76
+ const onlyEngineering = blocking.length > 0 &&
77
+ blocking.every((g) => g.code === "EMPTY_ALLOWED_PATHS" ||
78
+ g.code === "EMPTY_TASK_KIND" ||
79
+ g.code === "MISSING_FEATURE_ID" ||
80
+ g.code === "PATH_POLICY" ||
81
+ g.code.startsWith("UNSAFE_") ||
82
+ g.code === "ALLOWED_FORBIDDEN_OVERLAP");
83
+ if (onlyEngineering) {
84
+ return {
85
+ eligible: false,
86
+ reason: "only engineering boundary gaps; provide --allowed-path / flags",
87
+ };
88
+ }
89
+ const structural = codes.has("SEMANTIC_INTAKE_RECOMMENDED") ||
90
+ blockingCodes.has("EMPTY_ACCEPTANCE") ||
91
+ blockingCodes.has("EMPTY_OBJECTIVE") ||
92
+ blockingCodes.has("NO_PARSEABLE_REQUIREMENT");
93
+ if (!structural && !input.force) {
94
+ return {
95
+ eligible: false,
96
+ reason: "no structural requirement gaps for semantic intake",
97
+ };
98
+ }
99
+ return { eligible: true, reason: "structural intake gaps with imported sources" };
100
+ }
101
+ export function extractJsonObject(text) {
102
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(text);
103
+ const candidate = fenced?.[1]?.trim() || text.trim();
104
+ const start = candidate.indexOf("{");
105
+ const end = candidate.lastIndexOf("}");
106
+ if (start < 0 || end <= start) {
107
+ throw new Error("no JSON object found in model output");
108
+ }
109
+ try {
110
+ return JSON.parse(candidate.slice(start, end + 1));
111
+ }
112
+ catch (error) {
113
+ const message = error instanceof Error ? error.message : String(error);
114
+ throw new Error(`invalid JSON object in model output: ${message}`);
115
+ }
116
+ }
117
+ export function parseSemanticRequirementDraft(text) {
118
+ const raw = extractJsonObject(text);
119
+ return requirementDraftSchema.parse(raw);
120
+ }
121
+ /** Reject drafts that invent product behavior when only inferred ACs exist. */
122
+ export function assertSemanticDraftAcceptable(draft) {
123
+ const inferredOnly = draft.acceptanceCriteria.length > 0 &&
124
+ draft.acceptanceCriteria.every((ac) => ac.provenance === "inferred");
125
+ if (inferredOnly) {
126
+ throw new Error("semantic intake produced only inferred acceptance criteria; refuse to invent product behavior");
127
+ }
128
+ if (!draft.objective.trim()) {
129
+ throw new Error("semantic intake objective is empty");
130
+ }
131
+ }
132
+ export function buildDraftFromSemanticIntake(input) {
133
+ const allowedPaths = input.flags.allowedPaths?.length
134
+ ? [
135
+ ...(input.existingAllowedPaths ?? []),
136
+ ...input.flags.allowedPaths,
137
+ ]
138
+ : (input.existingAllowedPaths ?? input.flags.allowedPaths ?? []);
139
+ const forbiddenPaths = input.flags.forbiddenPaths?.length
140
+ ? [
141
+ ...(input.existingForbiddenPaths ?? []),
142
+ ...input.flags.forbiddenPaths,
143
+ ]
144
+ : (input.existingForbiddenPaths ?? input.flags.forbiddenPaths ?? []);
145
+ const verifyCommands = input.flags.verifyCommands?.length
146
+ ? input.flags.verifyCommands
147
+ : (input.existingVerify ?? []);
148
+ const draft = {
149
+ schemaVersion: TASK_CONTRACT_DRAFT_SCHEMA_VERSION,
150
+ taskId: input.taskId,
151
+ title: input.semantic.title?.trim() || input.title,
152
+ taskKind: input.flags.taskKind || input.taskKind || "standard",
153
+ requirement: {
154
+ objective: input.semantic.objective.trim(),
155
+ scope: input.semantic.scope.map((s) => s.trim()).filter(Boolean),
156
+ nonGoals: input.semantic.nonGoals.map((s) => s.trim()).filter(Boolean),
157
+ acceptanceCriteria: input.semantic.acceptanceCriteria.map((ac) => ({
158
+ id: ac.id.trim(),
159
+ text: ac.text.trim(),
160
+ })),
161
+ },
162
+ constraints: {
163
+ invariants: input.flags.invariants ?? [],
164
+ allowedPaths: [...new Set(allowedPaths.map((p) => p.trim()).filter(Boolean))],
165
+ forbiddenPaths: [
166
+ ...new Set(forbiddenPaths.map((p) => p.trim()).filter(Boolean)),
167
+ ],
168
+ ...(input.flags.allowedRoots
169
+ ? { allowedRoots: input.flags.allowedRoots }
170
+ : {}),
171
+ },
172
+ verification: {
173
+ commands: verifyCommands
174
+ .filter((c) => c.label && c.command)
175
+ .map((c) => ({
176
+ label: c.label,
177
+ command: c.command,
178
+ ...(c.timeoutMs !== undefined ? { timeoutMs: c.timeoutMs } : {}),
179
+ })),
180
+ },
181
+ };
182
+ if (input.featureId || input.flags.featureId) {
183
+ draft.featureId = input.flags.featureId ?? input.featureId;
184
+ }
185
+ if (input.references?.length)
186
+ draft.references = input.references;
187
+ if (input.semantic.openQuestions?.length) {
188
+ draft.openQuestions = input.semantic.openQuestions;
189
+ }
190
+ if (input.semantic.assumptions?.length) {
191
+ draft.assumptions = input.semantic.assumptions;
192
+ }
193
+ return draft;
194
+ }
195
+ function buildPrompt(input) {
196
+ const bodies = input.documents
197
+ .map((doc, index) => {
198
+ const meta = detectProductArtifactMeta(doc.content);
199
+ const metaLine = meta.recognized
200
+ ? `artifact_type=${meta.artifactType} status=${meta.requirementStatus ?? meta.analysisStatus ?? "n/a"}`
201
+ : "artifact_type=unknown";
202
+ const clipped = doc.content.length > 24_000
203
+ ? `${doc.content.slice(0, 24_000)}\n\n[truncated]`
204
+ : doc.content;
205
+ return [
206
+ `### Document ${index + 1}`,
207
+ `role: ${doc.role}`,
208
+ `path: ${doc.materializedPath}`,
209
+ metaLine,
210
+ "```markdown",
211
+ clipped,
212
+ "```",
213
+ ].join("\n");
214
+ })
215
+ .join("\n\n");
216
+ return [
217
+ "You are a read-only requirement structurer for loop-agent task intake.",
218
+ "Convert imported product documents into a thin requirement draft JSON.",
219
+ "Do NOT invent product behavior that is not supported by the documents.",
220
+ "Do NOT emit engineering fields (allowedPaths, forbiddenPaths, verifyCommands, taskKind).",
221
+ "Do NOT rewrite or suggest editing the original PRD files.",
222
+ "Prefer explicit text; use provenance derived for rephrasing; use inferred only when clearly implied — if you must invent ACs, leave acceptanceCriteria empty so the system can stop for clarification.",
223
+ "Return exactly one JSON object, no markdown prose outside optional fence:",
224
+ JSON.stringify({
225
+ title: "optional string",
226
+ objective: "required string",
227
+ scope: ["bullet"],
228
+ nonGoals: ["bullet"],
229
+ acceptanceCriteria: [
230
+ {
231
+ id: "AC-001",
232
+ text: "observable criterion",
233
+ provenance: "explicit|derived|inferred",
234
+ },
235
+ ],
236
+ openQuestions: ["optional"],
237
+ assumptions: ["optional"],
238
+ }, null, 2),
239
+ `taskId: ${input.taskId}`,
240
+ `fallbackTitle: ${input.title}`,
241
+ "Documents:",
242
+ bodies,
243
+ ].join("\n\n");
244
+ }
245
+ export async function readSemanticIntakeAttempted(repoRoot, taskId) {
246
+ const paths = getTaskPaths(repoRoot, taskId);
247
+ const marker = path.join(paths.taskDir, SEMANTIC_INTAKE_ATTEMPT_MARKER);
248
+ try {
249
+ await readFile(marker, "utf-8");
250
+ return true;
251
+ }
252
+ catch {
253
+ return false;
254
+ }
255
+ }
256
+ async function writeAttemptMarker(repoRoot, taskId, payload) {
257
+ const paths = getTaskPaths(repoRoot, taskId);
258
+ const dir = path.join(paths.taskDir, "artifacts", "intake");
259
+ await mkdir(dir, { recursive: true });
260
+ const marker = path.join(paths.taskDir, SEMANTIC_INTAKE_ATTEMPT_MARKER);
261
+ await writeFile(marker, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
262
+ return marker;
263
+ }
264
+ /**
265
+ * Run at most one semantic intake attempt for a task (caller must also latch
266
+ * per advance invocation). Writes artifacts under taskDir/artifacts/intake/.
267
+ */
268
+ export async function runSemanticIntake(input) {
269
+ const paths = getTaskPaths(input.repoRoot, input.taskId);
270
+ const intakeDir = path.join(paths.taskDir, "artifacts", "intake");
271
+ await mkdir(intakeDir, { recursive: true });
272
+ const usableDocs = input.documents.filter((d) => d.content?.trim() && d.role !== "design");
273
+ if (usableDocs.length === 0) {
274
+ await writeAttemptMarker(input.repoRoot, input.taskId, {
275
+ ok: false,
276
+ code: "SEMANTIC_INTAKE_NO_DOCUMENTS",
277
+ at: new Date().toISOString(),
278
+ });
279
+ return {
280
+ ok: false,
281
+ code: "SEMANTIC_INTAKE_NO_DOCUMENTS",
282
+ message: "no imported document content available for semantic intake",
283
+ };
284
+ }
285
+ // Refuse pure analysis/clarification-only suites (no requirement body).
286
+ const onlyNonExecutable = usableDocs.every((d) => {
287
+ const meta = detectProductArtifactMeta(d.content);
288
+ return (meta.artifactType === "product-analysis" ||
289
+ meta.artifactType === "requirement-clarification" ||
290
+ d.role === "analysis" ||
291
+ d.role === "clarification");
292
+ });
293
+ if (onlyNonExecutable) {
294
+ await writeAttemptMarker(input.repoRoot, input.taskId, {
295
+ ok: false,
296
+ code: "SEMANTIC_INTAKE_REFUSED_NON_EXECUTABLE",
297
+ at: new Date().toISOString(),
298
+ });
299
+ return {
300
+ ok: false,
301
+ code: "SEMANTIC_INTAKE_REFUSED_NON_EXECUTABLE",
302
+ message: "documents are analysis/clarification only; use complete product-requirement.md",
303
+ };
304
+ }
305
+ let assistantText;
306
+ if (input.fixtureAssistantText !== undefined) {
307
+ assistantText = input.fixtureAssistantText;
308
+ }
309
+ else {
310
+ const execute = input.executePi ?? executePiStep;
311
+ const manifest = await loadHarnessManifest(input.repoRoot);
312
+ const models = resolveExecutorModelMatrices(manifest);
313
+ const model = models.pi.MED;
314
+ const attached = usableDocs
315
+ .map((d) => d.absolutePath)
316
+ .filter((p) => Boolean(p));
317
+ const result = await execute({
318
+ attachedFiles: attached,
319
+ modelConfig: resolveDagPiModelConfig(model),
320
+ prompt: buildPrompt({
321
+ taskId: input.taskId,
322
+ title: input.title,
323
+ documents: usableDocs,
324
+ }),
325
+ repoRoot: input.repoRoot,
326
+ step: "analyze",
327
+ toolNames: ["read", "grep", "find", "ls"],
328
+ userMessage: "Structure the imported requirement documents into the required JSON only.",
329
+ validateOutput: (text) => {
330
+ try {
331
+ const parsed = parseSemanticRequirementDraft(text);
332
+ assertSemanticDraftAcceptable(parsed);
333
+ return [];
334
+ }
335
+ catch (error) {
336
+ return [error instanceof Error ? error.message : String(error)];
337
+ }
338
+ },
339
+ });
340
+ if (!result.ok) {
341
+ await writeAttemptMarker(input.repoRoot, input.taskId, {
342
+ ok: false,
343
+ code: "SEMANTIC_INTAKE_PI_FAILED",
344
+ failureCategory: result.failureCategory,
345
+ at: new Date().toISOString(),
346
+ });
347
+ return {
348
+ ok: false,
349
+ code: "SEMANTIC_INTAKE_PI_FAILED",
350
+ message: `semantic intake pi failed: ${result.failureCategory}: ${result.stderr || result.assistantText}`.slice(0, 2000),
351
+ };
352
+ }
353
+ assistantText = result.assistantText;
354
+ }
355
+ try {
356
+ const semantic = parseSemanticRequirementDraft(assistantText);
357
+ assertSemanticDraftAcceptable(semantic);
358
+ const draft = buildDraftFromSemanticIntake({
359
+ taskId: input.taskId,
360
+ title: input.title,
361
+ taskKind: input.taskKind ?? "standard",
362
+ featureId: input.featureId,
363
+ semantic,
364
+ flags: input.flags,
365
+ existingAllowedPaths: input.existingAllowedPaths,
366
+ existingForbiddenPaths: input.existingForbiddenPaths,
367
+ existingVerify: input.existingVerify,
368
+ references: usableDocs.map((d) => ({
369
+ role: d.role,
370
+ ref: d.materializedPath.replace(/\\/g, "/"),
371
+ })),
372
+ });
373
+ const artifactPath = path.join(paths.taskDir, SEMANTIC_INTAKE_ARTIFACT_REL);
374
+ await writeFile(artifactPath, `${JSON.stringify({
375
+ schemaVersion: 1,
376
+ kind: "semantic-intake-draft",
377
+ taskId: input.taskId,
378
+ createdAt: new Date().toISOString(),
379
+ semantic,
380
+ draft,
381
+ }, null, 2)}\n`, "utf-8");
382
+ await writeAttemptMarker(input.repoRoot, input.taskId, {
383
+ ok: true,
384
+ code: "SEMANTIC_INTAKE_OK",
385
+ artifactPath: SEMANTIC_INTAKE_ARTIFACT_REL,
386
+ at: new Date().toISOString(),
387
+ });
388
+ return { ok: true, draft, artifactPath, semantic };
389
+ }
390
+ catch (error) {
391
+ const message = error instanceof Error ? error.message : String(error);
392
+ await writeAttemptMarker(input.repoRoot, input.taskId, {
393
+ ok: false,
394
+ code: "SEMANTIC_INTAKE_INVALID_OUTPUT",
395
+ message,
396
+ at: new Date().toISOString(),
397
+ });
398
+ return {
399
+ ok: false,
400
+ code: "SEMANTIC_INTAKE_INVALID_OUTPUT",
401
+ message,
402
+ };
403
+ }
404
+ }
@@ -36,6 +36,7 @@ export function openConsoleAppData(options) {
36
36
  drafts: path.join(repoNamespace, "drafts"),
37
37
  operations: path.join(repoNamespace, "operations"),
38
38
  confirmations: path.join(repoNamespace, "confirmations"),
39
+ dagReceipts: path.join(repoNamespace, "dag-receipts"),
39
40
  staged: path.join(repoNamespace, "staged"),
40
41
  assessments: path.join(repoNamespace, "assessments"),
41
42
  interviews: path.join(repoNamespace, "interviews"),
@@ -47,6 +48,7 @@ export function openConsoleAppData(options) {
47
48
  paths.drafts,
48
49
  paths.operations,
49
50
  paths.confirmations,
51
+ paths.dagReceipts,
50
52
  paths.staged,
51
53
  paths.assessments,
52
54
  paths.interviews,