vibe-coding-master 0.7.43 → 0.7.45

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 (54) hide show
  1. package/README.md +64 -39
  2. package/dist/backend/adapters/claude-adapter.js +2 -2
  3. package/dist/backend/adapters/codex-bridge-adapter.js +213 -0
  4. package/dist/backend/api/app-settings-routes.js +6 -6
  5. package/dist/backend/api/artifact-routes.js +3 -0
  6. package/dist/backend/api/harness-routes.js +16 -0
  7. package/dist/backend/api/task-routes.js +1 -0
  8. package/dist/backend/api/workflow-control-routes.js +12 -0
  9. package/dist/backend/cli/install-vcm-harness.js +21 -0
  10. package/dist/backend/server.js +11 -9
  11. package/dist/backend/services/app-settings-service.js +11 -11
  12. package/dist/backend/services/artifact-service.js +7 -30
  13. package/dist/backend/services/auto-memory-service.js +640 -12
  14. package/dist/backend/services/claude-hook-service.js +80 -2
  15. package/dist/backend/services/{ccr-integration-service.js → codex-bridge-integration-service.js} +71 -71
  16. package/dist/backend/services/gate-review-service.js +173 -74
  17. package/dist/backend/services/harness-feedback-service.js +76 -78
  18. package/dist/backend/services/harness-service.js +27 -3
  19. package/dist/backend/services/runtime-coordinator-service.js +2 -1
  20. package/dist/backend/services/session-service.js +16 -16
  21. package/dist/backend/services/status-service.js +1 -0
  22. package/dist/backend/services/translation-worker-service.js +19 -4
  23. package/dist/backend/services/usage-analytics-service.js +3 -3
  24. package/dist/backend/services/workflow-control-service.js +96 -12
  25. package/dist/backend/templates/handoff.js +44 -2
  26. package/dist/backend/templates/harness/architect-agent.js +13 -7
  27. package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +1 -1
  28. package/dist/backend/templates/harness/check-scaffold-ledger.js +234 -10
  29. package/dist/backend/templates/harness/claude-root.js +3 -2
  30. package/dist/backend/templates/harness/coder-agent.js +8 -0
  31. package/dist/backend/templates/harness/gate-review.js +144 -49
  32. package/dist/backend/templates/harness/harness-engineer-agent.js +25 -10
  33. package/dist/backend/templates/harness/project-manager-agent.js +16 -10
  34. package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +60 -0
  35. package/dist/backend/templates/harness/tester-agent.js +13 -0
  36. package/dist/backend/templates/harness/vcm-ask-user-skill.js +82 -0
  37. package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -5
  38. package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -2
  39. package/dist/backend/templates/harness/vcm-workflow-review-skill.js +1 -1
  40. package/dist/shared/types/session.js +28 -22
  41. package/dist/shared/types/workflow.js +1 -0
  42. package/dist/shared/validation/artifact-check.js +3 -3
  43. package/dist/shared/validation/artifact-contract.js +1 -1
  44. package/dist/shared/validation/artifact-registry.js +16 -0
  45. package/dist-frontend/assets/index-DDmygnV6.css +32 -0
  46. package/dist-frontend/assets/{index-VW9tYPP5.js → index-nAV6toi8.js} +47 -47
  47. package/dist-frontend/index.html +2 -2
  48. package/package.json +1 -1
  49. package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +21 -6
  50. package/scripts/{ccr-api-key-helper.mjs → codex-bridge-api-key-helper.mjs} +1 -1
  51. package/scripts/harness-tools/vcm-artifact +1 -2
  52. package/scripts/harness-tools/vcm-bash-guard +1 -1
  53. package/dist/backend/adapters/ccr-gateway-adapter.js +0 -172
  54. package/dist-frontend/assets/index-B0d4Z6ny.css +0 -32
@@ -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, "chore(vcm-harness): update harness file")).harnessCommit;
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, "chore(vcm-harness): update fixed harness");
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, "chore(vcm-harness): update fixed harness");
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");
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
4
- import { CCR_GPT_SESSION_MODEL, isCcrSessionModel } from "../../shared/types/session.js";
4
+ import { isCodexBridgeSessionModel } from "../../shared/types/session.js";
5
5
  import { VcmError } from "../errors.js";
6
6
  import { resolveRepoPath } from "../adapters/filesystem.js";
7
7
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
@@ -598,21 +598,21 @@ export function createSessionService(deps) {
598
598
  return migrateRunningProjectToolSessionCwd(repoRoot, resumed, targetCwd);
599
599
  }
600
600
  async function getModelLaunchEnvironment(model) {
601
- if (!deps.ccrIntegration) {
602
- if (isCcrSessionModel(model)) {
601
+ if (!deps.codexBridgeIntegration) {
602
+ if (isCodexBridgeSessionModel(model)) {
603
603
  throw new VcmError({
604
- code: "CCR_UNAVAILABLE",
605
- message: "CCR integration is not available in this VCM runtime.",
604
+ code: "CODEX_BRIDGE_UNAVAILABLE",
605
+ message: "Codex Bridge integration is not available in this VCM runtime.",
606
606
  statusCode: 409,
607
- hint: "Enable and configure CCR GPT models before starting this session."
607
+ hint: "Enable and configure Codex Bridge before starting this session."
608
608
  });
609
609
  }
610
610
  return {};
611
611
  }
612
- return deps.ccrIntegration.getLaunchEnvironment(model);
612
+ return deps.codexBridgeIntegration.getLaunchEnvironment(model);
613
613
  }
614
614
  async function getModelLaunchSettingsOverride(model) {
615
- return deps.ccrIntegration?.getLaunchSettingsOverride(model);
615
+ return deps.codexBridgeIntegration?.getLaunchSettingsOverride(model);
616
616
  }
617
617
  function isRuntimeSessionAlive(session) {
618
618
  if (!session || isExitedStatus(session.status) || session.pid === undefined) {
@@ -1642,7 +1642,7 @@ function normalizeClaudeModel(value) {
1642
1642
  || value === "claude-opus-4-8"
1643
1643
  || value === "sonnet"
1644
1644
  || value === "fable"
1645
- || value === CCR_GPT_SESSION_MODEL) {
1645
+ || isCodexBridgeSessionModel(value)) {
1646
1646
  return value;
1647
1647
  }
1648
1648
  return "default";
@@ -1660,20 +1660,20 @@ function normalizeClaudeEffort(value) {
1660
1660
  }
1661
1661
  function assertResumeProviderCompatible(session, requestedModel) {
1662
1662
  const persistedModel = normalizeClaudeModel(session.model);
1663
- if (isCcrSessionModel(persistedModel) !== isCcrSessionModel(requestedModel)) {
1663
+ if (isCodexBridgeSessionModel(persistedModel) !== isCodexBridgeSessionModel(requestedModel)) {
1664
1664
  throw new VcmError({
1665
1665
  code: "SESSION_PROVIDER_SWITCH_REQUIRES_RESTART",
1666
1666
  message: `Cannot resume ${session.role} with a different model provider.`,
1667
1667
  statusCode: 409,
1668
- hint: "Use Restart to switch between native Claude and CCR models."
1668
+ hint: "Use Restart to switch between native Claude and Codex Bridge models."
1669
1669
  });
1670
1670
  }
1671
- if (isCcrSessionModel(requestedModel) && !session.claudeConfigDir) {
1671
+ if (isCodexBridgeSessionModel(requestedModel) && !session.claudeConfigDir) {
1672
1672
  throw new VcmError({
1673
- code: "CCR_SESSION_CONFIG_MISSING",
1674
- message: `${session.role} was created before isolated CCR session storage was enabled.`,
1673
+ code: "CODEX_BRIDGE_SESSION_CONFIG_MISSING",
1674
+ message: `${session.role} was created without isolated Codex Bridge session storage.`,
1675
1675
  statusCode: 409,
1676
- hint: "Restart this role once to create an isolated CCR session."
1676
+ hint: "Restart this role once to create an isolated Codex Bridge session."
1677
1677
  });
1678
1678
  }
1679
1679
  }
@@ -1713,7 +1713,7 @@ function buildUsageTelemetryEnvironment(apiUrl, role, model) {
1713
1713
  OTEL_LOG_TOOL_DETAILS: "0",
1714
1714
  OTEL_LOG_RAW_API_BODIES: "0"
1715
1715
  };
1716
- if (!apiUrl || isCcrSessionModel(model)) {
1716
+ if (!apiUrl || isCodexBridgeSessionModel(model)) {
1717
1717
  return disabled;
1718
1718
  }
1719
1719
  return {
@@ -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 batch?.items ?? [next]) {
227
+ for (const item of dispatchedItems) {
219
228
  item.status = "running";
220
229
  item.updatedAt = dispatchedAt;
221
230
  }
222
- queue.updatedAt = dispatchedAt;
223
- await saveQueue(repoRoot, queue);
224
- await Promise.all((batch?.items ?? [next]).map((item) => syncJobStatus(repoRoot, item)));
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;
@@ -76,7 +76,7 @@ function extractApiRequestEvents(payload) {
76
76
  continue;
77
77
  }
78
78
  const model = normalizeModel(readString(attributes.model));
79
- if (isCcrModel(model)) {
79
+ if (isCodexBridgeModel(model)) {
80
80
  continue;
81
81
  }
82
82
  const sequence = readInteger(attributes["event.sequence"]);
@@ -334,9 +334,9 @@ function normalizeModel(input) {
334
334
  const model = input?.slice(0, 200).trim();
335
335
  return model && isSafeGroupKey(model) ? model : "unknown";
336
336
  }
337
- function isCcrModel(model) {
337
+ function isCodexBridgeModel(model) {
338
338
  const normalized = model.toLowerCase();
339
- return normalized.startsWith("gpt-") || normalized.startsWith("ccr:") || normalized.includes("codex api/");
339
+ return normalized.startsWith("gpt-") || normalized.startsWith("codex-bridge:");
340
340
  }
341
341
  function isSafeGroupKey(value) {
342
342
  return value !== "__proto__" && value !== "prototype" && value !== "constructor";
@@ -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-sync-report.md", "docs-sync-report");
355
- return evidenceIsFresh(state, flow, "architect", "docs-sync-report.md", docs.hash)
356
- && docs.complete && (docs.value === "synced" || docs.value === "unchanged")
357
- ? []
358
- : ["docs-only/architect", "code-change/architect", "validation-only/tester"];
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
- "docs-only/architect",
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-sync-report.md", "docs-sync-report");
790
- if (!evidenceProducedAfterActiveDispatch(state, candidate.flow, "architect", "docs-sync-report.md", docs.hash)
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 Sync Report with Decision: synced or Decision: unchanged.");
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
- if (pendingDispatch === undefined || activeDispatch === undefined || flowRun === undefined || userAuthorizations === undefined) {
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({ code, message, hint, statusCode: code.includes("PENDING") ? 409 : 422 });
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, DOCS_SYNC_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";
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(DOCS_SYNC_DECISIONS)}
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) {
@@ -24,8 +24,9 @@ ${renderRoleMemoryRules("architect")}
24
24
  - Follow the preloaded \`vcm-code-navigation\` skill whenever work requires code definitions, implementations, references, callers, callees, or behavior paths. This applies in every Architect mode and in direct user communication.
25
25
  - LSP is mandatory and directly available for navigating project source code. Start source-code navigation with LSP. If it is unavailable, report a VCM LSP configuration failure; do not substitute source text search.
26
26
  - Use LSP definitions, implementations, references, document or workspace symbols, and incoming or outgoing calls for all source-code symbol and relationship queries.
27
- - Do not use the built-in \`Grep\` tool or shell text-search commands such as \`grep\`, \`rg\`, \`git grep\`, \`ag\`, or \`ack\` to search project source code, test code, or executable scripts. This prohibition includes locating or inferring definitions, implementations, references, callers, callees, symbols, and behavior paths.
28
- - Use Glob to locate files and Read to inspect complete code. Text search is allowed only for non-source artifacts that LSP does not model, such as documentation, configuration or data files, generated context, and logs. To inspect source comments, locate the source through LSP or Glob and use Read.
27
+ - LSP reference results are symbol-specific. An accessor, its backing field, a trait declaration, its implementation method, a wrapper, and an alias are separate symbols. Query every relevant symbol separately; one symbol's reference result cannot prove a complete semantic class. When starting from an accessor or wrapper, read its implementation, resolve its backing field, delegate, or trait item with LSP, then query those symbols too.
28
+ - Do not use the built-in \`Grep\` tool or shell text-search commands such as \`grep\`, \`rg\`, \`git grep\`, \`ag\`, or \`ack\` to search project source code, test code, or executable scripts, except for the bounded fallback below. This prohibition includes locating or inferring definitions, implementations, references, callers, callees, symbols, and behavior paths.
29
+ - Use Glob to locate files and Read to inspect complete code. Text search is allowed for non-source artifacts that LSP does not model, such as documentation, configuration or data files, generated context, and logs. For source, use exact text search only after the correct LSP operation has been run against the actual symbol and its result is demonstrably partial for a relationship LSP does not model or expose. Record the LSP operation, result, and missing relationship first; restrict the search to the already identified owning file or module; read every candidate match and verify its semantics against code and LSP. Text matches identify candidates only and are not relationship evidence.
29
30
  - Cost, latency, round trips, batching, parallelism, or an expectation of equivalent results never permits source-code text search. Wait for the role-session LSP and follow the skill's bounded retry procedure.
30
31
  - If LSP cannot resolve a required project-owned relationship, record it as unresolved. Do not replace semantic evidence with text matches.
31
32
 
@@ -109,7 +110,7 @@ ${renderRoleMemoryRules("architect")}
109
110
  - \`Scaffold Manifest\`: an item ledger — one entry per implementation item. Use columns in the exact order \`ID | Action | File | ...\`; use an ID matching \`AA-1\` through \`AAAAAA-9999\`, an Action of exactly \`create\`, \`change\`, or \`delete\`, and a backticked repo-relative File path. An item is one created body or surface, one required change site — one contiguous edit region inside an existing body or surface — or one deletion of a body, site, or file. An item not in the ledger is not in the plan; coder must not implement it. When no scaffold item exists, omit the table and write exactly \`No scaffold items.\`; an empty table is invalid.
110
111
  - Each ledger entry carries, in this column order: a unique stable ID such as \`SCF-001\`, action, exact file path, symbol or site, coder work, allowed implementation freedom, and a behavior/contract proof point. Per-file evidence, why-in-scope, and durable-comment needs live in the Module/File Plan, not in the ledger. Open-ended coverage language ("as work proceeds", "replicate", "etc.", "and others") is forbidden anywhere in the ledger.
111
112
  - IDs and markers correspond one to one: every \`create\`, \`change\`, and \`delete\` entry has exactly one \`VCM:CODE <ID>\` marker pre-placed at its declared file and site; a \`delete\` marker sits on the code to be removed and leaves with it.
112
- - The Scaffold Manifest is complete only when the ledger ID set and the tree's \`VCM:CODE\` ID set are equal, each ID appears exactly once on each side, and each marker sits in its declared file (\`.ai/tools/check-scaffold-ledger\` automates the check). Any mismatch means the plan is not complete.
113
+ - The Scaffold Manifest is complete only when the ledger ID set and the tree's \`VCM:CODE\` ID set are equal, each ID appears exactly once on each side, and each marker sits in its declared file (\`.ai/tools/check-scaffold-ledger --mode scaffold\` automates the check). Any mismatch means the plan is not complete.
113
114
  - \`Scaffold Build Evidence\`: the exact compile/typecheck commands run on the committed scaffold, their results, and the scaffold commit hash. A missing, red, or stale-hash result means the plan is not complete.
114
115
  - When the plan introduces or changes a build configuration — a new compilation target, a feature-gated or restricted-runtime variant, a new artifact type, or a build-environment constraint — scaffold that configuration and add to \`Scaffold Build Evidence\` one named check per configuration that fails when the claim is false, run green at the scaffold commit; choose proving checks from the project coding standards when defined there. A build-configuration claim without its named green check means the plan is not complete.
115
116
  - The compile/typecheck and build-configuration checks in \`Scaffold Build Evidence\` must cover every wired exemplar, so each new cross-module call path is proven compilable at the scaffold stage. A call path that exists only in plan prose, over stub scaffold that never names the surfaces it will invoke, is not proven and the plan is not complete.
@@ -212,7 +213,7 @@ The code-reading phase is complete only when:
212
213
  - every indirect callback, event, hook, queue, route, and dynamic dispatch path has been resolved
213
214
  - every relevant state reader and writer has been read
214
215
  - every relevant cross-file surface caller and consumer has been read
215
- - every semantic relationship records LSP, runtime, external-boundary, or generated-boundary evidence
216
+ - every semantic relationship records LSP, runtime, verified bounded source, external-boundary, or generated-boundary evidence
216
217
  - no unresolved project-owned symbol remains
217
218
 
218
219
  Do not diagnose the root cause or choose a fix before the Code Reading Closure is complete.
@@ -281,7 +282,12 @@ Each rewritten \`architecture-diagnosis.md\` must be a complete, self-contained
281
282
 
282
283
  ### Docs Sync
283
284
 
284
- - In Docs-Only Flow, verify claims against current code and durable docs, update the PM-assigned project documents directly, run applicable documentation checks, and commit the changes; tester completion is not required.
285
+ - For a \`[VCM Durable Documentation Assignment]\`, update the assigned durable
286
+ document and directly related documentation, run applicable documentation
287
+ checks, commit the result, and submit \`docs-update-report.md\` with the exact
288
+ assigned Assignment ID. This backend assignment does not start Docs-Only Flow.
289
+
290
+ - In Docs-Only Flow, verify claims against current code and durable docs, update the PM-assigned project documents and related documentation needed for consistency, run applicable documentation checks, commit the changes, and submit \`.ai/vcm/handoffs/docs-update-report.md\` through \`vcm-artifact\` before reporting to PM.
285
291
  - In Code-Change Flow, Architect Debug Flow, and a code-producing Architecture Diagnosis Flow, perform post-validation docs sync only when project-manager requests it after tester completes.
286
292
  - Architect Debug Branch and Architecture Diagnosis Branch do not run their own docs sync.
287
293
 
@@ -336,8 +342,8 @@ Each rewritten \`architecture-diagnosis.md\` must be a complete, self-contained
336
342
 
337
343
  #### Docs Sync Report
338
344
 
339
- - Write \`.ai/vcm/handoffs/docs-sync-report.md\` as the final result of Docs-Only Flow and for post-validation docs sync in Code-Change Flow, Architect Debug Flow, or a code-producing Architecture Diagnosis Flow. Do not write it for a Debug/Diagnosis Branch.
340
- - In Docs-Only Flow, submit the complete report before returning to PM. The report must record the decision, changed documents, evidence reviewed, checks performed, and commit.
345
+ - Write \`.ai/vcm/handoffs/docs-sync-report.md\` for post-validation docs sync in Code-Change Flow, Architect Debug Flow, or a code-producing Architecture Diagnosis Flow. Do not write it for Docs-Only Flow or a Debug/Diagnosis Branch.
346
+ - In Docs-Only Flow, submit the complete \`.ai/vcm/handoffs/docs-update-report.md\` before returning to PM. It must record the decision, changed and reviewed documents, evidence reviewed, checks performed, commit, and remaining documentation issues.
341
347
  - The report records decision, evidence reviewed, current-truth reconciliation, generated-context freshness, cross-document consistency, architecture docs, active plans, testing-doc consistency, known-issues disposition, durable-doc audit command and result, docs updated, docs left unchanged, remaining documentation risks, and handoff notes.
342
348
  - Each rewritten \`docs-sync-report.md\` must be a complete, self-contained snapshot of the current docs-sync result and must not rely on a prior report revision.
343
349
  - \`Decision\` must be \`synced\`, \`unchanged\`, or \`blocked\`.
@@ -14,7 +14,7 @@ You are \`vcm-architect-scaffold-worker\`, a foreground subagent invoked by Arch
14
14
 
15
15
  ### Validation And Commit
16
16
 
17
- - Run \`.ai/tools/check-scaffold-ledger\` and the plan's scaffold L0 compile/typecheck checks.
17
+ - Run \`.ai/tools/check-scaffold-ledger --mode scaffold\` and the plan's scaffold L0 compile/typecheck checks.
18
18
  - Commit only the scaffold changes after the ledger reconciles and required checks pass.
19
19
  - Return the commit hash, changed files, ledger result, and exact check results to Architect.
20
20
  - If the assigned scaffold cannot be completed, return the concrete failure evidence without changing the plan.