vibe-coding-master 0.7.42 → 0.7.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -17
- 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 +32 -2
- package/dist/backend/api/workflow-control-routes.js +7 -26
- package/dist/backend/cli/install-vcm-harness.js +61 -6
- package/dist/backend/role-tool-policy.js +1 -1
- package/dist/backend/server.js +14 -5
- package/dist/backend/services/artifact-service.js +10 -32
- package/dist/backend/services/auto-memory-service.js +642 -14
- package/dist/backend/services/claude-hook-service.js +183 -6
- package/dist/backend/services/gate-review-service.js +173 -74
- package/dist/backend/services/harness-feedback-service.js +180 -80
- package/dist/backend/services/harness-service.js +65 -9
- package/dist/backend/services/memory-review-paths.js +13 -0
- package/dist/backend/services/role-stall-detector-service.js +322 -0
- package/dist/backend/services/round-service.js +25 -0
- package/dist/backend/services/runtime-coordinator-service.js +12 -1
- package/dist/backend/services/session-service.js +70 -3
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/translation-worker-service.js +19 -4
- package/dist/backend/services/workflow-control-service.js +524 -204
- package/dist/backend/templates/handoff.js +46 -5
- package/dist/backend/templates/harness/architect-agent.js +12 -6
- 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 +12 -5
- package/dist/backend/templates/harness/gate-review.js +150 -57
- package/dist/backend/templates/harness/harness-engineer-agent.js +38 -13
- package/dist/backend/templates/harness/project-manager-agent.js +18 -12
- 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 -6
- package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +7 -9
- package/dist/shared/types/role-stall.js +1 -0
- package/dist/shared/types/workflow.js +15 -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 +17 -1
- package/dist-frontend/assets/{index-C_XHGNBD.css → index-B0d4Z6ny.css} +1 -1
- package/dist-frontend/assets/index-BvCmrFlN.js +97 -0
- 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/harness-tools/vcm-artifact +1 -2
- package/scripts/harness-tools/vcm-bash-guard +204 -14
- package/dist-frontend/assets/index-Bocc2DWF.js +0 -97
package/README.md
CHANGED
|
@@ -190,6 +190,12 @@ restart, but it does not infer transitions or choose the next role. Current
|
|
|
190
190
|
artifacts, Gate Review state, Round/Turn state, and the role rules remain
|
|
191
191
|
authoritative.
|
|
192
192
|
|
|
193
|
+
Whenever Project Manager asks the user a question, it first uses
|
|
194
|
+
`vcm-ask-user`. VCM records the wait and cancels any pending role dispatch in
|
|
195
|
+
one state update. Stop Hook then ends the PM turn without route scanning. Only
|
|
196
|
+
a new direct user message clears the wait, and the next role dispatch requires
|
|
197
|
+
a fresh Workflow Review.
|
|
198
|
+
|
|
193
199
|
Typical flow:
|
|
194
200
|
|
|
195
201
|
```text
|
|
@@ -329,7 +335,8 @@ strict `workflow-progress.md` transition through `vcm-workflow-review`. VCM
|
|
|
329
335
|
checks the confirmed dispatch history and current task artifacts, then grants
|
|
330
336
|
one matching route. The target role's `UserPromptSubmit` consumes that approval
|
|
331
337
|
and appends the confirmed transition. A rejected transition can be bypassed
|
|
332
|
-
only by
|
|
338
|
+
only by the user's exact one-time authorization recorded in the next Workflow
|
|
339
|
+
Progress submission.
|
|
333
340
|
|
|
334
341
|
If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
|
|
335
342
|
only controls the looping sound. Enabling Gateway turns that preference off once;
|
|
@@ -474,14 +481,22 @@ Use it to:
|
|
|
474
481
|
- merge task harness commits back to the connected repository branch when
|
|
475
482
|
appropriate
|
|
476
483
|
|
|
477
|
-
VCM bundles the Claude Code LSP bridge and loads it for Architect
|
|
478
|
-
|
|
484
|
+
VCM bundles the Claude Code LSP bridge and loads it only for Architect sessions,
|
|
485
|
+
including CCR launches. The project environment must still
|
|
479
486
|
provide the language server for each detected language: `rust-analyzer`,
|
|
480
487
|
`typescript-language-server`, `pyright-langserver`, `gopls`, `clangd`, or
|
|
481
|
-
`jdtls`.
|
|
482
|
-
|
|
488
|
+
`jdtls`. Architect preloads `vcm-code-navigation` and uses LSP for semantic
|
|
489
|
+
relationships. Accessors, backing fields, trait items, implementations, wrappers,
|
|
490
|
+
and aliases are queried as separate symbols; a bounded source-text fallback may
|
|
491
|
+
only locate candidates after the correct LSP query is demonstrably partial.
|
|
492
|
+
Other roles use generated context and ordinary source reads.
|
|
493
|
+
Harness Studio reports
|
|
483
494
|
whether the server executable and plugin can run; the role Session performs the
|
|
484
|
-
real workspace
|
|
495
|
+
real workspace indexing and semantic query retries. VCM uses Claude Code progress
|
|
496
|
+
hooks to detect a model response, tool call, subagent, or compaction that may be
|
|
497
|
+
stalled. Detection only opens a warning; it does not interrupt the Session or
|
|
498
|
+
change the Round. The user can ignore that warning or explicitly stop and resume
|
|
499
|
+
the same Claude Session from the warning dialog.
|
|
485
500
|
|
|
486
501
|
Harness Engineer is task-scoped and runs from the active task worktree. The
|
|
487
502
|
backend automatically starts a fresh Harness Engineer for each active task or
|
|
@@ -493,10 +508,11 @@ files.
|
|
|
493
508
|
`Auto memory` is the switch for the entire automated memory workflow. During
|
|
494
509
|
Review Task Harness after Final Acceptance, Project Manager, Architect, Coder,
|
|
495
510
|
Tester, and an enabled Reviewer submit proposals in sequence through
|
|
496
|
-
`vcm-propose-memory`. Harness Engineer verifies and consolidates them
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
assigned Auto Memory
|
|
511
|
+
`vcm-propose-memory`. Harness Engineer verifies and consolidates them, commits
|
|
512
|
+
the reviewed memory, and writes the assigned machine-readable review result as
|
|
513
|
+
part of the same Task Harness Retrospective. Workflow roles cannot edit active
|
|
514
|
+
memory directly; Harness Engineer edits it only during the assigned Auto Memory
|
|
515
|
+
Retrospective.
|
|
500
516
|
|
|
501
517
|
When Auto Memory is enabled, the planning Architect writes a provisional memory
|
|
502
518
|
candidate before its post-planning Session restart. VCM snapshots that candidate
|
|
@@ -523,7 +539,10 @@ Final Acceptance
|
|
|
523
539
|
-> Review pending Harness Feedback
|
|
524
540
|
-> Review Auto Memory proposals, when Auto Memory is enabled
|
|
525
541
|
-> Harness Engineer updates and commits memory, when Auto Memory is enabled
|
|
526
|
-
|
|
542
|
+
-> Harness Engineer writes review-result.json, when Auto Memory is enabled
|
|
543
|
+
-> VCM validates and records the committed memory result
|
|
544
|
+
-> VCM dispatches durable-document assignments one at a time
|
|
545
|
+
-> Complete Task Harness Retrospective after every assignment completes
|
|
527
546
|
```
|
|
528
547
|
|
|
529
548
|
Memory proposal prompts sent to Project Manager, Architect, Coder, Tester, and
|
|
@@ -537,16 +556,29 @@ impact, and durable-document disposition, then evaluates every proposal item
|
|
|
537
556
|
independently. Each Add or Update decision records why the memory is necessary,
|
|
538
557
|
what happens if it is absent, whether it belongs in memory or a durable
|
|
539
558
|
document, and the exact final memory content when retained. VCM validates that
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
559
|
+
every existing entry and proposal has one structurally valid decision, every
|
|
560
|
+
move decision has one durable-document assignment, moved content has already
|
|
561
|
+
been removed from memory, and the commit changes only assigned memory host
|
|
562
|
+
files and only their `<VCM-memory>` blocks. VCM does not decide whether the
|
|
563
|
+
Harness Engineer's semantic judgment is correct. Harness Engineer review and
|
|
564
|
+
retrospective work remain tool role activity and do not participate in Round
|
|
565
|
+
completion.
|
|
566
|
+
|
|
567
|
+
For `move-to-durable-doc`, memory is removed in the Harness Engineer commit
|
|
568
|
+
before documentation work starts. VCM assigns architecture and known-issues
|
|
569
|
+
documents to Architect and testing documentation to Tester. For another target,
|
|
570
|
+
PM selects Architect, Coder, or Tester and asks the user only when ownership
|
|
571
|
+
cannot be determined. These assignments are backend-dispatched work rather than
|
|
572
|
+
a Docs-Only Flow. Each owner updates and commits the document, submits the exact
|
|
573
|
+
assignment ID in `docs-update-report.md`, and finishes before the next assignment
|
|
574
|
+
starts. Harness Studio shows assignment status and allows failed work to be
|
|
575
|
+
retried.
|
|
544
576
|
|
|
545
577
|
When Auto Memory is disabled, Review Task Harness does not collect proposals or
|
|
546
578
|
ask Harness Engineer to update memory. When enabled, both automatic and manual
|
|
547
579
|
review requests collect proposals before starting the retrospective. A failed
|
|
548
|
-
proposal collection
|
|
549
|
-
from Harness Studio.
|
|
580
|
+
proposal collection, combined retrospective memory review, or durable-document
|
|
581
|
+
assignment must be retried from Harness Studio.
|
|
550
582
|
|
|
551
583
|
## Closing a Task
|
|
552
584
|
|
|
@@ -144,6 +144,9 @@ function artifactNameToPath(paths, artifactName) {
|
|
|
144
144
|
if (artifactName === "test-report.md") {
|
|
145
145
|
return paths.testReportPath;
|
|
146
146
|
}
|
|
147
|
+
if (artifactName === "docs-update-report.md") {
|
|
148
|
+
return paths.docsUpdateReportPath;
|
|
149
|
+
}
|
|
147
150
|
if (artifactName === "docs-sync-report.md") {
|
|
148
151
|
return paths.docsSyncReportPath;
|
|
149
152
|
}
|
|
@@ -208,6 +208,22 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
208
208
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
209
209
|
return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
|
|
210
210
|
});
|
|
211
|
+
app.post("/api/projects/harness/memory/assignments/retry", async (request) => {
|
|
212
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
213
|
+
return deps.autoMemoryService.retryDurableDocAssignment(project.repoRoot, task.worktreePath, request.body?.assignmentId ?? "");
|
|
214
|
+
});
|
|
215
|
+
app.post("/api/projects/harness/memory/assignments/owner", async (request) => {
|
|
216
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
217
|
+
const owner = request.body?.owner;
|
|
218
|
+
if (owner !== "architect" && owner !== "coder" && owner !== "tester") {
|
|
219
|
+
throw new VcmError({
|
|
220
|
+
code: "DURABLE_DOC_OWNER_INVALID",
|
|
221
|
+
message: "Durable-document owner must be architect, coder, or tester.",
|
|
222
|
+
statusCode: 400
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
return deps.autoMemoryService.resolveDurableDocAssignmentOwner(project.repoRoot, task.worktreePath, request.body?.assignmentId ?? "", owner);
|
|
226
|
+
});
|
|
211
227
|
}
|
|
212
228
|
async function persistHarnessEngineerDefaults(appSettings, session) {
|
|
213
229
|
const defaults = createDefaultToolSessionDefaults()["harness-engineer"];
|
|
@@ -71,7 +71,8 @@ export function registerTaskRoutes(app, deps) {
|
|
|
71
71
|
orchestration,
|
|
72
72
|
roundState,
|
|
73
73
|
workflowState,
|
|
74
|
-
architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug)
|
|
74
|
+
architectRestart: deps.architectRestartService.getState(project.repoRoot, taskSlug),
|
|
75
|
+
roleStallWarning: deps.roleStallDetector.getWarning(project.repoRoot, taskSlug)
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
catch (error) {
|
|
@@ -86,12 +87,30 @@ export function registerTaskRoutes(app, deps) {
|
|
|
86
87
|
},
|
|
87
88
|
roundState: degradedRoundState(taskSlug),
|
|
88
89
|
workflowState: degradedWorkflowState(taskSlug),
|
|
89
|
-
architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug)
|
|
90
|
+
architectRestart: deps.architectRestartService.getState(repoRoot, taskSlug),
|
|
91
|
+
roleStallWarning: deps.roleStallDetector.getWarning(repoRoot, taskSlug)
|
|
90
92
|
};
|
|
91
93
|
}
|
|
92
94
|
throw error;
|
|
93
95
|
}
|
|
94
96
|
});
|
|
97
|
+
app.post("/api/tasks/:taskSlug/role-stall/ignore", async (request) => {
|
|
98
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
99
|
+
return deps.roleStallDetector.ignoreWarning(project.repoRoot, request.params.taskSlug, requireWarningId(request.body?.warningId));
|
|
100
|
+
});
|
|
101
|
+
app.post("/api/tasks/:taskSlug/role-stall/recover", async (request) => {
|
|
102
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
103
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
104
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
105
|
+
const taskRepoRoot = getTaskRuntimeRepoRoot(task);
|
|
106
|
+
return deps.roleStallDetector.recoverWarning({
|
|
107
|
+
repoRoot: project.repoRoot,
|
|
108
|
+
taskRepoRoot,
|
|
109
|
+
stateRoot: config.stateRoot,
|
|
110
|
+
taskSlug: task.taskSlug,
|
|
111
|
+
warningId: requireWarningId(request.body?.warningId)
|
|
112
|
+
});
|
|
113
|
+
});
|
|
95
114
|
app.post("/api/tasks/:taskSlug/workflow-state", async (request) => {
|
|
96
115
|
const project = await requireCurrentProject(deps.projectService);
|
|
97
116
|
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
@@ -117,6 +136,16 @@ export function registerTaskRoutes(app, deps) {
|
|
|
117
136
|
return deps.taskCloseService.closeTask(project.repoRoot, request.params.taskSlug);
|
|
118
137
|
});
|
|
119
138
|
}
|
|
139
|
+
function requireWarningId(value) {
|
|
140
|
+
if (typeof value === "string" && value.trim()) {
|
|
141
|
+
return value.trim();
|
|
142
|
+
}
|
|
143
|
+
throw new VcmError({
|
|
144
|
+
code: "ROLE_STALL_WARNING_ID_REQUIRED",
|
|
145
|
+
message: "A role stall warning id is required.",
|
|
146
|
+
statusCode: 400
|
|
147
|
+
});
|
|
148
|
+
}
|
|
120
149
|
async function requireCurrentProject(projectService) {
|
|
121
150
|
const project = await projectService.getCurrentProject();
|
|
122
151
|
if (!project) {
|
|
@@ -206,6 +235,7 @@ function degradedArtifactSummary(handoffDir) {
|
|
|
206
235
|
architectDebugPath: `${handoffDir}/architect-debug.md`,
|
|
207
236
|
architectureDiagnosisPath: `${handoffDir}/architecture-diagnosis.md`,
|
|
208
237
|
testReportPath: `${handoffDir}/test-report.md`,
|
|
238
|
+
docsUpdateReportPath: `${handoffDir}/docs-update-report.md`,
|
|
209
239
|
docsSyncReportPath: `${handoffDir}/docs-sync-report.md`,
|
|
210
240
|
workflowProgressPath: `${handoffDir}/workflow-progress.md`,
|
|
211
241
|
finalAcceptancePath: `${handoffDir}/final-acceptance.md`
|
|
@@ -1,30 +1,21 @@
|
|
|
1
1
|
import { VcmError } from "../errors.js";
|
|
2
|
-
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
3
2
|
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
4
3
|
export function registerWorkflowControlRoutes(app, deps) {
|
|
5
4
|
app.get("/api/tasks/:taskSlug/workflow-control", async (request) => {
|
|
6
5
|
const context = await getContext(deps, request.params.taskSlug);
|
|
7
6
|
return deps.workflowControlService.getState(context);
|
|
8
7
|
});
|
|
9
|
-
app.post("/api/tasks/:taskSlug/
|
|
10
|
-
const
|
|
11
|
-
|
|
8
|
+
app.post("/api/tasks/:taskSlug/ask-user", async (request) => {
|
|
9
|
+
const context = await getContext(deps, request.params.taskSlug);
|
|
10
|
+
const question = typeof request.body?.question === "string" ? request.body.question.trim() : "";
|
|
11
|
+
if (!question) {
|
|
12
12
|
throw new VcmError({
|
|
13
|
-
code: "
|
|
14
|
-
message: "
|
|
13
|
+
code: "WORKFLOW_USER_QUESTION_REQUIRED",
|
|
14
|
+
message: "A non-empty user question is required.",
|
|
15
15
|
statusCode: 400
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
|
-
|
|
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;
|
|
18
|
+
return deps.workflowControlService.requestUserInput(context, question);
|
|
28
19
|
});
|
|
29
20
|
}
|
|
30
21
|
async function getContext(deps, taskSlug) {
|
|
@@ -44,13 +35,3 @@ async function getContext(deps, taskSlug) {
|
|
|
44
35
|
taskSlug: task.taskSlug
|
|
45
36
|
};
|
|
46
37
|
}
|
|
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,10 +28,12 @@ import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/v
|
|
|
28
28
|
import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
|
|
29
29
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
30
30
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
31
|
+
import { renderAskUserTool, renderVcmAskUserSkillRules } from "../templates/harness/vcm-ask-user-skill.js";
|
|
31
32
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
32
33
|
import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
|
|
33
34
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
34
35
|
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
36
|
+
import { renderResolveDurableDocAssignmentTool } from "../templates/harness/resolve-durable-doc-assignment.js";
|
|
35
37
|
import { readVcmPackageVersion } from "../app-version.js";
|
|
36
38
|
const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
37
39
|
const APP_ROOT = path.resolve(CLI_DIR, "../../..");
|
|
@@ -57,8 +59,15 @@ const VCM_HOOK_DEFINITIONS = [
|
|
|
57
59
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
58
60
|
{ eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
59
61
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
62
|
+
{ eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
63
|
+
{ eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
64
|
+
{ eventName: "PostToolUseFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
65
|
+
{ eventName: "PostToolBatch", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
66
|
+
{ eventName: "SubagentStart", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
67
|
+
{ eventName: "SubagentStop", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
60
68
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
61
69
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
70
|
+
{ eventName: "PreCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
62
71
|
{ eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
63
72
|
{ eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
|
|
64
73
|
];
|
|
@@ -74,8 +83,7 @@ const AGENT_FRONTMATTER = {
|
|
|
74
83
|
},
|
|
75
84
|
coder: {
|
|
76
85
|
description: "VCM implementation role for scoped code changes and focused tests.",
|
|
77
|
-
disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", ")
|
|
78
|
-
skills: ["vcm-code-navigation"]
|
|
86
|
+
disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", ")
|
|
79
87
|
},
|
|
80
88
|
tester: {
|
|
81
89
|
description: "VCM testing role for validation, test adequacy, approved-scope validation, and risk findings.",
|
|
@@ -83,8 +91,7 @@ const AGENT_FRONTMATTER = {
|
|
|
83
91
|
},
|
|
84
92
|
reviewer: {
|
|
85
93
|
description: "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.",
|
|
86
|
-
disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", ")
|
|
87
|
-
skills: ["vcm-code-navigation"]
|
|
94
|
+
disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", ")
|
|
88
95
|
},
|
|
89
96
|
translator: {
|
|
90
97
|
description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
|
|
@@ -114,7 +121,9 @@ const REQUIRED_AGENT_TOOLS = {
|
|
|
114
121
|
"harness-engineer": ["Skill"]
|
|
115
122
|
};
|
|
116
123
|
const REQUIRED_AGENT_SKILLS = {
|
|
117
|
-
architect: ["vcm-code-navigation"]
|
|
124
|
+
architect: ["vcm-code-navigation"]
|
|
125
|
+
};
|
|
126
|
+
const REMOVED_AGENT_SKILLS = {
|
|
118
127
|
coder: ["vcm-code-navigation"],
|
|
119
128
|
reviewer: ["vcm-code-navigation"]
|
|
120
129
|
};
|
|
@@ -285,7 +294,7 @@ const WHOLE_FILES = [
|
|
|
285
294
|
path: ".claude/skills/vcm-code-navigation/SKILL.md",
|
|
286
295
|
category: "skill",
|
|
287
296
|
mode: 0o644,
|
|
288
|
-
content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect
|
|
297
|
+
content: renderSkillFile("VCM Code Navigation Skill", "vcm-code-navigation", "Use when Architect must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths.", renderVcmCodeNavigationSkillRules())
|
|
289
298
|
},
|
|
290
299
|
{
|
|
291
300
|
path: ".claude/skills/vcm-final-acceptance/SKILL.md",
|
|
@@ -311,6 +320,12 @@ const WHOLE_FILES = [
|
|
|
311
320
|
mode: 0o644,
|
|
312
321
|
content: renderSkillFile("VCM Route Message Skill", "vcm-route-message", "Use when project-manager dispatches a VCM role or when a VCM role reports a question, result, blocker, or finding back to project-manager.", renderVcmRouteMessageSkillRules())
|
|
313
322
|
},
|
|
323
|
+
{
|
|
324
|
+
path: ".claude/skills/vcm-ask-user/SKILL.md",
|
|
325
|
+
category: "skill",
|
|
326
|
+
mode: 0o644,
|
|
327
|
+
content: renderSkillFile("VCM Ask User Skill", "vcm-ask-user", "Use whenever project-manager asks the user a question and must pause the workflow.", renderVcmAskUserSkillRules())
|
|
328
|
+
},
|
|
314
329
|
{
|
|
315
330
|
path: ".claude/skills/vcm-task-state/SKILL.md",
|
|
316
331
|
category: "skill",
|
|
@@ -353,6 +368,12 @@ const WHOLE_FILES = [
|
|
|
353
368
|
mode: 0o755,
|
|
354
369
|
content: renderRequestGateReviewTool()
|
|
355
370
|
},
|
|
371
|
+
{
|
|
372
|
+
path: ".ai/tools/vcm-ask-user",
|
|
373
|
+
category: "runtime-tool",
|
|
374
|
+
mode: 0o755,
|
|
375
|
+
content: renderAskUserTool()
|
|
376
|
+
},
|
|
356
377
|
{
|
|
357
378
|
path: ".ai/tools/update-task-state",
|
|
358
379
|
category: "runtime-tool",
|
|
@@ -371,6 +392,12 @@ const WHOLE_FILES = [
|
|
|
371
392
|
mode: 0o755,
|
|
372
393
|
content: renderRequestArchitectRestartTool()
|
|
373
394
|
},
|
|
395
|
+
{
|
|
396
|
+
path: ".ai/tools/resolve-durable-doc-assignment",
|
|
397
|
+
category: "runtime-tool",
|
|
398
|
+
mode: 0o755,
|
|
399
|
+
content: renderResolveDurableDocAssignmentTool()
|
|
400
|
+
},
|
|
374
401
|
{
|
|
375
402
|
path: ".ai/tools/run-long-check",
|
|
376
403
|
category: "runtime-tool",
|
|
@@ -635,6 +662,7 @@ function fixedDirectories() {
|
|
|
635
662
|
".claude/skills/vcm-harness-bootstrap/",
|
|
636
663
|
".claude/skills/vcm-long-running-validation/",
|
|
637
664
|
".claude/skills/vcm-route-message/",
|
|
665
|
+
".claude/skills/vcm-ask-user/",
|
|
638
666
|
".claude/skills/vcm-task-state/",
|
|
639
667
|
".claude/skills/vcm-workflow-review/",
|
|
640
668
|
".claude/skills/vcm-gate-review/",
|
|
@@ -711,6 +739,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
711
739
|
for (const requiredSkill of REQUIRED_AGENT_SKILLS[definition.agentName] ?? []) {
|
|
712
740
|
nextContent = ensureAgentSkill(nextContent, requiredSkill);
|
|
713
741
|
}
|
|
742
|
+
for (const removedSkill of REMOVED_AGENT_SKILLS[definition.agentName] ?? []) {
|
|
743
|
+
nextContent = removeAgentSkill(nextContent, removedSkill);
|
|
744
|
+
}
|
|
714
745
|
await writeIfChanged({
|
|
715
746
|
targetPath,
|
|
716
747
|
relativePath: definition.path,
|
|
@@ -829,6 +860,30 @@ function ensureAgentSkill(content, requiredSkill) {
|
|
|
829
860
|
const nextSkills = `${skillsMatch[0].trimEnd()}\n - ${requiredSkill}`;
|
|
830
861
|
return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], nextSkills));
|
|
831
862
|
}
|
|
863
|
+
function removeAgentSkill(content, removedSkill) {
|
|
864
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
865
|
+
if (!frontmatterMatch) {
|
|
866
|
+
return content;
|
|
867
|
+
}
|
|
868
|
+
const frontmatter = frontmatterMatch[0];
|
|
869
|
+
const skillsMatch = frontmatter.match(/^skills:[ \t]*(?:\r?\n((?:\s+-\s+[^\r\n]+\r?\n?)*))?/m);
|
|
870
|
+
if (!skillsMatch) {
|
|
871
|
+
return content;
|
|
872
|
+
}
|
|
873
|
+
const listedSkills = (skillsMatch[1] ?? "")
|
|
874
|
+
.split(/\r?\n/)
|
|
875
|
+
.map((line) => line.match(/^\s+-\s+(.+)$/)?.[1]?.trim())
|
|
876
|
+
.filter(Boolean);
|
|
877
|
+
const remainingSkills = listedSkills.filter((skill) => skill !== removedSkill);
|
|
878
|
+
if (remainingSkills.length === listedSkills.length) {
|
|
879
|
+
return content;
|
|
880
|
+
}
|
|
881
|
+
const lineEnding = skillsMatch[0].includes("\r\n") ? "\r\n" : "\n";
|
|
882
|
+
const replacement = remainingSkills.length > 0
|
|
883
|
+
? `skills:${lineEnding}${remainingSkills.map((skill) => ` - ${skill}`).join(lineEnding)}${lineEnding}`
|
|
884
|
+
: "";
|
|
885
|
+
return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], replacement));
|
|
886
|
+
}
|
|
832
887
|
function migrateLegacyManagedFile(definition, currentContent, block) {
|
|
833
888
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
834
889
|
if (!legacyContent) {
|
|
@@ -44,7 +44,7 @@ export const REVIEWER_RUNTIME_DISALLOWED_TOOLS = [
|
|
|
44
44
|
"Edit",
|
|
45
45
|
...NON_VCM_ROLE_RUNTIME_TOOLS
|
|
46
46
|
];
|
|
47
|
-
const LSP_ROLE_NAMES = new Set(["architect"
|
|
47
|
+
const LSP_ROLE_NAMES = new Set(["architect"]);
|
|
48
48
|
export function roleUsesLsp(role) {
|
|
49
49
|
return LSP_ROLE_NAMES.has(role);
|
|
50
50
|
}
|
package/dist/backend/server.js
CHANGED
|
@@ -13,6 +13,7 @@ import { createAppSettingsService } from "./services/app-settings-service.js";
|
|
|
13
13
|
import { createCcrIntegrationService } from "./services/ccr-integration-service.js";
|
|
14
14
|
import { createAutoMemoryService } from "./services/auto-memory-service.js";
|
|
15
15
|
import { createArchitectRestartService } from "./services/architect-restart-service.js";
|
|
16
|
+
import { createRoleStallDetectorService } from "./services/role-stall-detector-service.js";
|
|
16
17
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
17
18
|
import { createGateReviewService } from "./services/gate-review-service.js";
|
|
18
19
|
import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
|
|
@@ -134,7 +135,8 @@ export async function createServer(deps, options = {}) {
|
|
|
134
135
|
taskLaunchService: deps.taskLaunchService,
|
|
135
136
|
roundService: deps.roundService,
|
|
136
137
|
taskWorkflowService: deps.taskWorkflowService,
|
|
137
|
-
architectRestartService: deps.architectRestartService
|
|
138
|
+
architectRestartService: deps.architectRestartService,
|
|
139
|
+
roleStallDetector: deps.roleStallDetector
|
|
138
140
|
});
|
|
139
141
|
registerSessionRoutes(app, {
|
|
140
142
|
projectService: deps.projectService,
|
|
@@ -154,9 +156,7 @@ export async function createServer(deps, options = {}) {
|
|
|
154
156
|
registerWorkflowControlRoutes(app, {
|
|
155
157
|
projectService: deps.projectService,
|
|
156
158
|
taskService: deps.taskService,
|
|
157
|
-
|
|
158
|
-
workflowControlService: deps.workflowControlService,
|
|
159
|
-
runtime: deps.runtime
|
|
159
|
+
workflowControlService: deps.workflowControlService
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
registerMessageRoutes(app, {
|
|
@@ -332,6 +332,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
332
332
|
appSettings,
|
|
333
333
|
sessionService,
|
|
334
334
|
roundService,
|
|
335
|
+
workflowControlService,
|
|
335
336
|
onArchitecturePlanDisposition: ({ repoRoot, taskSlug, accepted }) => architectRestartService.recordArchitectureGateDisposition(repoRoot, taskSlug, accepted)
|
|
336
337
|
});
|
|
337
338
|
const translationWorkerService = createTranslationWorkerService({
|
|
@@ -340,6 +341,10 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
340
341
|
sessionService
|
|
341
342
|
});
|
|
342
343
|
const transcripts = createClaudeTranscriptService();
|
|
344
|
+
const roleStallDetector = createRoleStallDetectorService({
|
|
345
|
+
sessionService,
|
|
346
|
+
roundService
|
|
347
|
+
});
|
|
343
348
|
const translationService = createTranslationService({
|
|
344
349
|
runtime,
|
|
345
350
|
sessionRegistry: registry,
|
|
@@ -411,7 +416,9 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
411
416
|
gatewayService,
|
|
412
417
|
jobGuard: createJobGuardService(),
|
|
413
418
|
translationWorkerService,
|
|
414
|
-
architectRestartService
|
|
419
|
+
architectRestartService,
|
|
420
|
+
roleStallDetector,
|
|
421
|
+
workflowControlService
|
|
415
422
|
});
|
|
416
423
|
const runtimeCoordinator = createRuntimeCoordinatorService({
|
|
417
424
|
appSettings,
|
|
@@ -423,6 +430,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
423
430
|
harnessFeedbackService,
|
|
424
431
|
autoMemoryService,
|
|
425
432
|
roundService,
|
|
433
|
+
roleStallDetector,
|
|
426
434
|
gatewayService,
|
|
427
435
|
async getStateRoot(repoRoot) {
|
|
428
436
|
return (await projectService.loadConfig(repoRoot)).stateRoot;
|
|
@@ -463,6 +471,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
463
471
|
autoMemoryService,
|
|
464
472
|
commandDispatcher,
|
|
465
473
|
claudeHookService,
|
|
474
|
+
roleStallDetector,
|
|
466
475
|
messageService,
|
|
467
476
|
taskLaunchService,
|
|
468
477
|
gateReviewService,
|
|
@@ -4,9 +4,10 @@ import { checkMarkdownArtifact } from "../../shared/validation/artifact-check.js
|
|
|
4
4
|
import { getArtifactDefinition, isArtifactKind } from "../../shared/validation/artifact-registry.js";
|
|
5
5
|
import { VcmError } from "../errors.js";
|
|
6
6
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
7
|
-
import { renderArchitectureBriefTemplate, renderArchitectureDiagnosisTemplate, renderArchitectureEvidenceTemplate, renderArchitecturePlanTemplate, renderArchitectDebugTemplate, renderCoderCompletionTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderPlanningProgressTemplate, renderMessageRouteTemplate, renderTestReportTemplate, renderWorkflowProgressTemplate } from "../templates/handoff.js";
|
|
7
|
+
import { renderArchitectureBriefTemplate, renderArchitectureDiagnosisTemplate, renderArchitectureEvidenceTemplate, renderArchitecturePlanTemplate, renderArchitectDebugTemplate, renderCoderCompletionTemplate, renderDocsUpdateReportTemplate, renderDocsSyncReportTemplate, renderFinalAcceptanceTemplate, renderKnownIssuesTemplate, renderPlanningProgressTemplate, renderMessageRouteTemplate, renderTestReportTemplate, renderWorkflowProgressTemplate } from "../templates/handoff.js";
|
|
8
8
|
import { renderRoleCommandTemplate } from "../templates/role-command.js";
|
|
9
9
|
import { validateMemoryProposal } from "./memory-proposal-validation.js";
|
|
10
|
+
import { isMemoryProposalSubmissionPath } from "./memory-review-paths.js";
|
|
10
11
|
const ARTIFACT_PATH_KEYS = [
|
|
11
12
|
["architecture-brief", "architectureBriefPath"],
|
|
12
13
|
["architecture-evidence", "architectureEvidencePath"],
|
|
@@ -17,6 +18,7 @@ const ARTIFACT_PATH_KEYS = [
|
|
|
17
18
|
["architect-debug", "architectDebugPath"],
|
|
18
19
|
["architecture-diagnosis", "architectureDiagnosisPath"],
|
|
19
20
|
["test-report", "testReportPath"],
|
|
21
|
+
["docs-update-report", "docsUpdateReportPath"],
|
|
20
22
|
["docs-sync-report", "docsSyncReportPath"],
|
|
21
23
|
["workflow-progress", "workflowProgressPath"],
|
|
22
24
|
["final-acceptance", "finalAcceptancePath"]
|
|
@@ -54,6 +56,7 @@ export function createArtifactService(fs, deps = {}) {
|
|
|
54
56
|
architectDebugPath: path.posix.join(handoffDir, "architect-debug.md"),
|
|
55
57
|
architectureDiagnosisPath: path.posix.join(handoffDir, "architecture-diagnosis.md"),
|
|
56
58
|
testReportPath: path.posix.join(handoffDir, "test-report.md"),
|
|
59
|
+
docsUpdateReportPath: path.posix.join(handoffDir, "docs-update-report.md"),
|
|
57
60
|
docsSyncReportPath: path.posix.join(handoffDir, "docs-sync-report.md"),
|
|
58
61
|
workflowProgressPath: path.posix.join(handoffDir, "workflow-progress.md"),
|
|
59
62
|
finalAcceptancePath: path.posix.join(handoffDir, "final-acceptance.md")
|
|
@@ -84,6 +87,7 @@ export function createArtifactService(fs, deps = {}) {
|
|
|
84
87
|
[paths.architectDebugPath, renderArchitectDebugTemplate(input.taskSlug)],
|
|
85
88
|
[paths.architectureDiagnosisPath, renderArchitectureDiagnosisTemplate(input.taskSlug)],
|
|
86
89
|
[paths.testReportPath, renderTestReportTemplate(input.taskSlug)],
|
|
90
|
+
[paths.docsUpdateReportPath, renderDocsUpdateReportTemplate(input.taskSlug)],
|
|
87
91
|
[paths.docsSyncReportPath, renderDocsSyncReportTemplate(input.taskSlug)],
|
|
88
92
|
[paths.workflowProgressPath, renderWorkflowProgressTemplate(input.taskSlug)],
|
|
89
93
|
[paths.finalAcceptancePath, renderFinalAcceptanceTemplate(input.taskSlug)],
|
|
@@ -189,10 +193,11 @@ export function createArtifactService(fs, deps = {}) {
|
|
|
189
193
|
}
|
|
190
194
|
if (isArtifactKind(input.kind)) {
|
|
191
195
|
const definition = getArtifactDefinition(input.kind);
|
|
192
|
-
|
|
196
|
+
const owners = Array.isArray(definition.owner) ? definition.owner : [definition.owner];
|
|
197
|
+
if (!owners.includes(input.role)) {
|
|
193
198
|
throw new VcmError({
|
|
194
199
|
code: "ARTIFACT_OWNER_MISMATCH",
|
|
195
|
-
message: `${input.kind} is owned by ${
|
|
200
|
+
message: `${input.kind} is owned by ${owners.join("|")}, not ${input.role}.`,
|
|
196
201
|
statusCode: 403
|
|
197
202
|
});
|
|
198
203
|
}
|
|
@@ -314,8 +319,8 @@ async function validateDynamicArtifact(fs, input, content, workflowControlServic
|
|
|
314
319
|
if (!VCM_ROLE_NAMES.includes(input.role)) {
|
|
315
320
|
throw artifactRejected(input.kind, ["Only a VCM workflow role may submit a Memory Proposal."]);
|
|
316
321
|
}
|
|
317
|
-
if (
|
|
318
|
-
throw artifactRejected(input.kind, ["Memory proposal path is
|
|
322
|
+
if (!isMemoryProposalSubmissionPath(artifactPath, input.role)) {
|
|
323
|
+
throw artifactRejected(input.kind, ["Memory proposal path is not assigned to the submitting role."]);
|
|
319
324
|
}
|
|
320
325
|
const memoryError = validateMemoryProposal(content);
|
|
321
326
|
if (memoryError) {
|
|
@@ -333,39 +338,12 @@ async function validateDynamicArtifact(fs, input, content, workflowControlServic
|
|
|
333
338
|
validateHarnessFeedback(content);
|
|
334
339
|
return { kind: input.kind, root: input.baseRepoRoot, path: artifactPath };
|
|
335
340
|
}
|
|
336
|
-
if (input.kind === "retrospective-report") {
|
|
337
|
-
if (input.role !== "harness-engineer") {
|
|
338
|
-
throw artifactRejected(input.kind, ["Only Harness Engineer may submit a retrospective report."]);
|
|
339
|
-
}
|
|
340
|
-
if (!/^\.ai\/vcm\/harness-feedback\/task-retrospectives\/[A-Za-z0-9._-]+\.md$/.test(artifactPath)) {
|
|
341
|
-
throw artifactRejected(input.kind, ["Retrospective report path must be under .ai/vcm/harness-feedback/task-retrospectives/."]);
|
|
342
|
-
}
|
|
343
|
-
validateRetrospectiveReport(content);
|
|
344
|
-
return { kind: input.kind, root: input.baseRepoRoot, path: artifactPath };
|
|
345
|
-
}
|
|
346
341
|
throw new VcmError({
|
|
347
342
|
code: "ARTIFACT_KIND_INVALID",
|
|
348
343
|
message: `Unknown managed artifact kind: ${input.kind}`,
|
|
349
344
|
statusCode: 400
|
|
350
345
|
});
|
|
351
346
|
}
|
|
352
|
-
function validateRetrospectiveReport(content) {
|
|
353
|
-
const errors = getRetrospectiveReportErrors(content);
|
|
354
|
-
if (errors.length > 0)
|
|
355
|
-
throw artifactRejected("retrospective-report", errors);
|
|
356
|
-
}
|
|
357
|
-
export function getRetrospectiveReportErrors(content) {
|
|
358
|
-
const errors = [];
|
|
359
|
-
if (!/^# Task Harness Retrospective(?::\s*.+)?\s*$/m.test(content)) {
|
|
360
|
-
errors.push("Retrospective report requires '# Task Harness Retrospective: <task>'.");
|
|
361
|
-
}
|
|
362
|
-
for (const heading of ["Findings", "Feedback Dispositions", "Recommended Harness Changes", "VCM Issue Drafts"]) {
|
|
363
|
-
if (!new RegExp(`^## ${escapeRegExp(heading)}\\s*$`, "m").test(content)) {
|
|
364
|
-
errors.push(`Missing required section: ${heading}.`);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return errors;
|
|
368
|
-
}
|
|
369
347
|
function validateCoderWorkerReport(content) {
|
|
370
348
|
const errors = [];
|
|
371
349
|
if (!/^# Coder Worker Report:\s*\S.+$/m.test(content)) {
|