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.
- package/README.md +64 -39
- package/dist/backend/adapters/claude-adapter.js +2 -2
- package/dist/backend/adapters/codex-bridge-adapter.js +213 -0
- package/dist/backend/api/app-settings-routes.js +6 -6
- 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 +11 -9
- package/dist/backend/services/app-settings-service.js +11 -11
- 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/{ccr-integration-service.js → codex-bridge-integration-service.js} +71 -71
- 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/session-service.js +16 -16
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/translation-worker-service.js +19 -4
- package/dist/backend/services/usage-analytics-service.js +3 -3
- 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/session.js +28 -22
- 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-DDmygnV6.css +32 -0
- package/dist-frontend/assets/{index-VW9tYPP5.js → index-nAV6toi8.js} +47 -47
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +21 -6
- package/scripts/{ccr-api-key-helper.mjs → codex-bridge-api-key-helper.mjs} +1 -1
- package/scripts/harness-tools/vcm-artifact +1 -2
- package/scripts/harness-tools/vcm-bash-guard +1 -1
- package/dist/backend/adapters/ccr-gateway-adapter.js +0 -172
- 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, "
|
|
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");
|
|
@@ -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 {
|
|
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.
|
|
602
|
-
if (
|
|
601
|
+
if (!deps.codexBridgeIntegration) {
|
|
602
|
+
if (isCodexBridgeSessionModel(model)) {
|
|
603
603
|
throw new VcmError({
|
|
604
|
-
code: "
|
|
605
|
-
message: "
|
|
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
|
|
607
|
+
hint: "Enable and configure Codex Bridge before starting this session."
|
|
608
608
|
});
|
|
609
609
|
}
|
|
610
610
|
return {};
|
|
611
611
|
}
|
|
612
|
-
return deps.
|
|
612
|
+
return deps.codexBridgeIntegration.getLaunchEnvironment(model);
|
|
613
613
|
}
|
|
614
614
|
async function getModelLaunchSettingsOverride(model) {
|
|
615
|
-
return deps.
|
|
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
|
|
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 (
|
|
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
|
|
1668
|
+
hint: "Use Restart to switch between native Claude and Codex Bridge models."
|
|
1669
1669
|
});
|
|
1670
1670
|
}
|
|
1671
|
-
if (
|
|
1671
|
+
if (isCodexBridgeSessionModel(requestedModel) && !session.claudeConfigDir) {
|
|
1672
1672
|
throw new VcmError({
|
|
1673
|
-
code: "
|
|
1674
|
-
message: `${session.role} was created
|
|
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
|
|
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 ||
|
|
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
|
|
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;
|
|
@@ -76,7 +76,7 @@ function extractApiRequestEvents(payload) {
|
|
|
76
76
|
continue;
|
|
77
77
|
}
|
|
78
78
|
const model = normalizeModel(readString(attributes.model));
|
|
79
|
-
if (
|
|
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
|
|
337
|
+
function isCodexBridgeModel(model) {
|
|
338
338
|
const normalized = model.toLowerCase();
|
|
339
|
-
return normalized.startsWith("gpt-") || normalized.startsWith("
|
|
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-
|
|
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) {
|
|
@@ -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
|
-
-
|
|
28
|
-
-
|
|
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
|
-
-
|
|
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\`
|
|
340
|
-
- In Docs-Only Flow, submit the complete report before returning to PM.
|
|
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.
|