vibe-coding-master 0.7.43 → 0.7.44
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 +40 -13
- package/dist/backend/api/artifact-routes.js +3 -0
- package/dist/backend/api/harness-routes.js +16 -0
- package/dist/backend/api/task-routes.js +1 -0
- package/dist/backend/api/workflow-control-routes.js +12 -0
- package/dist/backend/cli/install-vcm-harness.js +21 -0
- package/dist/backend/server.js +3 -1
- package/dist/backend/services/artifact-service.js +7 -30
- package/dist/backend/services/auto-memory-service.js +640 -12
- package/dist/backend/services/claude-hook-service.js +80 -2
- package/dist/backend/services/gate-review-service.js +173 -74
- package/dist/backend/services/harness-feedback-service.js +76 -78
- package/dist/backend/services/harness-service.js +27 -3
- package/dist/backend/services/runtime-coordinator-service.js +2 -1
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/translation-worker-service.js +19 -4
- package/dist/backend/services/workflow-control-service.js +96 -12
- package/dist/backend/templates/handoff.js +44 -2
- package/dist/backend/templates/harness/architect-agent.js +13 -7
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +1 -1
- package/dist/backend/templates/harness/check-scaffold-ledger.js +234 -10
- package/dist/backend/templates/harness/claude-root.js +3 -2
- package/dist/backend/templates/harness/coder-agent.js +8 -0
- package/dist/backend/templates/harness/gate-review.js +144 -49
- package/dist/backend/templates/harness/harness-engineer-agent.js +25 -10
- package/dist/backend/templates/harness/project-manager-agent.js +16 -10
- package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +60 -0
- package/dist/backend/templates/harness/tester-agent.js +13 -0
- package/dist/backend/templates/harness/vcm-ask-user-skill.js +82 -0
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -5
- package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +1 -1
- package/dist/shared/types/workflow.js +1 -0
- package/dist/shared/validation/artifact-check.js +3 -3
- package/dist/shared/validation/artifact-contract.js +1 -1
- package/dist/shared/validation/artifact-registry.js +16 -0
- package/dist-frontend/assets/{index-VW9tYPP5.js → index-BvCmrFlN.js} +49 -49
- package/dist-frontend/index.html +1 -1
- package/package.json +1 -1
- package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +21 -6
- package/scripts/harness-tools/vcm-artifact +1 -2
- package/scripts/harness-tools/vcm-bash-guard +1 -1
|
@@ -4,7 +4,6 @@ import { checkMarkdownArtifact, readArtifactSectionValue } from "../../shared/va
|
|
|
4
4
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
5
5
|
import { VcmError } from "../errors.js";
|
|
6
6
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
7
|
-
import { getRetrospectiveReportErrors } from "./artifact-service.js";
|
|
8
7
|
const FEEDBACK_ROOT = ".ai/vcm/harness-feedback";
|
|
9
8
|
const PENDING_DIR = `${FEEDBACK_ROOT}/pending`;
|
|
10
9
|
const TASK_RETROSPECTIVE_DIR = `${FEEDBACK_ROOT}/task-retrospectives`;
|
|
@@ -143,7 +142,7 @@ export function createHarnessFeedbackService(deps) {
|
|
|
143
142
|
]
|
|
144
143
|
: ["Report is empty."];
|
|
145
144
|
const reportReady = reportErrors.length === 0;
|
|
146
|
-
if (!reportReady || (marker.memoryRunId &&
|
|
145
|
+
if (!reportReady || (marker.memoryRunId && input.memoryReviewStatus === "failed")) {
|
|
147
146
|
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
148
147
|
...marker,
|
|
149
148
|
status: "failed",
|
|
@@ -155,6 +154,41 @@ export function createHarnessFeedbackService(deps) {
|
|
|
155
154
|
});
|
|
156
155
|
return true;
|
|
157
156
|
}
|
|
157
|
+
if (marker.memoryRunId && input.memoryReviewStatus === "documenting") {
|
|
158
|
+
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
159
|
+
...marker,
|
|
160
|
+
status: "waiting-docs",
|
|
161
|
+
updatedAt: timestamp
|
|
162
|
+
});
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
await completeTaskRetrospective(repoRoot, marker);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
async function completeWaitingTaskRetrospective(repoRoot, taskSlug, memoryStatus) {
|
|
169
|
+
const marker = await loadTaskRetrospectiveMarker(repoRoot, taskSlug);
|
|
170
|
+
if (!marker || marker.status !== "waiting-docs") {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (memoryStatus === "documenting") {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (memoryStatus === "failed") {
|
|
177
|
+
const timestamp = now();
|
|
178
|
+
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
179
|
+
...marker,
|
|
180
|
+
status: "failed",
|
|
181
|
+
failedAt: timestamp,
|
|
182
|
+
updatedAt: timestamp,
|
|
183
|
+
error: "Task Harness Retrospective durable-document assignment failed."
|
|
184
|
+
});
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
await completeTaskRetrospective(repoRoot, marker);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
async function completeTaskRetrospective(repoRoot, marker) {
|
|
191
|
+
const timestamp = now();
|
|
158
192
|
try {
|
|
159
193
|
await removeProcessedFeedback(repoRoot, marker.pendingFeedbackPaths ?? []);
|
|
160
194
|
}
|
|
@@ -166,7 +200,7 @@ export function createHarnessFeedbackService(deps) {
|
|
|
166
200
|
updatedAt: timestamp,
|
|
167
201
|
error: `VCM could not remove processed Harness Feedback: ${errorMessage(error)}`
|
|
168
202
|
});
|
|
169
|
-
return
|
|
203
|
+
return;
|
|
170
204
|
}
|
|
171
205
|
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
172
206
|
...marker,
|
|
@@ -174,7 +208,6 @@ export function createHarnessFeedbackService(deps) {
|
|
|
174
208
|
completedAt: timestamp,
|
|
175
209
|
updatedAt: timestamp
|
|
176
210
|
});
|
|
177
|
-
return true;
|
|
178
211
|
}
|
|
179
212
|
async function assertHarnessEngineerAvailable(_repoRoot) {
|
|
180
213
|
return undefined;
|
|
@@ -285,6 +318,16 @@ export function createHarnessFeedbackService(deps) {
|
|
|
285
318
|
`Current memory snapshot: ${memoryReview.currentMemoryPath}`,
|
|
286
319
|
"Active memory files:",
|
|
287
320
|
...memoryReview.activeMemoryPaths.map((memoryPath) => `- ${memoryPath}`),
|
|
321
|
+
"Proposal candidates:",
|
|
322
|
+
...(memoryReview.proposalCandidates.length > 0
|
|
323
|
+
? memoryReview.proposalCandidates.map((candidate) => [
|
|
324
|
+
candidate.id,
|
|
325
|
+
`source=${candidate.source}`,
|
|
326
|
+
`operation=${candidate.operation}`,
|
|
327
|
+
`target=${candidate.target}`,
|
|
328
|
+
`entry=${candidate.content ?? candidate.existing ?? "none"}`
|
|
329
|
+
].join(" | "))
|
|
330
|
+
: ["none"]),
|
|
288
331
|
...(memoryReview.planningCandidatePath
|
|
289
332
|
? [`Architect planning-session candidate: ${memoryReview.planningCandidatePath}`]
|
|
290
333
|
: []),
|
|
@@ -292,88 +335,30 @@ export function createHarnessFeedbackService(deps) {
|
|
|
292
335
|
"Review every memory candidate against final task evidence while performing this retrospective.",
|
|
293
336
|
"The snapshot files contain only the matching pre-review <VCM-memory> block content.",
|
|
294
337
|
"Before evaluating proposals, review every substantive entry in every current memory snapshot against current code, documentation, and final task evidence.",
|
|
295
|
-
"
|
|
296
|
-
"
|
|
297
|
-
"
|
|
298
|
-
"
|
|
299
|
-
"
|
|
300
|
-
|
|
301
|
-
"
|
|
302
|
-
"
|
|
303
|
-
"
|
|
304
|
-
"
|
|
305
|
-
"
|
|
306
|
-
"Do not keep full content in memory when durable-doc is correct. Use memory-reference only when an ongoing role needs the document pointer.",
|
|
307
|
-
"Use none as the complete Existing Memory Decisions body only when no substantive existing memory entry exists.",
|
|
308
|
-
"Apply the reviewed result directly to the <VCM-memory> blocks in the listed active memory files. Do not change any content outside those blocks.",
|
|
309
|
-
"If memory changes, commit only the changed active memory files before ending the turn. Use commit message: chore: update VCM memory. If memory is unchanged, do not create a commit.",
|
|
310
|
-
"Use this exact block in the retrospective report and replace each option or placeholder with one allowed value or a concise summary:",
|
|
338
|
+
"Evaluate each proposal independently. Keep only verified, durable, reusable project knowledge; do not keep task narrative, temporary state, unverified conclusions, or Harness rules in memory.",
|
|
339
|
+
"For every existing entry and proposal, record why the decision is necessary, the impact if the knowledge is absent, the evidence checked, and whether a durable document is the correct source.",
|
|
340
|
+
"When the decision is move-to-durable-doc, remove the entry from memory now and add one durableDocAssignment. Do not wait for the durable document update before removing memory.",
|
|
341
|
+
"Apply the reviewed result directly to the listed <VCM-memory> blocks. Do not change content outside those blocks.",
|
|
342
|
+
"If memory changes, commit only the changed active memory files with message [VCM Harness] Update VCM memory. If memory is unchanged, do not create a commit.",
|
|
343
|
+
`Write the complete machine-readable review to: ${memoryReview.reviewResultPath}`,
|
|
344
|
+
"The JSON root must be: {\"version\":1,\"runId\":\"<assigned run id>\",\"memoryCommit\":\"<full commit or none>\",\"decisions\":[],\"durableDocAssignments\":[]}.",
|
|
345
|
+
"Each decision must contain itemId, source (existing|proposal), target, entry, decision, reason, impactIfAbsent, evidence (non-empty array), finalContent, and durableDocPath.",
|
|
346
|
+
"Existing decisions use retain|update|remove|move-to-durable-doc. Add or Update proposal decisions use keep-in-memory|keep-memory-reference|move-to-durable-doc|reject; Remove proposal decisions use remove|retain. Proposal itemId must equal the assigned candidate ID.",
|
|
347
|
+
"Each durableDocAssignment must contain sourceMemoryPath, sourceEntry, targetPath, content, reason, and evidence (non-empty array). Use a project-relative Markdown target outside .ai/vcm.",
|
|
348
|
+
"In the retrospective report, include only this memory summary:",
|
|
311
349
|
"",
|
|
312
350
|
"## Memory Review",
|
|
313
|
-
"
|
|
314
|
-
|
|
315
|
-
"
|
|
316
|
-
...(memoryReview.proposalCandidates.length > 0
|
|
317
|
-
? memoryReview.proposalCandidates.flatMap(renderMemoryProposalDecisionTemplate)
|
|
318
|
-
: ["none"]),
|
|
319
|
-
"",
|
|
320
|
-
"### Existing Memory Decisions",
|
|
321
|
-
"#### Item 1",
|
|
322
|
-
"Target: shared",
|
|
323
|
-
"Existing: <exact existing memory entry>",
|
|
324
|
-
"Decision: retain",
|
|
325
|
-
"Reason: <why this decision is correct>",
|
|
326
|
-
"Impact if removed: <specific future role or task failure>",
|
|
327
|
-
"Durable doc disposition: memory",
|
|
328
|
-
"Durable doc path: none",
|
|
329
|
-
"Evidence: <current code, durable documentation, or final task evidence>",
|
|
330
|
-
"",
|
|
331
|
-
"### Existing Memory Changes",
|
|
332
|
-
"- retained: <summary or none>",
|
|
333
|
-
"- updated: <summary or none>",
|
|
334
|
-
"- removed: <summary or none>",
|
|
335
|
-
"",
|
|
336
|
-
"Reviewed memory set: complete"
|
|
351
|
+
"Memory commit: <full commit or none>",
|
|
352
|
+
`Review result: ${memoryReview.reviewResultPath}`,
|
|
353
|
+
"Durable document assignments: <count>"
|
|
337
354
|
]
|
|
338
355
|
: []),
|
|
339
356
|
"",
|
|
340
357
|
`Write the analysis to Result Path: ${resolveRepoPath(repoRoot, analysisPath)}`,
|
|
341
|
-
|
|
342
|
-
"End your turn after
|
|
358
|
+
"Write the report directly to that path. Do not use vcm-artifact.",
|
|
359
|
+
"End your turn after the report is complete."
|
|
343
360
|
].join("\n");
|
|
344
361
|
}
|
|
345
|
-
function renderMemoryProposalDecisionTemplate(candidate) {
|
|
346
|
-
if (candidate.operation === "remove") {
|
|
347
|
-
return [
|
|
348
|
-
`#### Candidate ${candidate.id}`,
|
|
349
|
-
`Source: ${candidate.source}`,
|
|
350
|
-
"Operation: remove",
|
|
351
|
-
`Target: ${candidate.target}`,
|
|
352
|
-
`Existing: ${candidate.existing}`,
|
|
353
|
-
"Decision: remove|retain",
|
|
354
|
-
"Reason: <why the proposed removal should be applied or rejected>",
|
|
355
|
-
"Evidence checked: <current code, durable documentation, or final task evidence>",
|
|
356
|
-
""
|
|
357
|
-
];
|
|
358
|
-
}
|
|
359
|
-
return [
|
|
360
|
-
`#### Candidate ${candidate.id}`,
|
|
361
|
-
`Source: ${candidate.source}`,
|
|
362
|
-
`Operation: ${candidate.operation}`,
|
|
363
|
-
`Target: ${candidate.target}`,
|
|
364
|
-
`Candidate: ${candidate.content}`,
|
|
365
|
-
"Decision: keep-in-memory|keep-memory-reference|move-to-durable-doc|reject",
|
|
366
|
-
"Final target: shared|project-manager|architect|coder|tester|reviewer|harness-engineer|none",
|
|
367
|
-
"Why memory is necessary: <independent reason, or why it is not necessary>",
|
|
368
|
-
"Impact if absent: <specific impact, or why no durable impact exists>",
|
|
369
|
-
"Durable doc disposition: memory|durable-doc|memory-reference",
|
|
370
|
-
"Durable doc analysis: <why this destination is correct>",
|
|
371
|
-
"Durable doc path: <none or a project-relative durable doc path>",
|
|
372
|
-
"Evidence checked: <current code, durable documentation, or final task evidence>",
|
|
373
|
-
"Final content: <exact one-line reviewed-memory content or none>",
|
|
374
|
-
""
|
|
375
|
-
];
|
|
376
|
-
}
|
|
377
362
|
function buildPendingFeedbackPrompt(repoRoot, feedbackPath) {
|
|
378
363
|
return [
|
|
379
364
|
"[VCM Harness Feedback]",
|
|
@@ -431,6 +416,7 @@ export function createHarnessFeedbackService(deps) {
|
|
|
431
416
|
sendPendingFeedback,
|
|
432
417
|
startTaskRetrospective,
|
|
433
418
|
handleTaskRetrospectiveHook,
|
|
419
|
+
completeWaitingTaskRetrospective,
|
|
434
420
|
assertHarnessEngineerAvailable
|
|
435
421
|
};
|
|
436
422
|
}
|
|
@@ -495,6 +481,18 @@ function assertPendingFeedbackPath(feedbackPath) {
|
|
|
495
481
|
throw new Error(`Invalid assigned Harness Feedback path: ${feedbackPath}.`);
|
|
496
482
|
}
|
|
497
483
|
}
|
|
484
|
+
function getRetrospectiveReportErrors(content) {
|
|
485
|
+
const errors = [];
|
|
486
|
+
if (!/^# Task Harness Retrospective(?::\s*.+)?\s*$/m.test(content)) {
|
|
487
|
+
errors.push("Retrospective report requires '# Task Harness Retrospective: <task>'.");
|
|
488
|
+
}
|
|
489
|
+
for (const heading of ["Findings", "Feedback Dispositions", "Recommended Harness Changes", "VCM Issue Drafts"]) {
|
|
490
|
+
if (!new RegExp(`^## ${heading}\\s*$`, "m").test(content)) {
|
|
491
|
+
errors.push(`Missing required section: ${heading}.`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return errors;
|
|
495
|
+
}
|
|
498
496
|
function parseSimpleMetadata(content) {
|
|
499
497
|
const result = {};
|
|
500
498
|
for (const line of content.split(/\r?\n/).slice(0, 80)) {
|
|
@@ -25,10 +25,12 @@ import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/v
|
|
|
25
25
|
import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
|
|
26
26
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
27
27
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
28
|
+
import { renderAskUserTool, renderVcmAskUserSkillRules } from "../templates/harness/vcm-ask-user-skill.js";
|
|
28
29
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
29
30
|
import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
|
|
30
31
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
31
32
|
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
33
|
+
import { renderResolveDurableDocAssignmentTool } from "../templates/harness/resolve-durable-doc-assignment.js";
|
|
32
34
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
33
35
|
import { VcmError } from "../errors.js";
|
|
34
36
|
import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
|
|
@@ -138,6 +140,14 @@ const HARNESS_FILES = [
|
|
|
138
140
|
ownership: "whole-file",
|
|
139
141
|
renderRules: renderVcmRouteMessageSkillRules
|
|
140
142
|
},
|
|
143
|
+
{
|
|
144
|
+
kind: "skill-vcm-ask-user",
|
|
145
|
+
path: ".claude/skills/vcm-ask-user/SKILL.md",
|
|
146
|
+
title: "VCM Ask User Skill",
|
|
147
|
+
frontmatter: renderSkillFrontmatter("vcm-ask-user", "Use whenever project-manager asks the user a question and must pause the workflow."),
|
|
148
|
+
ownership: "whole-file",
|
|
149
|
+
renderRules: renderVcmAskUserSkillRules
|
|
150
|
+
},
|
|
141
151
|
{
|
|
142
152
|
kind: "skill-vcm-task-state",
|
|
143
153
|
path: ".claude/skills/vcm-task-state/SKILL.md",
|
|
@@ -257,6 +267,13 @@ const HARNESS_FILES = [
|
|
|
257
267
|
ownership: "raw-file",
|
|
258
268
|
renderRules: renderRequestGateReviewTool
|
|
259
269
|
},
|
|
270
|
+
{
|
|
271
|
+
kind: "tool-vcm-ask-user",
|
|
272
|
+
path: ".ai/tools/vcm-ask-user",
|
|
273
|
+
title: "VCM Ask User Tool",
|
|
274
|
+
ownership: "raw-file",
|
|
275
|
+
renderRules: renderAskUserTool
|
|
276
|
+
},
|
|
260
277
|
{
|
|
261
278
|
kind: "tool-update-task-state",
|
|
262
279
|
path: ".ai/tools/update-task-state",
|
|
@@ -278,6 +295,13 @@ const HARNESS_FILES = [
|
|
|
278
295
|
ownership: "raw-file",
|
|
279
296
|
renderRules: renderRequestArchitectRestartTool
|
|
280
297
|
},
|
|
298
|
+
{
|
|
299
|
+
kind: "tool-resolve-durable-doc-assignment",
|
|
300
|
+
path: ".ai/tools/resolve-durable-doc-assignment",
|
|
301
|
+
title: "Resolve Durable Documentation Assignment Tool",
|
|
302
|
+
ownership: "raw-file",
|
|
303
|
+
renderRules: renderResolveDurableDocAssignmentTool
|
|
304
|
+
},
|
|
281
305
|
{
|
|
282
306
|
kind: "agent-project-manager",
|
|
283
307
|
path: ".claude/agents/project-manager.md",
|
|
@@ -360,7 +384,7 @@ export function createHarnessService(deps) {
|
|
|
360
384
|
let harnessCommit;
|
|
361
385
|
if (nextContent !== currentContent) {
|
|
362
386
|
await bumpHarnessRevision(deps.fs, repoRoot, now());
|
|
363
|
-
harnessCommit = (await commitHarnessVisibleChanges(deps.git, repoRoot, "
|
|
387
|
+
harnessCommit = (await commitHarnessVisibleChanges(deps.git, repoRoot, "[VCM Harness] Update harness file")).harnessCommit;
|
|
364
388
|
}
|
|
365
389
|
const file = await readHarnessFileContent(deps.fs, repoRoot, definition.path);
|
|
366
390
|
const [analyses, codeIntelligence] = await Promise.all([
|
|
@@ -384,7 +408,7 @@ export function createHarnessService(deps) {
|
|
|
384
408
|
if (result.changedFiles.length > 0) {
|
|
385
409
|
await bumpHarnessRevision(deps.fs, repoRoot, now());
|
|
386
410
|
}
|
|
387
|
-
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "
|
|
411
|
+
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "[VCM Harness] Update fixed harness");
|
|
388
412
|
return {
|
|
389
413
|
...result,
|
|
390
414
|
changedFiles: committed.changedFiles.length > 0 ? committed.changedFiles : result.changedFiles,
|
|
@@ -408,7 +432,7 @@ export function createHarnessService(deps) {
|
|
|
408
432
|
if (changedFiles.length > 0) {
|
|
409
433
|
await bumpHarnessRevision(deps.fs, repoRoot, now());
|
|
410
434
|
}
|
|
411
|
-
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "
|
|
435
|
+
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "[VCM Harness] Update fixed harness");
|
|
412
436
|
return {
|
|
413
437
|
version: VCM_HARNESS_VERSION,
|
|
414
438
|
changedFiles: committed.changedFiles.length > 0 ? committed.changedFiles : changedFiles,
|
|
@@ -66,7 +66,8 @@ export function createRuntimeCoordinatorService(deps) {
|
|
|
66
66
|
else {
|
|
67
67
|
await deps.translationService.stopTask(taskRepoRoot, activeTask.taskSlug).catch(() => undefined);
|
|
68
68
|
}
|
|
69
|
-
await reconcileAutoMemory(repoRoot, activeTask, preferences.autoTaskHarnessReviewEnabled ? "auto" : undefined);
|
|
69
|
+
const memoryState = await reconcileAutoMemory(repoRoot, activeTask, preferences.autoTaskHarnessReviewEnabled ? "auto" : undefined);
|
|
70
|
+
await deps.harnessFeedbackService.completeWaitingTaskRetrospective(repoRoot, activeTask.taskSlug, memoryState.status);
|
|
70
71
|
const memoryReadiness = await getTaskRetrospectiveMemoryReadiness(repoRoot, activeTask);
|
|
71
72
|
if ((preferences.autoTaskHarnessReviewEnabled || memoryReadiness.trigger) && memoryReadiness.ready) {
|
|
72
73
|
await maybeStartTaskHarnessRetrospective(repoRoot, activeTask, memoryReadiness.trigger ?? "auto");
|
|
@@ -59,6 +59,7 @@ function degradedArtifactSummary(handoffDir) {
|
|
|
59
59
|
architectDebugPath: `${handoffDir}/architect-debug.md`,
|
|
60
60
|
architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
|
|
61
61
|
testReportPath: `${handoffDir}/test-report.md`,
|
|
62
|
+
docsUpdateReportPath: `${handoffDir}/docs-update-report.md`,
|
|
62
63
|
docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
|
|
63
64
|
workflowProgressPath: `${handoffDir}/workflow-progress.md`,
|
|
64
65
|
finalAcceptancePath: `${handoffDir}/final-acceptance.md`
|
|
@@ -214,14 +214,23 @@ export function createTranslationWorkerService(deps) {
|
|
|
214
214
|
}
|
|
215
215
|
const session = await ensureTranslatorSession(repoRoot, next.targetLanguage, next.taskSlug);
|
|
216
216
|
await submitTerminalInput(deps.runtime, session.id, batch?.prompt ?? await buildQueuePrompt(repoRoot, next));
|
|
217
|
+
const latestQueue = await loadQueue(repoRoot);
|
|
218
|
+
if (latestQueue.activeItemId !== next.id) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const dispatchedItems = latestQueue.items.filter((item) => item.status === "dispatching" &&
|
|
222
|
+
(batch ? item.batchId === batch.items[0]?.batchId : item.id === next.id));
|
|
223
|
+
if (dispatchedItems.length === 0) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
217
226
|
const dispatchedAt = now();
|
|
218
|
-
for (const item of
|
|
227
|
+
for (const item of dispatchedItems) {
|
|
219
228
|
item.status = "running";
|
|
220
229
|
item.updatedAt = dispatchedAt;
|
|
221
230
|
}
|
|
222
|
-
|
|
223
|
-
await saveQueue(repoRoot,
|
|
224
|
-
await Promise.all(
|
|
231
|
+
latestQueue.updatedAt = dispatchedAt;
|
|
232
|
+
await saveQueue(repoRoot, latestQueue);
|
|
233
|
+
await Promise.all(dispatchedItems.map((item) => syncJobStatus(repoRoot, item)));
|
|
225
234
|
}
|
|
226
235
|
catch (error) {
|
|
227
236
|
const failedItems = queue.activeItemId === next.id
|
|
@@ -438,6 +447,12 @@ export function createTranslationWorkerService(deps) {
|
|
|
438
447
|
await validateActiveQueueItem(repoRoot);
|
|
439
448
|
return true;
|
|
440
449
|
}
|
|
450
|
+
// `dispatching` covers the interval between writing the prompt and the
|
|
451
|
+
// Translator's UserPromptSubmit hook. The role session is legitimately idle
|
|
452
|
+
// during that interval, so only an actual result can reconcile the item.
|
|
453
|
+
if (active.status === "dispatching") {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
441
456
|
if (await translatorSessionSettled(repoRoot, active.taskSlug)) {
|
|
442
457
|
await validateActiveQueueItem(repoRoot);
|
|
443
458
|
return true;
|
|
@@ -10,6 +10,15 @@ const HISTORY_SEPARATOR = "| --- | --- | --- | --- | --- | --- |";
|
|
|
10
10
|
const TARGET_ROLES = new Set(["architect", "coder", "tester"]);
|
|
11
11
|
const FINAL_GATE_STATUSES = new Set(["disabled", "not_required", "skipped", "overridden"]);
|
|
12
12
|
const MISSING_EVIDENCE_HASH = "<missing>";
|
|
13
|
+
const DOCS_ONLY_ROLE_TRANSITIONS = [
|
|
14
|
+
"docs-only/architect",
|
|
15
|
+
"docs-only/coder",
|
|
16
|
+
"docs-only/tester"
|
|
17
|
+
];
|
|
18
|
+
const DOCS_ONLY_EXIT_TRANSITIONS = [
|
|
19
|
+
"code-change/architect",
|
|
20
|
+
"validation-only/tester"
|
|
21
|
+
];
|
|
13
22
|
export function createWorkflowControlService(deps) {
|
|
14
23
|
const now = deps.now ?? (() => new Date().toISOString());
|
|
15
24
|
const id = deps.id ?? (() => `wfauth_${randomUUID()}`);
|
|
@@ -35,6 +44,7 @@ export function createWorkflowControlService(deps) {
|
|
|
35
44
|
return withLock(statePath(input), async () => {
|
|
36
45
|
const state = await getState(input);
|
|
37
46
|
failOnStateWarnings(state);
|
|
47
|
+
failWhileAwaitingUser(state);
|
|
38
48
|
if (state.pendingDispatch) {
|
|
39
49
|
throw workflowError("WORKFLOW_DISPATCH_PENDING", `A ${state.pendingDispatch.targetRole} dispatch is already ${state.pendingDispatch.status}.`, "Complete or recover the existing dispatch before proposing another workflow transition.");
|
|
40
50
|
}
|
|
@@ -112,6 +122,7 @@ export function createWorkflowControlService(deps) {
|
|
|
112
122
|
async function assertRouteAuthorized(input) {
|
|
113
123
|
const state = await getState(input);
|
|
114
124
|
failOnStateWarnings(state);
|
|
125
|
+
failWhileAwaitingUser(state);
|
|
115
126
|
const pending = state.pendingDispatch;
|
|
116
127
|
if (!pending || pending.status !== "pending") {
|
|
117
128
|
throw workflowError("WORKFLOW_ROUTE_NOT_APPROVED", "Project Manager has no pending workflow approval for this route.", "Submit a valid workflow-progress.md transition before the PM route message.");
|
|
@@ -165,6 +176,7 @@ export function createWorkflowControlService(deps) {
|
|
|
165
176
|
await withLock(statePath(input), async () => {
|
|
166
177
|
const state = await getState(input);
|
|
167
178
|
failOnStateWarnings(state);
|
|
179
|
+
failWhileAwaitingUser(state);
|
|
168
180
|
const pending = state.pendingDispatch;
|
|
169
181
|
if (!pending || pending.status !== "dispatching" || pending.messageId !== messageId) {
|
|
170
182
|
throw workflowError("WORKFLOW_DISPATCH_CONFIRMATION_MISMATCH", `Message ${messageId} does not own the pending workflow dispatch.`, "Do not advance Workflow Progress from an unrelated UserPromptSubmit event.");
|
|
@@ -207,12 +219,50 @@ export function createWorkflowControlService(deps) {
|
|
|
207
219
|
}
|
|
208
220
|
return {
|
|
209
221
|
getState,
|
|
222
|
+
getProgress: (input) => readProgress(deps.fs, input),
|
|
223
|
+
requestUserInput,
|
|
224
|
+
resolveUserInput,
|
|
210
225
|
submitProgress,
|
|
211
226
|
assertRouteAuthorized,
|
|
212
227
|
claimDispatch,
|
|
213
228
|
releaseDispatch,
|
|
214
229
|
confirmDispatch
|
|
215
230
|
};
|
|
231
|
+
async function requestUserInput(input, question) {
|
|
232
|
+
return withLock(statePath(input), async () => {
|
|
233
|
+
const state = await getState(input);
|
|
234
|
+
const normalizedQuestion = question.trim();
|
|
235
|
+
if (!normalizedQuestion) {
|
|
236
|
+
throw workflowError("WORKFLOW_USER_QUESTION_REQUIRED", "A non-empty user question is required.");
|
|
237
|
+
}
|
|
238
|
+
const timestamp = now();
|
|
239
|
+
const next = {
|
|
240
|
+
...state,
|
|
241
|
+
awaitingUser: {
|
|
242
|
+
question: normalizedQuestion,
|
|
243
|
+
requestedAt: timestamp
|
|
244
|
+
},
|
|
245
|
+
pendingDispatch: null,
|
|
246
|
+
updatedAt: timestamp
|
|
247
|
+
};
|
|
248
|
+
await saveState(input, next);
|
|
249
|
+
return next;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async function resolveUserInput(input) {
|
|
253
|
+
return withLock(statePath(input), async () => {
|
|
254
|
+
const state = await getState(input);
|
|
255
|
+
if (!state.awaitingUser)
|
|
256
|
+
return state;
|
|
257
|
+
const next = {
|
|
258
|
+
...state,
|
|
259
|
+
awaitingUser: null,
|
|
260
|
+
updatedAt: now()
|
|
261
|
+
};
|
|
262
|
+
await saveState(input, next);
|
|
263
|
+
return next;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
216
266
|
async function saveState(input, state) {
|
|
217
267
|
await deps.fs.writeJsonAtomic(statePath(input), state);
|
|
218
268
|
}
|
|
@@ -351,11 +401,15 @@ async function getAllowedTransitions(fs, input, state, current) {
|
|
|
351
401
|
const flow = current.flow;
|
|
352
402
|
const flowRun = resolveFlowRun(state.flowRun, current);
|
|
353
403
|
if (flow === "docs-only") {
|
|
354
|
-
const docs = await artifactState(fs, input, "docs-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
404
|
+
const docs = await artifactState(fs, input, "docs-update-report.md", "docs-update-report");
|
|
405
|
+
const active = state.activeDispatch;
|
|
406
|
+
if (!active || active.flow !== "docs-only") {
|
|
407
|
+
return [...DOCS_ONLY_ROLE_TRANSITIONS, ...DOCS_ONLY_EXIT_TRANSITIONS];
|
|
408
|
+
}
|
|
409
|
+
const hasFreshResult = evidenceProducedAfterActiveDispatch(state, flow, active.targetRole, "docs-update-report.md", docs.hash);
|
|
410
|
+
return hasFreshResult && docs.complete
|
|
411
|
+
? [...DOCS_ONLY_ROLE_TRANSITIONS, ...DOCS_ONLY_EXIT_TRANSITIONS]
|
|
412
|
+
: [`docs-only/${active.targetRole}`, ...DOCS_ONLY_EXIT_TRANSITIONS];
|
|
359
413
|
}
|
|
360
414
|
if (flow === "validation-only") {
|
|
361
415
|
const test = await artifactState(fs, input, "test-report.md", "test-report");
|
|
@@ -387,7 +441,7 @@ function initialTransitions() {
|
|
|
387
441
|
"code-change/architect",
|
|
388
442
|
"architect-debug/architect",
|
|
389
443
|
"architecture-diagnosis/architect",
|
|
390
|
-
|
|
444
|
+
...DOCS_ONLY_ROLE_TRANSITIONS,
|
|
391
445
|
"validation-only/tester"
|
|
392
446
|
];
|
|
393
447
|
}
|
|
@@ -554,8 +608,9 @@ async function artifactState(fs, input, fileName, kind) {
|
|
|
554
608
|
value = readArtifactSectionContent(content, "Final Disposition")?.trim().toLowerCase();
|
|
555
609
|
if (kind === "test-report")
|
|
556
610
|
value = inline("Test Result");
|
|
557
|
-
if (kind === "docs-sync-report" || kind === "final-acceptance")
|
|
611
|
+
if (kind === "docs-update-report" || kind === "docs-sync-report" || kind === "final-acceptance") {
|
|
558
612
|
value = readArtifactSectionContent(content, "Decision")?.trim().toLowerCase();
|
|
613
|
+
}
|
|
559
614
|
const infrastructure = kind === "test-report"
|
|
560
615
|
? /^Status:\s*(.+?)\s*$/mi.exec(readArtifactSectionContent(content, "Test Infrastructure") ?? "")?.[1]?.trim().toLowerCase()
|
|
561
616
|
: undefined;
|
|
@@ -786,10 +841,14 @@ async function validateCompletion(fs, input, state, candidate) {
|
|
|
786
841
|
return;
|
|
787
842
|
}
|
|
788
843
|
if (candidate.flow === "docs-only") {
|
|
789
|
-
const docs = await artifactState(fs, input, "docs-
|
|
790
|
-
|
|
844
|
+
const docs = await artifactState(fs, input, "docs-update-report.md", "docs-update-report");
|
|
845
|
+
const activeRole = state.activeDispatch?.flow === "docs-only"
|
|
846
|
+
? state.activeDispatch.targetRole
|
|
847
|
+
: undefined;
|
|
848
|
+
if (!activeRole
|
|
849
|
+
|| !evidenceProducedAfterActiveDispatch(state, candidate.flow, activeRole, "docs-update-report.md", docs.hash)
|
|
791
850
|
|| !docs.complete || (docs.value !== "synced" && docs.value !== "unchanged")) {
|
|
792
|
-
throw workflowError("WORKFLOW_COMPLETION_INVALID", "Docs-only completion requires a complete Docs
|
|
851
|
+
throw workflowError("WORKFLOW_COMPLETION_INVALID", "Docs-only completion requires a fresh, complete Docs Update Report from the latest assigned role with Decision: synced or Decision: unchanged.");
|
|
793
852
|
}
|
|
794
853
|
return;
|
|
795
854
|
}
|
|
@@ -911,12 +970,20 @@ function normalizeState(value, taskSlug, timestamp) {
|
|
|
911
970
|
const flowRun = value.flowRun === undefined || value.flowRun === null
|
|
912
971
|
? null
|
|
913
972
|
: isFlowRun(value.flowRun) ? value.flowRun : undefined;
|
|
914
|
-
|
|
973
|
+
const awaitingUser = value.awaitingUser === undefined || value.awaitingUser === null
|
|
974
|
+
? null
|
|
975
|
+
: isAwaitingUser(value.awaitingUser) ? value.awaitingUser : undefined;
|
|
976
|
+
if (pendingDispatch === undefined
|
|
977
|
+
|| activeDispatch === undefined
|
|
978
|
+
|| flowRun === undefined
|
|
979
|
+
|| userAuthorizations === undefined
|
|
980
|
+
|| awaitingUser === undefined) {
|
|
915
981
|
return { ...emptyState(taskSlug, timestamp), warnings: ["Workflow control state has an unsupported shape."] };
|
|
916
982
|
}
|
|
917
983
|
return {
|
|
918
984
|
version: 1,
|
|
919
985
|
taskSlug,
|
|
986
|
+
awaitingUser,
|
|
920
987
|
pendingDispatch,
|
|
921
988
|
activeDispatch,
|
|
922
989
|
flowRun,
|
|
@@ -929,6 +996,7 @@ function emptyState(taskSlug, timestamp) {
|
|
|
929
996
|
return {
|
|
930
997
|
version: 1,
|
|
931
998
|
taskSlug,
|
|
999
|
+
awaitingUser: null,
|
|
932
1000
|
pendingDispatch: null,
|
|
933
1001
|
activeDispatch: null,
|
|
934
1002
|
flowRun: null,
|
|
@@ -988,11 +1056,21 @@ function failOnStateWarnings(state) {
|
|
|
988
1056
|
if (state.warnings.length > 0)
|
|
989
1057
|
throw workflowError("WORKFLOW_STATE_INVALID", state.warnings.join(" "));
|
|
990
1058
|
}
|
|
1059
|
+
function failWhileAwaitingUser(state) {
|
|
1060
|
+
if (!state.awaitingUser)
|
|
1061
|
+
return;
|
|
1062
|
+
throw workflowError("WORKFLOW_AWAITING_USER", "Project Manager is waiting for the user's answer and cannot advance the workflow.", "Wait for a new direct user message. The previous workflow approval was canceled; request a fresh approval after the answer arrives.");
|
|
1063
|
+
}
|
|
991
1064
|
function progressValidationError(errors) {
|
|
992
1065
|
return workflowError("WORKFLOW_PROGRESS_INVALID", `Workflow Progress validation failed:\n${errors.map((error) => `- ${error}`).join("\n")}`);
|
|
993
1066
|
}
|
|
994
1067
|
function workflowError(code, message, hint) {
|
|
995
|
-
return new VcmError({
|
|
1068
|
+
return new VcmError({
|
|
1069
|
+
code,
|
|
1070
|
+
message,
|
|
1071
|
+
hint,
|
|
1072
|
+
statusCode: code.includes("PENDING") || code.includes("AWAITING_USER") ? 409 : 422
|
|
1073
|
+
});
|
|
996
1074
|
}
|
|
997
1075
|
async function writeAtomic(fs, target, content) {
|
|
998
1076
|
if (fs.writeTextAtomic)
|
|
@@ -1003,6 +1081,12 @@ async function writeAtomic(fs, target, content) {
|
|
|
1003
1081
|
function isRecord(value) {
|
|
1004
1082
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
1005
1083
|
}
|
|
1084
|
+
function isAwaitingUser(value) {
|
|
1085
|
+
return isRecord(value)
|
|
1086
|
+
&& typeof value.question === "string"
|
|
1087
|
+
&& value.question.trim().length > 0
|
|
1088
|
+
&& typeof value.requestedAt === "string";
|
|
1089
|
+
}
|
|
1006
1090
|
function isPendingDispatch(value) {
|
|
1007
1091
|
if (!isRecord(value))
|
|
1008
1092
|
return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ARCHITECT_DEBUG_STATUSES, ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_DIAGNOSIS_DISPOSITIONS, ARCHITECTURE_EVIDENCE_STATUSES, ARCHITECTURE_PLAN_RESULTS, CODER_COMPLETION_DECISIONS,
|
|
1
|
+
import { ARCHITECT_DEBUG_STATUSES, ARCHITECTURE_BRIEF_STATUSES, ARCHITECTURE_DIAGNOSIS_DISPOSITIONS, ARCHITECTURE_EVIDENCE_STATUSES, ARCHITECTURE_PLAN_RESULTS, CODER_COMPLETION_DECISIONS, DOCS_REPORT_DECISIONS, FINAL_ACCEPTANCE_DECISIONS, L3_ACTIONS, L3_REQUIRED_VALUES, PLANNING_PROGRESS_STATUSES, STRICT_NONE_VALUE, TEST_INFRASTRUCTURE_STATUSES, TEST_RESULTS, renderArtifactOptions } from "../../shared/validation/artifact-contract.js";
|
|
2
2
|
const CURRENT_HANDOFF_NOTICE = "<!-- VCM current handoff: replace this file with one complete, self-contained snapshot of the current result. Restate all still-relevant evidence; do not refer to a prior revision, route message, Session, or transcript as evidence. -->";
|
|
3
3
|
export function renderArchitectureBriefTemplate(taskSlug) {
|
|
4
4
|
return `# Architecture Brief: ${taskSlug}
|
|
@@ -518,7 +518,49 @@ TBD
|
|
|
518
518
|
|
|
519
519
|
## Decision
|
|
520
520
|
|
|
521
|
-
${renderArtifactOptions(
|
|
521
|
+
${renderArtifactOptions(DOCS_REPORT_DECISIONS)}
|
|
522
|
+
`;
|
|
523
|
+
}
|
|
524
|
+
export function renderDocsUpdateReportTemplate(taskSlug, assignmentId = "docs-only") {
|
|
525
|
+
return `# Docs Update Report: ${taskSlug}
|
|
526
|
+
|
|
527
|
+
${CURRENT_HANDOFF_NOTICE}
|
|
528
|
+
|
|
529
|
+
## Summary
|
|
530
|
+
|
|
531
|
+
TBD
|
|
532
|
+
|
|
533
|
+
## Assignment ID
|
|
534
|
+
|
|
535
|
+
${assignmentId}
|
|
536
|
+
|
|
537
|
+
## Documents Updated
|
|
538
|
+
|
|
539
|
+
TBD
|
|
540
|
+
|
|
541
|
+
## Documents Reviewed And Left Unchanged
|
|
542
|
+
|
|
543
|
+
TBD
|
|
544
|
+
|
|
545
|
+
## Evidence Reviewed
|
|
546
|
+
|
|
547
|
+
TBD
|
|
548
|
+
|
|
549
|
+
## Checks Performed
|
|
550
|
+
|
|
551
|
+
TBD
|
|
552
|
+
|
|
553
|
+
## Commit
|
|
554
|
+
|
|
555
|
+
TBD
|
|
556
|
+
|
|
557
|
+
## Remaining Documentation Issues
|
|
558
|
+
|
|
559
|
+
TBD
|
|
560
|
+
|
|
561
|
+
## Decision
|
|
562
|
+
|
|
563
|
+
${renderArtifactOptions(DOCS_REPORT_DECISIONS)}
|
|
522
564
|
`;
|
|
523
565
|
}
|
|
524
566
|
export function renderWorkflowProgressTemplate(taskSlug) {
|