vibe-coding-master 0.7.38 → 0.7.40
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/README.md +12 -6
- package/dist/backend/adapters/claude-adapter.js +1 -0
- package/dist/backend/api/artifact-routes.js +72 -1
- package/dist/backend/api/runtime-state-routes.js +17 -5
- package/dist/backend/api/task-routes.js +6 -0
- package/dist/backend/api/workflow-control-routes.js +56 -0
- package/dist/backend/cli/install-vcm-harness.js +46 -22
- package/dist/backend/server.js +21 -5
- package/dist/backend/services/artifact-service.js +309 -5
- package/dist/backend/services/auto-memory-service.js +2 -2
- package/dist/backend/services/code-intelligence-service.js +5 -5
- package/dist/backend/services/gate-review-service.js +81 -33
- package/dist/backend/services/harness-feedback-service.js +11 -4
- package/dist/backend/services/harness-service.js +42 -26
- package/dist/backend/services/message-service.js +105 -37
- package/dist/backend/services/status-service.js +6 -0
- package/dist/backend/services/workflow-control-service.js +854 -0
- package/dist/backend/templates/handoff.js +158 -3
- package/dist/backend/templates/harness/architect-agent.js +3 -3
- package/dist/backend/templates/harness/check-scaffold-ledger.js +105 -39
- package/dist/backend/templates/harness/claude-root.js +9 -1
- package/dist/backend/templates/harness/coder-agent.js +2 -3
- package/dist/backend/templates/harness/coder-worker-agent.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +4 -5
- package/dist/backend/templates/harness/harness-engineer-agent.js +18 -1
- package/dist/backend/templates/harness/project-manager-agent.js +3 -2
- package/dist/backend/templates/harness/tester-agent.js +1 -0
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +5 -0
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -6
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +4 -2
- package/dist/backend/templates/harness/vcm-propose-memory-skill.js +4 -2
- package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +2 -1
- package/dist/backend/templates/harness/vcm-route-message-skill.js +4 -7
- package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -13
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +62 -0
- package/dist/backend/templates/message-envelope.js +4 -4
- package/dist/shared/types/workflow.js +7 -1
- package/dist/shared/validation/artifact-check.js +99 -95
- package/dist/shared/validation/artifact-contract.js +9 -0
- package/dist/shared/validation/artifact-registry.js +211 -0
- package/dist-frontend/assets/{index-VillnFRo.js → index-Bocc2DWF.js} +34 -34
- package/dist-frontend/assets/{index-CXSOe-NN.css → index-C_XHGNBD.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/run-long-check +38 -8
- package/scripts/harness-tools/vcm-artifact +148 -0
- package/scripts/harness-tools/vcm-bash-guard +42 -47
- package/scripts/harness-tools/watch-job +58 -4
|
@@ -25,6 +25,7 @@ import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propo
|
|
|
25
25
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
26
26
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
27
27
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
28
|
+
import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
|
|
28
29
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
29
30
|
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
30
31
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
@@ -53,7 +54,7 @@ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
|
53
54
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
54
55
|
const VCM_HOOK_DEFINITIONS = [
|
|
55
56
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
56
|
-
{ eventName: "PreToolUse", matcher: "
|
|
57
|
+
{ eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
57
58
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
58
59
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
59
60
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
@@ -137,6 +138,14 @@ const HARNESS_FILES = [
|
|
|
137
138
|
ownership: "whole-file",
|
|
138
139
|
renderRules: renderVcmTaskStateSkillRules
|
|
139
140
|
},
|
|
141
|
+
{
|
|
142
|
+
kind: "skill-vcm-workflow-review",
|
|
143
|
+
path: ".claude/skills/vcm-workflow-review/SKILL.md",
|
|
144
|
+
title: "VCM Workflow Review Skill",
|
|
145
|
+
frontmatter: renderSkillFrontmatter("vcm-workflow-review", "Use before every project-manager dispatch to Architect, Coder, or Tester so VCM can approve the workflow transition."),
|
|
146
|
+
ownership: "whole-file",
|
|
147
|
+
renderRules: renderVcmWorkflowReviewSkillRules
|
|
148
|
+
},
|
|
140
149
|
{
|
|
141
150
|
kind: "skill-vcm-final-acceptance",
|
|
142
151
|
path: ".claude/skills/vcm-final-acceptance/SKILL.md",
|
|
@@ -198,9 +207,9 @@ const HARNESS_FILES = [
|
|
|
198
207
|
path: ".claude/agents/reviewer.md",
|
|
199
208
|
title: "Reviewer Agent",
|
|
200
209
|
memoryBlock: true,
|
|
201
|
-
requiredTools: ["LSP"],
|
|
202
|
-
|
|
203
|
-
frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Glob, Bash, Write, LSP" }),
|
|
210
|
+
requiredTools: ["Grep", "LSP"],
|
|
211
|
+
requiredSkills: ["vcm-code-navigation"],
|
|
212
|
+
frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Grep, Glob, Bash, Write, LSP", skills: ["vcm-code-navigation"] }),
|
|
204
213
|
renderRules: renderReviewerAgentRules
|
|
205
214
|
},
|
|
206
215
|
{
|
|
@@ -273,10 +282,10 @@ const HARNESS_FILES = [
|
|
|
273
282
|
path: ".claude/agents/architect.md",
|
|
274
283
|
title: "Architect Agent",
|
|
275
284
|
memoryBlock: true,
|
|
276
|
-
requiredTools: ["Agent", "LSP"],
|
|
277
|
-
|
|
285
|
+
requiredTools: ["Grep", "Agent", "LSP"],
|
|
286
|
+
requiredSkills: ["vcm-code-navigation"],
|
|
278
287
|
blankLineBeforeEnd: true,
|
|
279
|
-
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" }),
|
|
288
|
+
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP", skills: ["vcm-code-navigation"] }),
|
|
280
289
|
renderRules: renderArchitectHarnessRules
|
|
281
290
|
},
|
|
282
291
|
{
|
|
@@ -284,9 +293,9 @@ const HARNESS_FILES = [
|
|
|
284
293
|
path: ".claude/agents/coder.md",
|
|
285
294
|
title: "Coder Agent",
|
|
286
295
|
memoryBlock: true,
|
|
287
|
-
requiredTools: ["Agent", "LSP"],
|
|
288
|
-
|
|
289
|
-
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Glob, Bash, Edit, Write, Agent, LSP" }),
|
|
296
|
+
requiredTools: ["Grep", "Agent", "LSP"],
|
|
297
|
+
requiredSkills: ["vcm-code-navigation"],
|
|
298
|
+
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP", skills: ["vcm-code-navigation"] }),
|
|
290
299
|
renderRules: renderCoderHarnessRules
|
|
291
300
|
},
|
|
292
301
|
{
|
|
@@ -1224,7 +1233,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1224
1233
|
const migratedContent = migrateLegacyHarnessFile(definition, currentContent, expectedBlock);
|
|
1225
1234
|
if (migratedContent) {
|
|
1226
1235
|
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(migratedContent) : migratedContent;
|
|
1227
|
-
const nextContent =
|
|
1236
|
+
const nextContent = normalizeAgentFrontmatter(memoryUpdatedContent, definition);
|
|
1228
1237
|
return {
|
|
1229
1238
|
definition,
|
|
1230
1239
|
status: {
|
|
@@ -1243,7 +1252,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1243
1252
|
};
|
|
1244
1253
|
}
|
|
1245
1254
|
const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
|
|
1246
|
-
const nextContent =
|
|
1255
|
+
const nextContent = normalizeAgentFrontmatter(insertedContent, definition);
|
|
1247
1256
|
return {
|
|
1248
1257
|
definition,
|
|
1249
1258
|
status: {
|
|
@@ -1265,7 +1274,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1265
1274
|
const currentBlock = match[0];
|
|
1266
1275
|
const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
|
|
1267
1276
|
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
|
|
1268
|
-
const nextContent =
|
|
1277
|
+
const nextContent = normalizeAgentFrontmatter(memoryUpdatedContent, definition);
|
|
1269
1278
|
const action = currentContent === nextContent ? "ok" : "update";
|
|
1270
1279
|
return {
|
|
1271
1280
|
definition,
|
|
@@ -1396,25 +1405,29 @@ function ensureAgentTool(content, requiredTool) {
|
|
|
1396
1405
|
function ensureAgentTools(content, requiredTools) {
|
|
1397
1406
|
return (requiredTools ?? []).reduce(ensureAgentTool, content);
|
|
1398
1407
|
}
|
|
1399
|
-
function
|
|
1408
|
+
function normalizeAgentFrontmatter(content, definition) {
|
|
1409
|
+
const toolsUpdated = ensureAgentTools(content, definition.requiredTools);
|
|
1410
|
+
return (definition.requiredSkills ?? []).reduce(ensureAgentSkill, toolsUpdated);
|
|
1411
|
+
}
|
|
1412
|
+
function ensureAgentSkill(content, requiredSkill) {
|
|
1400
1413
|
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
1401
1414
|
if (!frontmatterMatch) {
|
|
1402
1415
|
return content;
|
|
1403
1416
|
}
|
|
1404
|
-
const
|
|
1405
|
-
|
|
1406
|
-
|
|
1417
|
+
const frontmatter = frontmatterMatch[0];
|
|
1418
|
+
const skillsMatch = frontmatter.match(/^skills:[ \t]*(?:\r?\n((?:\s+-\s+[^\r\n]+\r?\n?)*))?/m);
|
|
1419
|
+
if (!skillsMatch) {
|
|
1420
|
+
return content.replace(frontmatter, frontmatter.replace(/\r?\n---$/, `\nskills:\n - ${requiredSkill}\n---`));
|
|
1407
1421
|
}
|
|
1408
|
-
const
|
|
1409
|
-
|
|
1410
|
-
|
|
1422
|
+
const listedSkills = (skillsMatch[1] ?? "")
|
|
1423
|
+
.split(/\r?\n/)
|
|
1424
|
+
.map((line) => line.match(/^\s+-\s+(.+)$/)?.[1]?.trim())
|
|
1425
|
+
.filter((skill) => Boolean(skill));
|
|
1426
|
+
if (listedSkills.includes(requiredSkill)) {
|
|
1411
1427
|
return content;
|
|
1412
1428
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
function normalizeAgentTools(content, requiredTools, forbiddenTools) {
|
|
1416
|
-
const required = ensureAgentTools(content, requiredTools);
|
|
1417
|
-
return (forbiddenTools ?? []).reduce(removeAgentTool, required);
|
|
1429
|
+
const nextSkills = `${skillsMatch[0].trimEnd()}\n - ${requiredSkill}`;
|
|
1430
|
+
return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], nextSkills));
|
|
1418
1431
|
}
|
|
1419
1432
|
function migrateLegacyHarnessFile(definition, currentContent, block) {
|
|
1420
1433
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
@@ -1548,7 +1561,10 @@ function renderAgentFrontmatter(name, description, options = {}) {
|
|
|
1548
1561
|
const tools = options.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
|
|
1549
1562
|
const model = options.model ? `\nmodel: ${options.model}` : "";
|
|
1550
1563
|
const effort = options.effort ? `\neffort: ${options.effort}` : "";
|
|
1551
|
-
|
|
1564
|
+
const skills = options.skills?.length
|
|
1565
|
+
? `\nskills:\n${options.skills.map((skill) => ` - ${skill}`).join("\n")}`
|
|
1566
|
+
: "";
|
|
1567
|
+
return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}${effort}${skills}\n---`;
|
|
1552
1568
|
}
|
|
1553
1569
|
function renderSkillFrontmatter(name, description) {
|
|
1554
1570
|
return `---\nname: ${name}\ndescription: ${description}\n---`;
|
|
@@ -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:
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
},
|
|
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
|
|
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: []
|