vibe-coding-master 0.7.39 → 0.7.41

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 (51) hide show
  1. package/README.md +11 -6
  2. package/dist/backend/adapters/claude-adapter.js +8 -6
  3. package/dist/backend/api/artifact-routes.js +72 -1
  4. package/dist/backend/api/runtime-state-routes.js +17 -5
  5. package/dist/backend/api/task-routes.js +6 -0
  6. package/dist/backend/api/workflow-control-routes.js +56 -0
  7. package/dist/backend/cli/install-vcm-harness.js +42 -47
  8. package/dist/backend/role-tool-policy.js +60 -0
  9. package/dist/backend/server.js +21 -5
  10. package/dist/backend/services/artifact-service.js +309 -5
  11. package/dist/backend/services/auto-memory-service.js +2 -2
  12. package/dist/backend/services/gate-review-service.js +81 -33
  13. package/dist/backend/services/harness-feedback-service.js +11 -4
  14. package/dist/backend/services/harness-service.js +35 -45
  15. package/dist/backend/services/lsp-plugin.js +1 -4
  16. package/dist/backend/services/message-service.js +105 -37
  17. package/dist/backend/services/status-service.js +6 -0
  18. package/dist/backend/services/workflow-control-service.js +854 -0
  19. package/dist/backend/templates/handoff.js +158 -3
  20. package/dist/backend/templates/harness/architect-agent.js +3 -2
  21. package/dist/backend/templates/harness/check-scaffold-ledger.js +105 -39
  22. package/dist/backend/templates/harness/claude-root.js +9 -1
  23. package/dist/backend/templates/harness/coder-agent.js +1 -2
  24. package/dist/backend/templates/harness/coder-worker-agent.js +1 -1
  25. package/dist/backend/templates/harness/gate-review.js +3 -4
  26. package/dist/backend/templates/harness/harness-engineer-agent.js +18 -1
  27. package/dist/backend/templates/harness/project-manager-agent.js +3 -2
  28. package/dist/backend/templates/harness/tester-agent.js +1 -0
  29. package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +5 -0
  30. package/dist/backend/templates/harness/vcm-code-navigation-skill.js +3 -4
  31. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +2 -2
  32. package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +4 -2
  33. package/dist/backend/templates/harness/vcm-propose-memory-skill.js +4 -2
  34. package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +2 -1
  35. package/dist/backend/templates/harness/vcm-route-message-skill.js +4 -7
  36. package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -13
  37. package/dist/backend/templates/harness/vcm-workflow-review-skill.js +62 -0
  38. package/dist/backend/templates/message-envelope.js +4 -4
  39. package/dist/shared/types/workflow.js +7 -1
  40. package/dist/shared/validation/artifact-check.js +99 -95
  41. package/dist/shared/validation/artifact-contract.js +9 -0
  42. package/dist/shared/validation/artifact-registry.js +211 -0
  43. package/dist-frontend/assets/{index-DkM0mnQD.js → index-Bocc2DWF.js} +34 -34
  44. package/dist-frontend/assets/{index-CXSOe-NN.css → index-C_XHGNBD.css} +1 -1
  45. package/dist-frontend/index.html +2 -2
  46. package/package.json +1 -1
  47. package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +2 -1
  48. package/scripts/harness-tools/run-long-check +38 -8
  49. package/scripts/harness-tools/vcm-artifact +148 -0
  50. package/scripts/harness-tools/vcm-bash-guard +42 -47
  51. package/scripts/harness-tools/watch-job +58 -4
@@ -2,6 +2,7 @@ import { execFile } from "node:child_process";
2
2
  import path from "node:path";
3
3
  import { promisify } from "node:util";
4
4
  import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
5
+ import { CODE_ROLE_DISALLOWED_TOOLS, REVIEWER_DISALLOWED_TOOLS } from "../role-tool-policy.js";
5
6
  import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
6
7
  import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
7
8
  import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
@@ -25,6 +26,7 @@ import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propo
25
26
  import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
26
27
  import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
27
28
  import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
29
+ import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
28
30
  import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
29
31
  import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
30
32
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
@@ -53,7 +55,7 @@ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
53
55
  const VCM_AUTO_MEMORY_ENABLED = false;
54
56
  const VCM_HOOK_DEFINITIONS = [
55
57
  { eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
56
- { eventName: "PreToolUse", matcher: "Grep", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
58
+ { eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
57
59
  { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
58
60
  { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
59
61
  { eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
@@ -137,6 +139,14 @@ const HARNESS_FILES = [
137
139
  ownership: "whole-file",
138
140
  renderRules: renderVcmTaskStateSkillRules
139
141
  },
142
+ {
143
+ kind: "skill-vcm-workflow-review",
144
+ path: ".claude/skills/vcm-workflow-review/SKILL.md",
145
+ title: "VCM Workflow Review Skill",
146
+ frontmatter: renderSkillFrontmatter("vcm-workflow-review", "Use before every project-manager dispatch to Architect, Coder, or Tester so VCM can approve the workflow transition."),
147
+ ownership: "whole-file",
148
+ renderRules: renderVcmWorkflowReviewSkillRules
149
+ },
140
150
  {
141
151
  kind: "skill-vcm-final-acceptance",
142
152
  path: ".claude/skills/vcm-final-acceptance/SKILL.md",
@@ -198,10 +208,9 @@ const HARNESS_FILES = [
198
208
  path: ".claude/agents/reviewer.md",
199
209
  title: "Reviewer Agent",
200
210
  memoryBlock: true,
201
- requiredTools: ["LSP"],
202
- forbiddenTools: ["Grep"],
211
+ requiredDisallowedTools: REVIEWER_DISALLOWED_TOOLS,
203
212
  requiredSkills: ["vcm-code-navigation"],
204
- frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Glob, Bash, Write, LSP", skills: ["vcm-code-navigation"] }),
213
+ frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", "), skills: ["vcm-code-navigation"] }),
205
214
  renderRules: renderReviewerAgentRules
206
215
  },
207
216
  {
@@ -274,11 +283,10 @@ const HARNESS_FILES = [
274
283
  path: ".claude/agents/architect.md",
275
284
  title: "Architect Agent",
276
285
  memoryBlock: true,
277
- requiredTools: ["Agent", "LSP"],
278
- forbiddenTools: ["Grep"],
286
+ requiredDisallowedTools: CODE_ROLE_DISALLOWED_TOOLS,
279
287
  requiredSkills: ["vcm-code-navigation"],
280
288
  blankLineBeforeEnd: true,
281
- frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Glob, Bash, Edit, Write, Agent, LSP", skills: ["vcm-code-navigation"] }),
289
+ frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", "), skills: ["vcm-code-navigation"] }),
282
290
  renderRules: renderArchitectHarnessRules
283
291
  },
284
292
  {
@@ -286,10 +294,9 @@ const HARNESS_FILES = [
286
294
  path: ".claude/agents/coder.md",
287
295
  title: "Coder Agent",
288
296
  memoryBlock: true,
289
- requiredTools: ["Agent", "LSP"],
290
- forbiddenTools: ["Grep"],
297
+ requiredDisallowedTools: CODE_ROLE_DISALLOWED_TOOLS,
291
298
  requiredSkills: ["vcm-code-navigation"],
292
- frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Glob, Bash, Edit, Write, Agent, LSP", skills: ["vcm-code-navigation"] }),
299
+ frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", "), skills: ["vcm-code-navigation"] }),
293
300
  renderRules: renderCoderHarnessRules
294
301
  },
295
302
  {
@@ -1380,47 +1387,28 @@ function renderNewHarnessFile(definition, block, contentAfterBlock = definition.
1380
1387
  const suffix = contentAfterBlock?.trim();
1381
1388
  return `${frontmatter}# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
1382
1389
  }
1383
- function ensureAgentTool(content, requiredTool) {
1384
- const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
1385
- if (!frontmatterMatch) {
1386
- return content;
1387
- }
1388
- const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
1389
- if (!toolsMatch) {
1390
- return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
1391
- }
1392
- const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
1393
- if (tools.includes(requiredTool)) {
1390
+ function ensureAgentDisallowedTools(content, requiredTools) {
1391
+ if (!requiredTools) {
1394
1392
  return content;
1395
1393
  }
1396
- const nextTools = [...tools, requiredTool].join(", ");
1397
- return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
1398
- }
1399
- function ensureAgentTools(content, requiredTools) {
1400
- return (requiredTools ?? []).reduce(ensureAgentTool, content);
1401
- }
1402
- function removeAgentTool(content, forbiddenTool) {
1403
1394
  const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
1404
1395
  if (!frontmatterMatch) {
1405
1396
  return content;
1406
1397
  }
1407
- const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
1408
- if (!toolsMatch) {
1409
- return content;
1410
- }
1411
- const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
1412
- const nextTools = tools.filter((tool) => tool !== forbiddenTool);
1413
- if (nextTools.length === tools.length) {
1414
- return content;
1415
- }
1416
- return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools.join(", ")}`));
1417
- }
1418
- function normalizeAgentTools(content, requiredTools, forbiddenTools) {
1419
- const required = ensureAgentTools(content, requiredTools);
1420
- return (forbiddenTools ?? []).reduce(removeAgentTool, required);
1398
+ const withoutAllowedTools = frontmatterMatch[0].replace(/^tools:\s*.*\r?\n?/m, "");
1399
+ const disallowedMatch = withoutAllowedTools.match(/^disallowedTools:\s*(.*)$/m);
1400
+ const existing = disallowedMatch?.[1]
1401
+ .split(",")
1402
+ .map((tool) => tool.trim())
1403
+ .filter(Boolean) ?? [];
1404
+ const disallowedTools = [...new Set([...existing, ...requiredTools])].join(", ");
1405
+ const nextFrontmatter = disallowedMatch
1406
+ ? withoutAllowedTools.replace(disallowedMatch[0], `disallowedTools: ${disallowedTools}`)
1407
+ : withoutAllowedTools.replace(/\r?\n---$/, `\ndisallowedTools: ${disallowedTools}\n---`);
1408
+ return content.replace(frontmatterMatch[0], nextFrontmatter);
1421
1409
  }
1422
1410
  function normalizeAgentFrontmatter(content, definition) {
1423
- const toolsUpdated = normalizeAgentTools(content, definition.requiredTools, definition.forbiddenTools);
1411
+ const toolsUpdated = ensureAgentDisallowedTools(content, definition.requiredDisallowedTools);
1424
1412
  return (definition.requiredSkills ?? []).reduce(ensureAgentSkill, toolsUpdated);
1425
1413
  }
1426
1414
  function ensureAgentSkill(content, requiredSkill) {
@@ -1572,13 +1560,15 @@ function isPlainObject(value) {
1572
1560
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1573
1561
  }
1574
1562
  function renderAgentFrontmatter(name, description, options = {}) {
1575
- const tools = options.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
1563
+ const toolPolicy = options.disallowedTools
1564
+ ? `disallowedTools: ${options.disallowedTools}`
1565
+ : `tools: ${options.tools ?? "Read, Grep, Glob, Bash, Edit, Write"}`;
1576
1566
  const model = options.model ? `\nmodel: ${options.model}` : "";
1577
1567
  const effort = options.effort ? `\neffort: ${options.effort}` : "";
1578
1568
  const skills = options.skills?.length
1579
1569
  ? `\nskills:\n${options.skills.map((skill) => ` - ${skill}`).join("\n")}`
1580
1570
  : "";
1581
- return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}${effort}${skills}\n---`;
1571
+ return `---\nname: ${name}\ndescription: ${description}\n${toolPolicy}${model}${effort}${skills}\n---`;
1582
1572
  }
1583
1573
  function renderSkillFrontmatter(name, description) {
1584
1574
  return `---\nname: ${name}\ndescription: ${description}\n---`;
@@ -1,9 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
+ export { roleUsesLsp } from "../role-tool-policy.js";
3
4
  export const VCM_LSP_PLUGIN_NAME = "vcm-lsp-bridge";
4
5
  export const VCM_LSP_PLUGIN_DIR = fileURLToPath(new URL("../../../scripts/claude-plugins/vcm-lsp-bridge", import.meta.url));
5
6
  export const VCM_LSP_PLUGIN_MANIFEST = path.join(VCM_LSP_PLUGIN_DIR, ".claude-plugin", "plugin.json");
6
- const LSP_ROLES = new Set(["architect", "coder", "reviewer"]);
7
- export function roleUsesLsp(role) {
8
- return LSP_ROLES.has(role);
9
- }
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { randomUUID } from "node:crypto";
2
+ import { createHash, randomUUID } from "node:crypto";
3
3
  import { CORE_VCM_ROLE_NAMES } from "../../shared/constants.js";
4
4
  import { resolveRepoPath } from "../adapters/filesystem.js";
5
5
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
@@ -94,8 +94,28 @@ export function createMessageService(deps) {
94
94
  failureReason: `${routeFile.toRole} is still running.`
95
95
  };
96
96
  }
97
+ const messageId = id();
98
+ if (routeFile.fromRole === PM_ROLE && deps.workflowControlService) {
99
+ try {
100
+ await deps.workflowControlService.claimDispatch({
101
+ ...toWorkflowContext(input),
102
+ routePath: routeFile.path,
103
+ targetRole: routeFile.toRole,
104
+ routeContentHash: routeFile.contentHash ?? "",
105
+ messageId
106
+ });
107
+ }
108
+ catch (error) {
109
+ return {
110
+ delivered: false,
111
+ requiresUserApproval: false,
112
+ clearedRouteFile: false,
113
+ failureReason: errorMessage(error)
114
+ };
115
+ }
116
+ }
97
117
  const message = {
98
- id: id(),
118
+ id: messageId,
99
119
  taskSlug: input.taskSlug,
100
120
  createdAt: timestamp,
101
121
  fromRole: routeFile.fromRole,
@@ -103,7 +123,6 @@ export function createMessageService(deps) {
103
123
  type: routeFile.type,
104
124
  body: routeFile.body,
105
125
  artifactRefs: routeFile.artifactRefs,
106
- workflow: routeFile.fromRole === PM_ROLE ? routeFile.workflow : undefined,
107
126
  bodyPath: routeFile.path,
108
127
  routePath: routeFile.path,
109
128
  dispatchingAt: timestamp,
@@ -115,16 +134,24 @@ export function createMessageService(deps) {
115
134
  ...message,
116
135
  deliveredAt: timestamp
117
136
  };
118
- await submitTerminalInput(deps.runtime, session.id, renderMessageEnvelope(delivered), {
119
- enterDelayMs: autoDispatchEnterDelayMs
120
- });
137
+ try {
138
+ await submitTerminalInput(deps.runtime, session.id, renderMessageEnvelope(delivered), {
139
+ enterDelayMs: autoDispatchEnterDelayMs
140
+ });
141
+ }
142
+ catch (error) {
143
+ if (routeFile.fromRole === PM_ROLE) {
144
+ await deps.workflowControlService?.releaseDispatch(toWorkflowContext(input), messageId);
145
+ }
146
+ throw error;
147
+ }
121
148
  await deps.sessionService.markRoleActivityRunning(input.repoRoot, input.taskSlug, routeFile.toRole, session.id);
122
149
  if (routeFile.fromRole === PM_ROLE) {
123
150
  await deps.taskWorkflowService?.recordPmDispatch({
124
151
  taskRepoRoot: input.taskRepoRoot ?? input.repoRoot,
125
152
  stateRoot: input.stateRoot,
126
153
  taskSlug: input.taskSlug
127
- }, routeFile.workflow, {
154
+ }, undefined, {
128
155
  messageId: delivered.id,
129
156
  toRole: delivered.toRole
130
157
  }).catch(() => undefined);
@@ -158,7 +185,7 @@ export function createMessageService(deps) {
158
185
  const messages = await readLatestMessages(deps.fs, getMessagesPath(getStateRepoRoot(input), input.stateRoot, input.taskSlug));
159
186
  const messageId = extractVcmMessageId(input.prompt);
160
187
  if (!messageId) {
161
- return undefined;
188
+ return confirmManualRoute(input, timestamp);
162
189
  }
163
190
  const message = findDeliveredMessageForPrompt(messages, input.role, messageId);
164
191
  if (!message) {
@@ -169,6 +196,9 @@ export function createMessageService(deps) {
169
196
  acceptedAt: timestamp,
170
197
  failureReason: undefined
171
198
  };
199
+ if (accepted.fromRole === PM_ROLE) {
200
+ await deps.workflowControlService?.confirmDispatch(toWorkflowContext(input), accepted.id);
201
+ }
172
202
  await appendMessageSnapshot(deps.fs, input, accepted);
173
203
  await clearRouteFileIfStillMatchesMessage(deps.fs, input, accepted);
174
204
  return accepted;
@@ -251,6 +281,58 @@ export function createMessageService(deps) {
251
281
  return messages.find((message) => message.id === messageId);
252
282
  });
253
283
  }
284
+ async function confirmManualRoute(input, timestamp) {
285
+ if (!input.prompt?.trim() || input.role === PM_ROLE)
286
+ return undefined;
287
+ const routes = await listRouteFiles(deps.fs, toRouteContext(input));
288
+ const routeFile = routes.find((candidate) => candidate.pending
289
+ && candidate.fromRole === PM_ROLE
290
+ && candidate.toRole === input.role
291
+ && candidate.body.trim() === input.prompt.trim());
292
+ if (!routeFile)
293
+ return undefined;
294
+ const messageId = id();
295
+ await deps.workflowControlService?.claimDispatch({
296
+ ...toWorkflowContext(input),
297
+ routePath: routeFile.path,
298
+ targetRole: routeFile.toRole,
299
+ routeContentHash: routeFile.contentHash ?? "",
300
+ messageId
301
+ });
302
+ const accepted = {
303
+ id: messageId,
304
+ taskSlug: input.taskSlug,
305
+ createdAt: timestamp,
306
+ fromRole: routeFile.fromRole,
307
+ toRole: routeFile.toRole,
308
+ type: routeFile.type,
309
+ body: routeFile.body,
310
+ artifactRefs: routeFile.artifactRefs,
311
+ bodyPath: routeFile.path,
312
+ routePath: routeFile.path,
313
+ dispatchingAt: timestamp,
314
+ deliveredAt: timestamp,
315
+ acceptedAt: timestamp
316
+ };
317
+ try {
318
+ await deps.workflowControlService?.confirmDispatch(toWorkflowContext(input), messageId);
319
+ }
320
+ catch (error) {
321
+ await deps.workflowControlService?.releaseDispatch(toWorkflowContext(input), messageId);
322
+ throw error;
323
+ }
324
+ await appendMessageSnapshot(deps.fs, input, accepted);
325
+ await clearRouteFileIfStillMatchesMessage(deps.fs, input, accepted);
326
+ await deps.taskWorkflowService?.recordPmDispatch({
327
+ taskRepoRoot: input.taskRepoRoot ?? input.repoRoot,
328
+ stateRoot: input.stateRoot,
329
+ taskSlug: input.taskSlug
330
+ }, undefined, {
331
+ messageId: accepted.id,
332
+ toRole: accepted.toRole
333
+ }).catch(() => undefined);
334
+ return accepted;
335
+ }
254
336
  async function markDispatchConfirmationFailure(input, messageId, failureReason) {
255
337
  await withTaskLock(taskLocks, getMessagesPath(getStateRepoRoot(input), input.stateRoot, input.taskSlug), async () => {
256
338
  const messages = await readLatestMessages(deps.fs, getMessagesPath(getStateRepoRoot(input), input.stateRoot, input.taskSlug));
@@ -258,6 +340,9 @@ export function createMessageService(deps) {
258
340
  if (!current || current.acceptedAt) {
259
341
  return;
260
342
  }
343
+ if (current.fromRole === PM_ROLE) {
344
+ await deps.workflowControlService?.releaseDispatch(toWorkflowContext(input), messageId);
345
+ }
261
346
  await appendMessageSnapshot(deps.fs, input, {
262
347
  ...current,
263
348
  failureReason
@@ -272,6 +357,14 @@ function toRouteContext(input) {
272
357
  handoffDir: input.handoffDir
273
358
  };
274
359
  }
360
+ function toWorkflowContext(input) {
361
+ return {
362
+ taskRepoRoot: input.taskRepoRoot ?? input.repoRoot,
363
+ stateRoot: input.stateRoot,
364
+ handoffDir: input.handoffDir,
365
+ taskSlug: input.taskSlug
366
+ };
367
+ }
275
368
  async function listRouteFiles(fs, input) {
276
369
  const repoRoot = input.taskRepoRoot ?? input.repoRoot;
277
370
  const routeDir = path.posix.join(input.handoffDir, "messages");
@@ -296,9 +389,9 @@ async function listRouteFiles(fs, input) {
296
389
  type: parsed.type,
297
390
  body: parsed.body,
298
391
  artifactRefs: parsed.artifactRefs,
299
- workflow: parsed.workflow,
300
392
  exists: true,
301
- pending: parsed.body.trim().length > 0
393
+ pending: parsed.body.trim().length > 0,
394
+ contentHash: createHash("sha256").update(content).digest("hex")
302
395
  });
303
396
  }
304
397
  return routeFiles;
@@ -320,12 +413,10 @@ function parseRouteFileContent(content, fromRole, toRole) {
320
413
  const { frontmatter, body } = splitFrontmatter(content);
321
414
  const type = parseMessageType(frontmatter.type) ?? getDefaultMessageType(fromRole, toRole);
322
415
  const artifactRefs = parseArtifactRefs(frontmatter);
323
- const workflow = fromRole === PM_ROLE ? parseWorkflowDeclaration(frontmatter) : undefined;
324
416
  return {
325
417
  type,
326
418
  body: body.trim(),
327
- artifactRefs,
328
- workflow
419
+ artifactRefs
329
420
  };
330
421
  }
331
422
  function splitFrontmatter(content) {
@@ -376,25 +467,6 @@ function parseArtifactRefs(frontmatter) {
376
467
  .map((entry) => entry.trim())
377
468
  .filter(Boolean);
378
469
  }
379
- function parseWorkflowDeclaration(frontmatter) {
380
- const declaration = {
381
- flow: frontmatter.workflow_flow,
382
- step: frontmatter.workflow_step,
383
- branch: frontmatter.workflow_branch,
384
- resumePoint: frontmatter.workflow_resume_point,
385
- status: frontmatter.workflow_status,
386
- evidenceRefs: parseCommaSeparated(frontmatter.workflow_evidence_refs)
387
- };
388
- return Object.values(declaration).some((value) => Array.isArray(value) ? value.length > 0 : value !== undefined)
389
- ? declaration
390
- : undefined;
391
- }
392
- function parseCommaSeparated(value) {
393
- if (value === undefined) {
394
- return undefined;
395
- }
396
- return value.split(",").map((entry) => entry.trim()).filter(Boolean);
397
- }
398
470
  function getDefaultMessageType(fromRole, toRole) {
399
471
  if (fromRole === PM_ROLE && toRole !== PM_ROLE) {
400
472
  return "task";
@@ -454,14 +526,10 @@ async function clearRouteFileIfStillMatchesMessage(fs, input, message) {
454
526
  const parsed = parseRouteFileContent(routeContent, fromRole, message.toRole);
455
527
  if (parsed.body.trim() === message.body.trim() &&
456
528
  parsed.type === message.type &&
457
- arraysEqual(parsed.artifactRefs, message.artifactRefs) &&
458
- workflowDeclarationsEqual(parsed.workflow, message.workflow)) {
529
+ arraysEqual(parsed.artifactRefs, message.artifactRefs)) {
459
530
  await fs.writeText(absolutePath, "");
460
531
  }
461
532
  }
462
- function workflowDeclarationsEqual(left, right) {
463
- return JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
464
- }
465
533
  function isCoreRouteRole(role) {
466
534
  return CORE_VCM_ROLE_NAMES.some((candidate) => candidate === role);
467
535
  }
@@ -51,10 +51,16 @@ function degradedArtifactSummary(handoffDir) {
51
51
  roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
52
52
  messageRoutePaths: {},
53
53
  architectureBriefPath: `${handoffDir}/architecture-brief.md`,
54
+ architectureEvidencePath: `${handoffDir}/architecture-evidence.md`,
55
+ planningProgressPath: `${handoffDir}/planning-progress.md`,
54
56
  architecturePlanPath: `${handoffDir}/architecture-plan.md`,
55
57
  knownIssuesPath: `${handoffDir}/known-issues.md`,
58
+ coderCompletionPath: `${handoffDir}/coder-completion.md`,
59
+ architectDebugPath: `${handoffDir}/architect-debug.md`,
60
+ architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
56
61
  testReportPath: `${handoffDir}/test-report.md`,
57
62
  docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
63
+ workflowProgressPath: `${handoffDir}/workflow-progress.md`,
58
64
  finalAcceptancePath: `${handoffDir}/final-acceptance.md`
59
65
  },
60
66
  checks: []