vibe-coding-master 0.7.38 → 0.7.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -6
- package/dist/backend/adapters/claude-adapter.js +1 -0
- package/dist/backend/api/artifact-routes.js +72 -1
- package/dist/backend/api/runtime-state-routes.js +17 -5
- package/dist/backend/api/task-routes.js +6 -0
- package/dist/backend/api/workflow-control-routes.js +56 -0
- package/dist/backend/cli/install-vcm-harness.js +46 -22
- package/dist/backend/server.js +21 -5
- package/dist/backend/services/artifact-service.js +309 -5
- package/dist/backend/services/auto-memory-service.js +2 -2
- package/dist/backend/services/code-intelligence-service.js +5 -5
- package/dist/backend/services/gate-review-service.js +81 -33
- package/dist/backend/services/harness-feedback-service.js +11 -4
- package/dist/backend/services/harness-service.js +42 -26
- package/dist/backend/services/message-service.js +105 -37
- package/dist/backend/services/status-service.js +6 -0
- package/dist/backend/services/workflow-control-service.js +854 -0
- package/dist/backend/templates/handoff.js +158 -3
- package/dist/backend/templates/harness/architect-agent.js +3 -3
- package/dist/backend/templates/harness/check-scaffold-ledger.js +105 -39
- package/dist/backend/templates/harness/claude-root.js +9 -1
- package/dist/backend/templates/harness/coder-agent.js +2 -3
- package/dist/backend/templates/harness/coder-worker-agent.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +4 -5
- package/dist/backend/templates/harness/harness-engineer-agent.js +18 -1
- package/dist/backend/templates/harness/project-manager-agent.js +3 -2
- package/dist/backend/templates/harness/tester-agent.js +1 -0
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +5 -0
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -6
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +4 -2
- package/dist/backend/templates/harness/vcm-propose-memory-skill.js +4 -2
- package/dist/backend/templates/harness/vcm-report-harness-issue-skill.js +2 -1
- package/dist/backend/templates/harness/vcm-route-message-skill.js +4 -7
- package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -13
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +62 -0
- package/dist/backend/templates/message-envelope.js +4 -4
- package/dist/shared/types/workflow.js +7 -1
- package/dist/shared/validation/artifact-check.js +99 -95
- package/dist/shared/validation/artifact-contract.js +9 -0
- package/dist/shared/validation/artifact-registry.js +211 -0
- package/dist-frontend/assets/{index-VillnFRo.js → index-Bocc2DWF.js} +34 -34
- package/dist-frontend/assets/{index-CXSOe-NN.css → index-C_XHGNBD.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/run-long-check +38 -8
- package/scripts/harness-tools/vcm-artifact +148 -0
- package/scripts/harness-tools/vcm-bash-guard +42 -47
- package/scripts/harness-tools/watch-job +58 -4
package/README.md
CHANGED
|
@@ -324,6 +324,13 @@ Automatic mode:
|
|
|
324
324
|
- the UI switches to the target role before dispatch
|
|
325
325
|
- Claude Code hooks confirm whether the prompt was accepted
|
|
326
326
|
|
|
327
|
+
Before Project Manager can dispatch Architect, Coder, or Tester, it submits a
|
|
328
|
+
strict `workflow-progress.md` transition through `vcm-workflow-review`. VCM
|
|
329
|
+
checks the confirmed dispatch history and current task artifacts, then grants
|
|
330
|
+
one matching route. The target role's `UserPromptSubmit` consumes that approval
|
|
331
|
+
and appends the confirmed transition. A rejected transition can be bypassed
|
|
332
|
+
only by an exact one-time user authorization recorded through the VCM dialog.
|
|
333
|
+
|
|
327
334
|
If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
|
|
328
335
|
only controls the looping sound. Enabling Gateway turns that preference off once;
|
|
329
336
|
it can be turned back on afterward. A new Gateway command closes an open pause
|
|
@@ -455,7 +462,7 @@ Harness Studio is the UI for VCM harness maintenance.
|
|
|
455
462
|
Use it to:
|
|
456
463
|
|
|
457
464
|
- inspect fixed harness status
|
|
458
|
-
- inspect detected project languages and
|
|
465
|
+
- inspect detected project languages and language-server availability
|
|
459
466
|
- run bootstrap
|
|
460
467
|
- open Harness Engineer
|
|
461
468
|
- review harness files
|
|
@@ -471,11 +478,10 @@ VCM bundles the Claude Code LSP bridge and loads it for Architect, Coder, and
|
|
|
471
478
|
Reviewer sessions, including CCR launches. The project environment must still
|
|
472
479
|
provide the language server for each detected language: `rust-analyzer`,
|
|
473
480
|
`typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
|
|
474
|
-
`jdtls`.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
with the backend diagnostic.
|
|
481
|
+
`jdtls`. Their Agent definitions preload `vcm-code-navigation`. Architect,
|
|
482
|
+
Coder, and Reviewer use LSP for semantic relationships. Harness Studio reports
|
|
483
|
+
whether the server executable and plugin can run; the role Session performs the
|
|
484
|
+
real workspace warm-up and semantic query retries.
|
|
479
485
|
|
|
480
486
|
Harness Engineer is task-scoped and runs from the active task worktree. The
|
|
481
487
|
backend automatically starts a fresh Harness Engineer for each active task or
|
|
@@ -20,6 +20,7 @@ export function createClaudeAdapter(runner) {
|
|
|
20
20
|
},
|
|
21
21
|
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt, pluginDirs = []) {
|
|
22
22
|
const args = pluginDirs.flatMap((pluginDir) => ["--plugin-dir", pluginDir]);
|
|
23
|
+
args.push("--allowedTools", "Glob,Grep");
|
|
23
24
|
args.push("--agent", role);
|
|
24
25
|
const sessionSettings = { ...settingsOverride };
|
|
25
26
|
if (claudeSessionId) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isDispatchableRole } from "../../shared/constants.js";
|
|
1
|
+
import { isDispatchableRole, isRoleName } from "../../shared/constants.js";
|
|
2
2
|
import { VcmError } from "../errors.js";
|
|
3
3
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
4
|
export function registerArtifactRoutes(app, deps) {
|
|
@@ -52,6 +52,59 @@ export function registerArtifactRoutes(app, deps) {
|
|
|
52
52
|
});
|
|
53
53
|
return { ok: true };
|
|
54
54
|
});
|
|
55
|
+
app.post("/api/tasks/:taskSlug/artifacts/submit", async (request) => {
|
|
56
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
57
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
58
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
59
|
+
const body = request.body;
|
|
60
|
+
if (!body || !isRoleName(body.role)) {
|
|
61
|
+
throw new VcmError({
|
|
62
|
+
code: "ARTIFACT_ROLE_INVALID",
|
|
63
|
+
message: `Unknown artifact role: ${body?.role ?? "missing"}`,
|
|
64
|
+
statusCode: 400
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
if (body.mode !== "draft" && body.mode !== "final") {
|
|
68
|
+
throw new VcmError({
|
|
69
|
+
code: "ARTIFACT_MODE_INVALID",
|
|
70
|
+
message: "Artifact mode must be draft or final.",
|
|
71
|
+
statusCode: 400
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
if (typeof body.content !== "string") {
|
|
75
|
+
throw new VcmError({
|
|
76
|
+
code: "ARTIFACT_CONTENT_INVALID",
|
|
77
|
+
message: "Artifact content must be text.",
|
|
78
|
+
statusCode: 400
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const session = body.role === "translator"
|
|
82
|
+
? await deps.sessionService.getProjectTranslatorSession(project.repoRoot)
|
|
83
|
+
: body.role === "harness-engineer"
|
|
84
|
+
? await deps.sessionService.getRoleSession(project.repoRoot, task.taskSlug, body.role)
|
|
85
|
+
?? await deps.sessionService.getProjectHarnessEngineerSession(project.repoRoot)
|
|
86
|
+
: await deps.sessionService.getRoleSession(project.repoRoot, task.taskSlug, body.role);
|
|
87
|
+
if (!session || !session.runtimeSessionToken || session.runtimeSessionToken !== body.runtimeSessionToken) {
|
|
88
|
+
throw new VcmError({
|
|
89
|
+
code: "ARTIFACT_SESSION_INVALID",
|
|
90
|
+
message: `${body.role} does not have the active VCM Session that owns this artifact submission.`,
|
|
91
|
+
statusCode: 409,
|
|
92
|
+
hint: "Submit from the active role terminal with .ai/tools/vcm-artifact."
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return deps.artifactService.submitArtifact({
|
|
96
|
+
repoRoot: getTaskRuntimeRepoRoot(task),
|
|
97
|
+
baseRepoRoot: project.repoRoot,
|
|
98
|
+
stateRoot: config.stateRoot,
|
|
99
|
+
handoffDir: task.handoffDir,
|
|
100
|
+
taskSlug: task.taskSlug,
|
|
101
|
+
kind: body.kind,
|
|
102
|
+
mode: body.mode,
|
|
103
|
+
role: body.role,
|
|
104
|
+
content: body.content,
|
|
105
|
+
artifactPath: body.path
|
|
106
|
+
});
|
|
107
|
+
});
|
|
55
108
|
}
|
|
56
109
|
function parseDispatchableRole(role) {
|
|
57
110
|
if (!isDispatchableRole(role)) {
|
|
@@ -67,18 +120,36 @@ function artifactNameToPath(paths, artifactName) {
|
|
|
67
120
|
if (artifactName === "architecture-brief.md") {
|
|
68
121
|
return paths.architectureBriefPath;
|
|
69
122
|
}
|
|
123
|
+
if (artifactName === "architecture-evidence.md") {
|
|
124
|
+
return paths.architectureEvidencePath;
|
|
125
|
+
}
|
|
126
|
+
if (artifactName === "planning-progress.md") {
|
|
127
|
+
return paths.planningProgressPath;
|
|
128
|
+
}
|
|
70
129
|
if (artifactName === "architecture-plan.md") {
|
|
71
130
|
return paths.architecturePlanPath;
|
|
72
131
|
}
|
|
73
132
|
if (artifactName === "known-issues.md") {
|
|
74
133
|
return paths.knownIssuesPath;
|
|
75
134
|
}
|
|
135
|
+
if (artifactName === "coder-completion.md") {
|
|
136
|
+
return paths.coderCompletionPath;
|
|
137
|
+
}
|
|
138
|
+
if (artifactName === "architect-debug.md") {
|
|
139
|
+
return paths.architectDebugPath;
|
|
140
|
+
}
|
|
141
|
+
if (artifactName === "architecture-diagnosis.md") {
|
|
142
|
+
return paths.architectureDiagnosisPath;
|
|
143
|
+
}
|
|
76
144
|
if (artifactName === "test-report.md") {
|
|
77
145
|
return paths.testReportPath;
|
|
78
146
|
}
|
|
79
147
|
if (artifactName === "docs-sync-report.md") {
|
|
80
148
|
return paths.docsSyncReportPath;
|
|
81
149
|
}
|
|
150
|
+
if (artifactName === "workflow-progress.md") {
|
|
151
|
+
return paths.workflowProgressPath;
|
|
152
|
+
}
|
|
82
153
|
if (artifactName === "final-acceptance.md") {
|
|
83
154
|
return paths.finalAcceptancePath;
|
|
84
155
|
}
|
|
@@ -23,15 +23,25 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
23
23
|
harnessBootstrapStatus: null,
|
|
24
24
|
harnessFeedbackState,
|
|
25
25
|
autoMemoryState: null,
|
|
26
|
-
gatewayStatus: coordinated.gatewayStatus
|
|
26
|
+
gatewayStatus: coordinated.gatewayStatus,
|
|
27
|
+
workflowControlState: null
|
|
27
28
|
};
|
|
28
29
|
}
|
|
29
30
|
try {
|
|
30
31
|
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
31
|
-
const
|
|
32
|
+
const config = deps.workflowControlService
|
|
33
|
+
? await deps.projectService.loadConfig(project.repoRoot)
|
|
34
|
+
: null;
|
|
35
|
+
const [harnessStatus, harnessBootstrapStatus, autoMemoryState, workflowControlState] = await Promise.all([
|
|
32
36
|
withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
|
|
33
37
|
withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath, task.taskSlug), (error) => degradedBootstrapStatus(error)),
|
|
34
|
-
deps.autoMemoryService.getState(project.repoRoot, task.worktreePath)
|
|
38
|
+
deps.autoMemoryService.getState(project.repoRoot, task.worktreePath),
|
|
39
|
+
deps.workflowControlService && config ? deps.workflowControlService.getState({
|
|
40
|
+
taskRepoRoot: task.worktreePath,
|
|
41
|
+
stateRoot: config.stateRoot,
|
|
42
|
+
handoffDir: task.handoffDir,
|
|
43
|
+
taskSlug: task.taskSlug
|
|
44
|
+
}) : Promise.resolve(null)
|
|
35
45
|
]);
|
|
36
46
|
return {
|
|
37
47
|
translatorSession,
|
|
@@ -41,7 +51,8 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
41
51
|
harnessBootstrapStatus,
|
|
42
52
|
harnessFeedbackState,
|
|
43
53
|
autoMemoryState,
|
|
44
|
-
gatewayStatus: coordinated.gatewayStatus
|
|
54
|
+
gatewayStatus: coordinated.gatewayStatus,
|
|
55
|
+
workflowControlState
|
|
45
56
|
};
|
|
46
57
|
}
|
|
47
58
|
catch (error) {
|
|
@@ -54,7 +65,8 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
54
65
|
harnessBootstrapStatus: degradedBootstrapStatus(error),
|
|
55
66
|
harnessFeedbackState,
|
|
56
67
|
autoMemoryState: null,
|
|
57
|
-
gatewayStatus: coordinated.gatewayStatus
|
|
68
|
+
gatewayStatus: coordinated.gatewayStatus,
|
|
69
|
+
workflowControlState: null
|
|
58
70
|
};
|
|
59
71
|
}
|
|
60
72
|
throw error;
|
|
@@ -198,10 +198,16 @@ function degradedArtifactSummary(handoffDir) {
|
|
|
198
198
|
roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
|
|
199
199
|
messageRoutePaths: {},
|
|
200
200
|
architectureBriefPath: `${handoffDir}/architecture-brief.md`,
|
|
201
|
+
architectureEvidencePath: `${handoffDir}/architecture-evidence.md`,
|
|
202
|
+
planningProgressPath: `${handoffDir}/planning-progress.md`,
|
|
201
203
|
architecturePlanPath: `${handoffDir}/architecture-plan.md`,
|
|
202
204
|
knownIssuesPath: `${handoffDir}/known-issues.md`,
|
|
205
|
+
coderCompletionPath: `${handoffDir}/coder-completion.md`,
|
|
206
|
+
architectDebugPath: `${handoffDir}/architect-debug.md`,
|
|
207
|
+
architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
|
|
203
208
|
testReportPath: `${handoffDir}/test-report.md`,
|
|
204
209
|
docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
|
|
210
|
+
workflowProgressPath: `${handoffDir}/workflow-progress.md`,
|
|
205
211
|
finalAcceptancePath: `${handoffDir}/final-acceptance.md`
|
|
206
212
|
},
|
|
207
213
|
checks: []
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { VcmError } from "../errors.js";
|
|
2
|
+
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
3
|
+
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
|
+
export function registerWorkflowControlRoutes(app, deps) {
|
|
5
|
+
app.get("/api/tasks/:taskSlug/workflow-control", async (request) => {
|
|
6
|
+
const context = await getContext(deps, request.params.taskSlug);
|
|
7
|
+
return deps.workflowControlService.getState(context);
|
|
8
|
+
});
|
|
9
|
+
app.post("/api/tasks/:taskSlug/workflow-overrides/:overrideId/approve", async (request) => {
|
|
10
|
+
const authorizationText = request.body?.authorizationText?.trim();
|
|
11
|
+
if (!authorizationText) {
|
|
12
|
+
throw new VcmError({
|
|
13
|
+
code: "WORKFLOW_OVERRIDE_AUTHORIZATION_REQUIRED",
|
|
14
|
+
message: "Enter the exact workflow exception that the user authorizes.",
|
|
15
|
+
statusCode: 400
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const context = await getContext(deps, request.params.taskSlug);
|
|
19
|
+
const state = await deps.workflowControlService.approveOverride(context, request.params.overrideId, authorizationText);
|
|
20
|
+
await notifyProjectManager(deps, context, request.params.overrideId, "approved", authorizationText);
|
|
21
|
+
return state;
|
|
22
|
+
});
|
|
23
|
+
app.post("/api/tasks/:taskSlug/workflow-overrides/:overrideId/reject", async (request) => {
|
|
24
|
+
const context = await getContext(deps, request.params.taskSlug);
|
|
25
|
+
const state = await deps.workflowControlService.rejectOverride(context, request.params.overrideId);
|
|
26
|
+
await notifyProjectManager(deps, context, request.params.overrideId, "rejected");
|
|
27
|
+
return state;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function getContext(deps, taskSlug) {
|
|
31
|
+
const project = await deps.projectService.getCurrentProject();
|
|
32
|
+
if (!project) {
|
|
33
|
+
throw new VcmError({ code: "PROJECT_NOT_CONNECTED", message: "Connect a repository first.", statusCode: 409 });
|
|
34
|
+
}
|
|
35
|
+
const [config, task] = await Promise.all([
|
|
36
|
+
deps.projectService.loadConfig(project.repoRoot),
|
|
37
|
+
deps.taskService.loadTask(project.repoRoot, taskSlug)
|
|
38
|
+
]);
|
|
39
|
+
return {
|
|
40
|
+
repoRoot: project.repoRoot,
|
|
41
|
+
taskRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
42
|
+
stateRoot: config.stateRoot,
|
|
43
|
+
handoffDir: task.handoffDir,
|
|
44
|
+
taskSlug: task.taskSlug
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function notifyProjectManager(deps, context, overrideId, decision, authorizationText) {
|
|
48
|
+
const session = await deps.sessionService.getRoleSession(context.repoRoot, context.taskSlug, "project-manager");
|
|
49
|
+
if (!session || session.status !== "running" || session.activityStatus === "running")
|
|
50
|
+
return;
|
|
51
|
+
const prompt = decision === "approved"
|
|
52
|
+
? `[VCM Workflow Override Decision]\nDecision: approved\nAuthorization ID: ${overrideId}\nAuthorization Text: ${authorizationText}\n\nResubmit workflow-progress.md with this Authorization ID and exact Authorization Text.`
|
|
53
|
+
: `[VCM Workflow Override Decision]\nDecision: rejected\nAuthorization ID: ${overrideId}\n\nKeep the current workflow and choose a legal next dispatch.`;
|
|
54
|
+
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
55
|
+
await deps.sessionService.markRoleActivityRunning(context.repoRoot, context.taskSlug, "project-manager", session.id);
|
|
56
|
+
}
|
|
@@ -28,6 +28,7 @@ import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propo
|
|
|
28
28
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
29
29
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
30
30
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
31
|
+
import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
|
|
31
32
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
32
33
|
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
33
34
|
import { readVcmPackageVersion } from "../app-version.js";
|
|
@@ -53,7 +54,7 @@ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
|
|
|
53
54
|
const VCM_AUTO_MEMORY_ENABLED = false;
|
|
54
55
|
const VCM_HOOK_DEFINITIONS = [
|
|
55
56
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
56
|
-
{ eventName: "PreToolUse", matcher: "
|
|
57
|
+
{ eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
57
58
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
58
59
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
59
60
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
@@ -66,18 +67,21 @@ const AGENT_FRONTMATTER = {
|
|
|
66
67
|
},
|
|
67
68
|
architect: {
|
|
68
69
|
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
|
|
69
|
-
tools: "Read, Glob, Bash, Edit, Write, Agent, LSP"
|
|
70
|
+
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP",
|
|
71
|
+
skills: ["vcm-code-navigation"]
|
|
70
72
|
},
|
|
71
73
|
coder: {
|
|
72
74
|
description: "VCM implementation role for scoped code changes and focused tests.",
|
|
73
|
-
tools: "Read, Glob, Bash, Edit, Write, Agent, LSP"
|
|
75
|
+
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent, LSP",
|
|
76
|
+
skills: ["vcm-code-navigation"]
|
|
74
77
|
},
|
|
75
78
|
tester: {
|
|
76
79
|
description: "VCM testing role for validation, test adequacy, approved-scope validation, and risk findings."
|
|
77
80
|
},
|
|
78
81
|
reviewer: {
|
|
79
82
|
description: "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.",
|
|
80
|
-
tools: "Read, Glob, Bash, Write, LSP"
|
|
83
|
+
tools: "Read, Grep, Glob, Bash, Write, LSP",
|
|
84
|
+
skills: ["vcm-code-navigation"]
|
|
81
85
|
},
|
|
82
86
|
translator: {
|
|
83
87
|
description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
|
|
@@ -96,14 +100,14 @@ const AGENT_FRONTMATTER = {
|
|
|
96
100
|
}
|
|
97
101
|
};
|
|
98
102
|
const REQUIRED_AGENT_TOOLS = {
|
|
99
|
-
architect: ["Agent", "LSP"],
|
|
100
|
-
coder: ["Agent", "LSP"],
|
|
101
|
-
reviewer: ["LSP"]
|
|
103
|
+
architect: ["Grep", "Agent", "LSP"],
|
|
104
|
+
coder: ["Grep", "Agent", "LSP"],
|
|
105
|
+
reviewer: ["Grep", "LSP"]
|
|
102
106
|
};
|
|
103
|
-
const
|
|
104
|
-
architect: ["
|
|
105
|
-
coder: ["
|
|
106
|
-
reviewer: ["
|
|
107
|
+
const REQUIRED_AGENT_SKILLS = {
|
|
108
|
+
architect: ["vcm-code-navigation"],
|
|
109
|
+
coder: ["vcm-code-navigation"],
|
|
110
|
+
reviewer: ["vcm-code-navigation"]
|
|
107
111
|
};
|
|
108
112
|
const MANAGED_FILES = [
|
|
109
113
|
{
|
|
@@ -304,6 +308,12 @@ const WHOLE_FILES = [
|
|
|
304
308
|
mode: 0o644,
|
|
305
309
|
content: renderSkillFile("VCM Task State Skill", "vcm-task-state", "Use only as project-manager to declare the current task workflow checkpoint to VCM.", renderVcmTaskStateSkillRules())
|
|
306
310
|
},
|
|
311
|
+
{
|
|
312
|
+
path: ".claude/skills/vcm-workflow-review/SKILL.md",
|
|
313
|
+
category: "skill",
|
|
314
|
+
mode: 0o644,
|
|
315
|
+
content: renderSkillFile("VCM Workflow Review Skill", "vcm-workflow-review", "Use before every project-manager dispatch to Architect, Coder, or Tester so VCM can approve the workflow transition.", renderVcmWorkflowReviewSkillRules())
|
|
316
|
+
},
|
|
307
317
|
{
|
|
308
318
|
path: ".claude/skills/vcm-gate-review/SKILL.md",
|
|
309
319
|
category: "skill",
|
|
@@ -369,6 +379,12 @@ const WHOLE_FILES = [
|
|
|
369
379
|
category: "runtime-tool",
|
|
370
380
|
mode: 0o755,
|
|
371
381
|
templatePath: "scripts/harness-tools/vcm-bash-guard"
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
path: ".ai/tools/vcm-artifact",
|
|
385
|
+
category: "runtime-tool",
|
|
386
|
+
mode: 0o755,
|
|
387
|
+
templatePath: "scripts/harness-tools/vcm-artifact"
|
|
372
388
|
}
|
|
373
389
|
];
|
|
374
390
|
const LEGACY_FLAT_SKILL_FILES = [
|
|
@@ -611,6 +627,7 @@ function fixedDirectories() {
|
|
|
611
627
|
".claude/skills/vcm-long-running-validation/",
|
|
612
628
|
".claude/skills/vcm-route-message/",
|
|
613
629
|
".claude/skills/vcm-task-state/",
|
|
630
|
+
".claude/skills/vcm-workflow-review/",
|
|
614
631
|
".claude/skills/vcm-gate-review/",
|
|
615
632
|
".claude/skills/vcm-report-harness-issue/",
|
|
616
633
|
".claude/skills/vcm-propose-memory/",
|
|
@@ -678,8 +695,8 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
678
695
|
for (const requiredTool of REQUIRED_AGENT_TOOLS[definition.agentName] ?? []) {
|
|
679
696
|
nextContent = ensureAgentTool(nextContent, requiredTool);
|
|
680
697
|
}
|
|
681
|
-
for (const
|
|
682
|
-
nextContent =
|
|
698
|
+
for (const requiredSkill of REQUIRED_AGENT_SKILLS[definition.agentName] ?? []) {
|
|
699
|
+
nextContent = ensureAgentSkill(nextContent, requiredSkill);
|
|
683
700
|
}
|
|
684
701
|
await writeIfChanged({
|
|
685
702
|
targetPath,
|
|
@@ -723,7 +740,10 @@ function renderNewManagedFile(definition, block) {
|
|
|
723
740
|
const tools = frontmatter.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
|
|
724
741
|
const model = frontmatter.model ? `\nmodel: ${frontmatter.model}` : "";
|
|
725
742
|
const effort = frontmatter.effort ? `\neffort: ${frontmatter.effort}` : "";
|
|
726
|
-
|
|
743
|
+
const skills = frontmatter.skills?.length
|
|
744
|
+
? `\nskills:\n${frontmatter.skills.map((skill) => ` - ${skill}`).join("\n")}`
|
|
745
|
+
: "";
|
|
746
|
+
return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: ${tools}${model}${effort}${skills}\n---\n\n# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
727
747
|
}
|
|
728
748
|
return `# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
729
749
|
}
|
|
@@ -743,21 +763,25 @@ function ensureAgentTool(content, requiredTool) {
|
|
|
743
763
|
const nextTools = [...tools, requiredTool].join(", ");
|
|
744
764
|
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
745
765
|
}
|
|
746
|
-
function
|
|
766
|
+
function ensureAgentSkill(content, requiredSkill) {
|
|
747
767
|
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
748
768
|
if (!frontmatterMatch) {
|
|
749
769
|
return content;
|
|
750
770
|
}
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
771
|
+
const frontmatter = frontmatterMatch[0];
|
|
772
|
+
const skillsMatch = frontmatter.match(/^skills:[ \t]*(?:\r?\n((?:\s+-\s+[^\r\n]+\r?\n?)*))?/m);
|
|
773
|
+
if (!skillsMatch) {
|
|
774
|
+
return content.replace(frontmatter, frontmatter.replace(/\r?\n---$/, `\nskills:\n - ${requiredSkill}\n---`));
|
|
754
775
|
}
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
|
|
776
|
+
const listedSkills = (skillsMatch[1] ?? "")
|
|
777
|
+
.split(/\r?\n/)
|
|
778
|
+
.map((line) => line.match(/^\s+-\s+(.+)$/)?.[1]?.trim())
|
|
779
|
+
.filter(Boolean);
|
|
780
|
+
if (listedSkills.includes(requiredSkill)) {
|
|
758
781
|
return content;
|
|
759
782
|
}
|
|
760
|
-
|
|
783
|
+
const nextSkills = `${skillsMatch[0].trimEnd()}\n - ${requiredSkill}`;
|
|
784
|
+
return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], nextSkills));
|
|
761
785
|
}
|
|
762
786
|
function migrateLegacyManagedFile(definition, currentContent, block) {
|
|
763
787
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
package/dist/backend/server.js
CHANGED
|
@@ -45,6 +45,7 @@ import { createTerminalInterruptService } from "./services/terminal-interrupt-se
|
|
|
45
45
|
import { createTerminalProcessExitService } from "./services/terminal-process-exit-service.js";
|
|
46
46
|
import { createTranslationService } from "./services/translation-service.js";
|
|
47
47
|
import { createUsageAnalyticsService } from "./services/usage-analytics-service.js";
|
|
48
|
+
import { createWorkflowControlService } from "./services/workflow-control-service.js";
|
|
48
49
|
import { createDiagnosticsService } from "./services/diagnostics-service.js";
|
|
49
50
|
import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
50
51
|
import { registerArtifactRoutes } from "./api/artifact-routes.js";
|
|
@@ -60,6 +61,7 @@ import { registerSessionRoutes } from "./api/session-routes.js";
|
|
|
60
61
|
import { registerTaskRoutes } from "./api/task-routes.js";
|
|
61
62
|
import { registerTranslationRoutes } from "./api/translation-routes.js";
|
|
62
63
|
import { registerUsageAnalyticsRoutes } from "./api/usage-analytics-routes.js";
|
|
64
|
+
import { registerWorkflowControlRoutes } from "./api/workflow-control-routes.js";
|
|
63
65
|
import { registerTerminalWs } from "./ws/terminal-ws.js";
|
|
64
66
|
import { toVcmError } from "./errors.js";
|
|
65
67
|
import { readVcmPackageVersion } from "./app-version.js";
|
|
@@ -120,7 +122,8 @@ export async function createServer(deps, options = {}) {
|
|
|
120
122
|
harnessService: deps.harnessService,
|
|
121
123
|
harnessFeedbackService: deps.harnessFeedbackService,
|
|
122
124
|
autoMemoryService: deps.autoMemoryService,
|
|
123
|
-
runtimeCoordinator: deps.runtimeCoordinator
|
|
125
|
+
runtimeCoordinator: deps.runtimeCoordinator,
|
|
126
|
+
workflowControlService: deps.workflowControlService
|
|
124
127
|
});
|
|
125
128
|
registerTaskRoutes(app, {
|
|
126
129
|
projectService: deps.projectService,
|
|
@@ -144,8 +147,18 @@ export async function createServer(deps, options = {}) {
|
|
|
144
147
|
registerArtifactRoutes(app, {
|
|
145
148
|
projectService: deps.projectService,
|
|
146
149
|
taskService: deps.taskService,
|
|
147
|
-
artifactService: deps.artifactService
|
|
148
|
-
|
|
150
|
+
artifactService: deps.artifactService,
|
|
151
|
+
sessionService: deps.sessionService
|
|
152
|
+
});
|
|
153
|
+
if (deps.workflowControlService) {
|
|
154
|
+
registerWorkflowControlRoutes(app, {
|
|
155
|
+
projectService: deps.projectService,
|
|
156
|
+
taskService: deps.taskService,
|
|
157
|
+
sessionService: deps.sessionService,
|
|
158
|
+
workflowControlService: deps.workflowControlService,
|
|
159
|
+
runtime: deps.runtime
|
|
160
|
+
});
|
|
161
|
+
}
|
|
149
162
|
registerMessageRoutes(app, {
|
|
150
163
|
projectService: deps.projectService,
|
|
151
164
|
taskService: deps.taskService,
|
|
@@ -228,7 +241,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
228
241
|
});
|
|
229
242
|
const runtime = createNodePtyTerminalRuntime({ fs });
|
|
230
243
|
const registry = createSessionRegistry();
|
|
231
|
-
const
|
|
244
|
+
const workflowControlService = createWorkflowControlService({ fs });
|
|
245
|
+
const artifactService = createArtifactService(fs, { workflowControlService });
|
|
232
246
|
const projectService = createProjectService({ fs, git, appSettings });
|
|
233
247
|
const taskService = createTaskService({ fs, git, artifactService, projectService });
|
|
234
248
|
const taskWorkflowService = createTaskWorkflowService({ fs });
|
|
@@ -292,6 +306,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
292
306
|
sessionService,
|
|
293
307
|
taskService,
|
|
294
308
|
taskWorkflowService,
|
|
309
|
+
workflowControlService,
|
|
295
310
|
onRouteDelivered: ({ repoRoot, taskSlug, message }) => architectRestartService.recordRouteDelivered(repoRoot, taskSlug, message)
|
|
296
311
|
});
|
|
297
312
|
const taskLaunchService = createTaskLaunchService({
|
|
@@ -462,7 +477,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
462
477
|
terminalProcessExitService,
|
|
463
478
|
runtime,
|
|
464
479
|
diagnosticsService,
|
|
465
|
-
usageAnalyticsService
|
|
480
|
+
usageAnalyticsService,
|
|
481
|
+
workflowControlService
|
|
466
482
|
};
|
|
467
483
|
}
|
|
468
484
|
export function getDefaultStaticDir() {
|