vibe-coding-master 0.7.5 → 0.7.6
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 +19 -16
- package/dist/backend/adapters/git-adapter.js +15 -0
- package/dist/backend/api/artifact-routes.js +3 -0
- package/dist/backend/api/harness-routes.js +50 -27
- package/dist/backend/api/runtime-state-routes.js +7 -3
- package/dist/backend/api/task-routes.js +36 -4
- package/dist/backend/api/translation-routes.js +11 -2
- package/dist/backend/api/translation-worker-routes.js +37 -9
- package/dist/backend/cli/install-vcm-harness.js +40 -2
- package/dist/backend/gateway/gateway-service.js +34 -17
- package/dist/backend/server.js +12 -3
- package/dist/backend/services/artifact-service.js +4 -1
- package/dist/backend/services/auto-memory-service.js +156 -81
- package/dist/backend/services/claude-hook-service.js +50 -35
- package/dist/backend/services/command-dispatcher.js +1 -1
- package/dist/backend/services/gate-review-service.js +44 -0
- package/dist/backend/services/harness-feedback-service.js +19 -8
- package/dist/backend/services/harness-service.js +112 -34
- package/dist/backend/services/message-service.js +39 -2
- package/dist/backend/services/round-service.js +10 -121
- package/dist/backend/services/runtime-coordinator-service.js +18 -10
- package/dist/backend/services/runtime-recovery-service.js +1 -2
- package/dist/backend/services/session-service.js +36 -98
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/task-close-service.js +12 -27
- package/dist/backend/services/task-workflow-service.js +228 -0
- package/dist/backend/services/translation-worker-service.js +14 -7
- package/dist/backend/templates/handoff.js +40 -1
- package/dist/backend/templates/harness/architect-agent.js +67 -19
- package/dist/backend/templates/harness/claude-root.js +25 -29
- package/dist/backend/templates/harness/gate-review.js +26 -13
- package/dist/backend/templates/harness/harness-engineer-agent.js +8 -8
- package/dist/backend/templates/harness/memory-block.js +69 -0
- package/dist/backend/templates/harness/project-known-issues.js +1 -0
- package/dist/backend/templates/harness/project-manager-agent.js +211 -73
- package/dist/backend/templates/harness/role-memory.js +9 -12
- package/dist/backend/templates/harness/tester-agent.js +4 -1
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +82 -0
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +4 -3
- package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +14 -3
- package/dist/backend/templates/harness/vcm-propose-memory-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-route-message-skill.js +5 -0
- package/dist/backend/templates/harness/vcm-task-state-skill.js +110 -0
- package/dist/backend/templates/message-envelope.js +1 -1
- package/dist/shared/constants.js +0 -10
- package/dist/shared/types/workflow.js +1 -0
- package/dist/shared/validation/artifact-check.js +20 -0
- package/dist-frontend/assets/index-BO2AuF-q.js +97 -0
- package/dist-frontend/assets/index-C2etsYlK.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/check-durable-docs +298 -0
- package/scripts/verify-package.mjs +1 -0
- package/dist-frontend/assets/index-DCb-S6Ls.css +0 -32
- package/dist-frontend/assets/index-NTlycxx9.js +0 -97
package/README.md
CHANGED
|
@@ -253,8 +253,10 @@ Automatic mode:
|
|
|
253
253
|
- the UI switches to the target role before dispatch
|
|
254
254
|
- Claude Code hooks confirm whether the prompt was accepted
|
|
255
255
|
|
|
256
|
-
If the flow stops, VCM shows a pause alert.
|
|
257
|
-
controls the sound
|
|
256
|
+
If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
|
|
257
|
+
only controls the looping sound. Enabling Gateway turns that preference off once;
|
|
258
|
+
it can be turned back on afterward. A new Gateway command closes an open pause
|
|
259
|
+
alert after its instruction is successfully submitted to PM.
|
|
258
260
|
|
|
259
261
|
## Gate Review Gates
|
|
260
262
|
|
|
@@ -280,8 +282,9 @@ findings back to the responsible role.
|
|
|
280
282
|
|
|
281
283
|
Conversation translation is controlled from the sidebar `Translation` section.
|
|
282
284
|
|
|
283
|
-
VCM uses
|
|
284
|
-
|
|
285
|
+
VCM uses a task-scoped Translator role and Claude transcript JSONL files, not
|
|
286
|
+
raw terminal text. Translation memory and completed file translations remain
|
|
287
|
+
project-level durable data.
|
|
285
288
|
|
|
286
289
|
Common controls:
|
|
287
290
|
|
|
@@ -385,8 +388,9 @@ Use it to:
|
|
|
385
388
|
- merge task harness commits back to the connected repository branch when
|
|
386
389
|
appropriate
|
|
387
390
|
|
|
388
|
-
Harness Engineer is
|
|
389
|
-
|
|
391
|
+
Harness Engineer is task-scoped and runs from the active task worktree. A new
|
|
392
|
+
task receives its own Harness Engineer session. Durable memory is versioned with
|
|
393
|
+
the project harness files.
|
|
390
394
|
|
|
391
395
|
### Auto Memory
|
|
392
396
|
|
|
@@ -396,10 +400,12 @@ Tester, and an enabled Gate Reviewer submit proposals in sequence through
|
|
|
396
400
|
`vcm-propose-memory`. Harness Engineer verifies and consolidates them before VCM
|
|
397
401
|
applies the result. Roles cannot edit active memory directly.
|
|
398
402
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
+
Shared memory is stored in the root `CLAUDE.md` `<VCM-memory>` block. Role memory
|
|
404
|
+
is stored in the matching `.claude/agents/*.md` block. VCM changes only block
|
|
405
|
+
contents and creates a dedicated commit in the active task worktree. Harness
|
|
406
|
+
Studio shows current memory and task-local applied history. Memory is applied
|
|
407
|
+
before user review; while the task worktree remains available, the user can edit
|
|
408
|
+
current memory or revert a recorded change through another commit.
|
|
403
409
|
|
|
404
410
|
Post-task processing is ordered by the backend:
|
|
405
411
|
|
|
@@ -420,12 +426,9 @@ can continue.
|
|
|
420
426
|
|
|
421
427
|
`Close Task` is destructive.
|
|
422
428
|
|
|
423
|
-
It stops
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
Project-scoped tool sessions such as Translator and Harness Engineer are not
|
|
427
|
-
ordinary task deliverables. VCM may move them to a safe cwd when task context
|
|
428
|
-
changes.
|
|
429
|
+
It stops every running session owned by the task, including Translator and
|
|
430
|
+
Harness Engineer, then removes task-owned worktree/branch state. Commit or
|
|
431
|
+
preserve anything important before closing.
|
|
429
432
|
|
|
430
433
|
## Troubleshooting
|
|
431
434
|
|
|
@@ -293,6 +293,21 @@ export function createGitAdapter(runner) {
|
|
|
293
293
|
}
|
|
294
294
|
return this.getHeadCommit(repoRoot);
|
|
295
295
|
},
|
|
296
|
+
async commitPaths(repoRoot, message, paths) {
|
|
297
|
+
if (paths.length === 0) {
|
|
298
|
+
return this.getHeadCommit(repoRoot);
|
|
299
|
+
}
|
|
300
|
+
const result = await runGit(runner, repoRoot, ["commit", "--only", "-m", message, "--", ...paths]);
|
|
301
|
+
if (result.exitCode !== 0) {
|
|
302
|
+
throw new VcmError({
|
|
303
|
+
code: "GIT_COMMIT_FAILED",
|
|
304
|
+
message: "Unable to commit VCM memory changes.",
|
|
305
|
+
statusCode: 409,
|
|
306
|
+
hint: result.stderr || result.stdout
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return this.getHeadCommit(repoRoot);
|
|
310
|
+
},
|
|
296
311
|
async createWorktree(input) {
|
|
297
312
|
const result = await runGit(runner, input.repoRoot, [
|
|
298
313
|
"worktree",
|
|
@@ -64,6 +64,9 @@ function parseDispatchableRole(role) {
|
|
|
64
64
|
return role;
|
|
65
65
|
}
|
|
66
66
|
function artifactNameToPath(paths, artifactName) {
|
|
67
|
+
if (artifactName === "architecture-brief.md") {
|
|
68
|
+
return paths.architectureBriefPath;
|
|
69
|
+
}
|
|
67
70
|
if (artifactName === "architecture-plan.md") {
|
|
68
71
|
return paths.architecturePlanPath;
|
|
69
72
|
}
|
|
@@ -55,7 +55,7 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
55
55
|
app.get("/api/projects/harness/bootstrap", async (request) => {
|
|
56
56
|
const { project, task } = await requireHarnessTaskContext(deps, request.query.taskSlug);
|
|
57
57
|
try {
|
|
58
|
-
return await deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath);
|
|
58
|
+
return await deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath, task.taskSlug);
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
61
|
if (isOpenFileLimitError(error)) {
|
|
@@ -76,55 +76,66 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
76
76
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
77
77
|
return deps.harnessService.restartHarnessBootstrap(project.repoRoot, task.worktreePath, request.body ?? {});
|
|
78
78
|
});
|
|
79
|
-
app.post("/api/projects/harness/bootstrap/stop", async () => {
|
|
80
|
-
const project = await
|
|
81
|
-
return deps.harnessService.stopHarnessBootstrap(project.repoRoot);
|
|
79
|
+
app.post("/api/projects/harness/bootstrap/stop", async (request) => {
|
|
80
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
81
|
+
return deps.harnessService.stopHarnessBootstrap(project.repoRoot, task.worktreePath, task.taskSlug);
|
|
82
82
|
});
|
|
83
83
|
app.post("/api/projects/harness/bootstrap/run", async (request) => {
|
|
84
84
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
85
85
|
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
86
86
|
await deps.harnessFeedbackService.assertHarnessEngineerAvailable(project.repoRoot);
|
|
87
|
-
return deps.harnessService.runHarnessBootstrap(project.repoRoot, task.worktreePath);
|
|
87
|
+
return deps.harnessService.runHarnessBootstrap(project.repoRoot, task.worktreePath, task.taskSlug);
|
|
88
88
|
});
|
|
89
|
-
app.get("/api/projects/harness/engineer/session", async () => {
|
|
89
|
+
app.get("/api/projects/harness/engineer/session", async (request) => {
|
|
90
90
|
const project = await requireCurrentProject(deps.projectService);
|
|
91
|
-
|
|
91
|
+
const taskSlug = request.query.taskSlug?.trim();
|
|
92
|
+
if (!taskSlug) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return (await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "harness-engineer")) ?? null;
|
|
92
96
|
});
|
|
93
97
|
app.post("/api/projects/harness/engineer/session/ensure", async (request) => {
|
|
94
98
|
const project = await requireCurrentProject(deps.projectService);
|
|
95
|
-
|
|
99
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
100
|
+
const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "harness-engineer");
|
|
101
|
+
if (existing?.status === "running") {
|
|
102
|
+
return existing;
|
|
103
|
+
}
|
|
104
|
+
if (existing?.claudeSessionId) {
|
|
105
|
+
return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
106
|
+
}
|
|
107
|
+
return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
96
108
|
});
|
|
97
109
|
app.post("/api/projects/harness/engineer/session/start", async (request) => {
|
|
98
110
|
const project = await requireCurrentProject(deps.projectService);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return deps.sessionService.startProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
111
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
112
|
+
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
113
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
114
|
+
return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
104
115
|
});
|
|
105
116
|
app.post("/api/projects/harness/engineer/session/resume", async (request) => {
|
|
106
117
|
const project = await requireCurrentProject(deps.projectService);
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return deps.sessionService.resumeProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
118
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
119
|
+
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
120
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
121
|
+
return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
112
122
|
});
|
|
113
123
|
app.post("/api/projects/harness/engineer/session/restart", async (request) => {
|
|
114
124
|
const project = await requireCurrentProject(deps.projectService);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return deps.sessionService.restartProjectHarnessEngineerSession(project.repoRoot, request.body);
|
|
125
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
126
|
+
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
127
|
+
await deps.autoMemoryService.assertHarnessEngineerAvailable(task.worktreePath);
|
|
128
|
+
return deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "harness-engineer", request.body);
|
|
120
129
|
});
|
|
121
|
-
app.post("/api/projects/harness/engineer/session/stop", async () => {
|
|
130
|
+
app.post("/api/projects/harness/engineer/session/stop", async (request) => {
|
|
122
131
|
const project = await requireCurrentProject(deps.projectService);
|
|
123
|
-
|
|
132
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
133
|
+
return deps.sessionService.stopRoleSession(project.repoRoot, taskSlug, "harness-engineer");
|
|
124
134
|
});
|
|
125
|
-
app.post("/api/projects/harness/engineer/session/notify-harness", async () => {
|
|
135
|
+
app.post("/api/projects/harness/engineer/session/notify-harness", async (request) => {
|
|
126
136
|
const project = await requireCurrentProject(deps.projectService);
|
|
127
|
-
|
|
137
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Harness Engineer");
|
|
138
|
+
return deps.sessionService.notifyRoleHarnessUpdated(project.repoRoot, taskSlug, "harness-engineer");
|
|
128
139
|
});
|
|
129
140
|
app.get("/api/projects/harness/feedback", async (request) => {
|
|
130
141
|
const project = await requireCurrentProject(deps.projectService);
|
|
@@ -183,6 +194,18 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
183
194
|
return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
|
|
184
195
|
});
|
|
185
196
|
}
|
|
197
|
+
function requireTaskSlug(value, roleLabel) {
|
|
198
|
+
const taskSlug = value?.trim();
|
|
199
|
+
if (!taskSlug) {
|
|
200
|
+
throw new VcmError({
|
|
201
|
+
code: "TOOL_SESSION_TASK_REQUIRED",
|
|
202
|
+
message: `${roleLabel} requires an active task.`,
|
|
203
|
+
statusCode: 409,
|
|
204
|
+
hint: "Create or select a task before using this session."
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return taskSlug;
|
|
208
|
+
}
|
|
186
209
|
function degradedHarnessStatus(error) {
|
|
187
210
|
return {
|
|
188
211
|
version: 1,
|
|
@@ -5,9 +5,13 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
5
5
|
const taskSlug = request.query.taskSlug?.trim();
|
|
6
6
|
const coordinated = await deps.runtimeCoordinator.reconcileProject(project.repoRoot, { taskSlug });
|
|
7
7
|
const [translatorSession, translationState, harnessEngineerSession, harnessFeedbackState] = await Promise.all([
|
|
8
|
-
|
|
8
|
+
taskSlug
|
|
9
|
+
? deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator").then((session) => session ?? null)
|
|
10
|
+
: Promise.resolve(null),
|
|
9
11
|
deps.translationWorkerService.getState(project.repoRoot, { visibility: "public" }),
|
|
10
|
-
|
|
12
|
+
taskSlug
|
|
13
|
+
? deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "harness-engineer").then((session) => session ?? null)
|
|
14
|
+
: Promise.resolve(null),
|
|
11
15
|
deps.harnessFeedbackService.getState(project.repoRoot, taskSlug || undefined)
|
|
12
16
|
]);
|
|
13
17
|
if (!taskSlug) {
|
|
@@ -26,7 +30,7 @@ export function registerRuntimeStateRoutes(app, deps) {
|
|
|
26
30
|
const task = await deps.taskService.loadTask(project.repoRoot, taskSlug);
|
|
27
31
|
const [harnessStatus, harnessBootstrapStatus, autoMemoryState] = await Promise.all([
|
|
28
32
|
withOpenFileLimitFallback(() => deps.harnessService.getHarnessStatus(task.worktreePath), (error) => degradedHarnessStatus(error)),
|
|
29
|
-
withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath), (error) => degradedBootstrapStatus(error)),
|
|
33
|
+
withOpenFileLimitFallback(() => deps.harnessService.getBootstrapStatus(project.repoRoot, task.worktreePath, task.taskSlug), (error) => degradedBootstrapStatus(error)),
|
|
30
34
|
deps.autoMemoryService.getState(project.repoRoot, task.worktreePath)
|
|
31
35
|
]);
|
|
32
36
|
return {
|
|
@@ -45,7 +45,7 @@ export function registerTaskRoutes(app, deps) {
|
|
|
45
45
|
handoffDir: task.handoffDir,
|
|
46
46
|
taskSlug
|
|
47
47
|
};
|
|
48
|
-
const [taskStatus, messages, orchestration, roundState] = await Promise.all([
|
|
48
|
+
const [taskStatus, messages, orchestration, roundState, workflowState] = await Promise.all([
|
|
49
49
|
withOpenFileLimitFallback(() => deps.statusService.getTaskStatus(project.repoRoot, taskSlug), (error) => degradedTaskStatus(project.repoRoot, taskSlug, error)),
|
|
50
50
|
withOpenFileLimitFallback(() => deps.messageService.listMessages(context), () => []),
|
|
51
51
|
withOpenFileLimitFallback(() => deps.messageService.getOrchestrationState(context), () => ({
|
|
@@ -58,13 +58,19 @@ export function registerTaskRoutes(app, deps) {
|
|
|
58
58
|
stateRepoRoot: taskRepoRoot,
|
|
59
59
|
stateRoot: config.stateRoot,
|
|
60
60
|
taskSlug
|
|
61
|
-
}), () => degradedRoundState(taskSlug))
|
|
61
|
+
}), () => degradedRoundState(taskSlug)),
|
|
62
|
+
deps.taskWorkflowService?.getState({
|
|
63
|
+
taskRepoRoot,
|
|
64
|
+
stateRoot: config.stateRoot,
|
|
65
|
+
taskSlug
|
|
66
|
+
}) ?? Promise.resolve(degradedWorkflowState(taskSlug))
|
|
62
67
|
]);
|
|
63
68
|
return {
|
|
64
69
|
taskStatus,
|
|
65
70
|
messages,
|
|
66
71
|
orchestration,
|
|
67
|
-
roundState
|
|
72
|
+
roundState,
|
|
73
|
+
workflowState
|
|
68
74
|
};
|
|
69
75
|
}
|
|
70
76
|
catch (error) {
|
|
@@ -77,12 +83,26 @@ export function registerTaskRoutes(app, deps) {
|
|
|
77
83
|
mode: "auto",
|
|
78
84
|
updatedAt: new Date().toISOString()
|
|
79
85
|
},
|
|
80
|
-
roundState: degradedRoundState(taskSlug)
|
|
86
|
+
roundState: degradedRoundState(taskSlug),
|
|
87
|
+
workflowState: degradedWorkflowState(taskSlug)
|
|
81
88
|
};
|
|
82
89
|
}
|
|
83
90
|
throw error;
|
|
84
91
|
}
|
|
85
92
|
});
|
|
93
|
+
app.post("/api/tasks/:taskSlug/workflow-state", async (request) => {
|
|
94
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
95
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
96
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
97
|
+
if (!deps.taskWorkflowService) {
|
|
98
|
+
return degradedWorkflowState(task.taskSlug);
|
|
99
|
+
}
|
|
100
|
+
return deps.taskWorkflowService.declare({
|
|
101
|
+
taskRepoRoot: getTaskRuntimeRepoRoot(task),
|
|
102
|
+
stateRoot: config.stateRoot,
|
|
103
|
+
taskSlug: task.taskSlug
|
|
104
|
+
}, request.body ?? {});
|
|
105
|
+
});
|
|
86
106
|
app.post("/api/tasks/:taskSlug/one-click-start", async (request) => {
|
|
87
107
|
const project = await requireCurrentProject(deps.projectService);
|
|
88
108
|
return deps.taskLaunchService.startTaskRoleSessions(project.repoRoot, {
|
|
@@ -143,6 +163,17 @@ function degradedRoundState(taskSlug) {
|
|
|
143
163
|
updatedAt: new Date().toISOString()
|
|
144
164
|
};
|
|
145
165
|
}
|
|
166
|
+
function degradedWorkflowState(taskSlug) {
|
|
167
|
+
return {
|
|
168
|
+
version: 1,
|
|
169
|
+
taskSlug,
|
|
170
|
+
revision: 0,
|
|
171
|
+
declared: null,
|
|
172
|
+
lastDispatch: null,
|
|
173
|
+
warnings: ["Task workflow state is temporarily unavailable."],
|
|
174
|
+
updatedAt: new Date().toISOString()
|
|
175
|
+
};
|
|
176
|
+
}
|
|
146
177
|
async function withOpenFileLimitFallback(run, fallback) {
|
|
147
178
|
try {
|
|
148
179
|
return await run();
|
|
@@ -164,6 +195,7 @@ function degradedArtifactSummary(handoffDir) {
|
|
|
164
195
|
messagesDir,
|
|
165
196
|
roleCommandPaths: Object.fromEntries(DISPATCHABLE_ROLES.map((role) => [role, `${roleCommandsDir}/${role}.md`])),
|
|
166
197
|
messageRoutePaths: {},
|
|
198
|
+
architectureBriefPath: `${handoffDir}/architecture-brief.md`,
|
|
167
199
|
architecturePlanPath: `${handoffDir}/architecture-plan.md`,
|
|
168
200
|
knownIssuesPath: `${handoffDir}/known-issues.md`,
|
|
169
201
|
testReportPath: `${handoffDir}/test-report.md`,
|
|
@@ -86,9 +86,18 @@ export function registerTranslationRoutes(app, deps) {
|
|
|
86
86
|
await deps.translationService.stopSession(request.params.sessionId);
|
|
87
87
|
return { ok: true };
|
|
88
88
|
});
|
|
89
|
-
app.post("/api/projects/translation/session/notify-harness", async () => {
|
|
89
|
+
app.post("/api/projects/translation/session/notify-harness", async (request) => {
|
|
90
90
|
const project = await requireCurrentProject(deps.projectService);
|
|
91
|
-
|
|
91
|
+
const taskSlug = request.body?.taskSlug?.trim();
|
|
92
|
+
if (!taskSlug) {
|
|
93
|
+
throw new VcmError({
|
|
94
|
+
code: "TOOL_SESSION_TASK_REQUIRED",
|
|
95
|
+
message: "Translator requires an active task.",
|
|
96
|
+
statusCode: 409,
|
|
97
|
+
hint: "Create or select a task before notifying Translator."
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return deps.sessionService.notifyRoleHarnessUpdated(project.repoRoot, taskSlug, "translator");
|
|
92
101
|
});
|
|
93
102
|
app.post("/api/translation/sessions/:sessionId/retry/:translationId", async (request) => {
|
|
94
103
|
await requireCurrentProject(deps.projectService);
|
|
@@ -6,33 +6,49 @@ export function registerTranslationWorkerRoutes(app, deps) {
|
|
|
6
6
|
visibility: "public"
|
|
7
7
|
});
|
|
8
8
|
});
|
|
9
|
-
app.get("/api/translation/session", async () => {
|
|
9
|
+
app.get("/api/translation/session", async (request) => {
|
|
10
10
|
const project = await requireCurrentProject(deps.projectService);
|
|
11
|
-
|
|
11
|
+
const taskSlug = request.query.taskSlug?.trim();
|
|
12
|
+
if (!taskSlug) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
return (await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator")) ?? null;
|
|
12
16
|
});
|
|
13
17
|
app.post("/api/translation/session/ensure", async (request) => {
|
|
14
18
|
const project = await requireCurrentProject(deps.projectService);
|
|
15
|
-
|
|
19
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
|
|
20
|
+
const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator");
|
|
21
|
+
if (existing?.status === "running") {
|
|
22
|
+
return existing;
|
|
23
|
+
}
|
|
24
|
+
if (existing?.claudeSessionId) {
|
|
25
|
+
return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
26
|
+
}
|
|
27
|
+
return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
16
28
|
});
|
|
17
29
|
app.post("/api/translation/session/start", async (request) => {
|
|
18
30
|
const project = await requireCurrentProject(deps.projectService);
|
|
19
|
-
|
|
31
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
|
|
32
|
+
return deps.sessionService.startRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
20
33
|
});
|
|
21
34
|
app.post("/api/translation/session/resume", async (request) => {
|
|
22
35
|
const project = await requireCurrentProject(deps.projectService);
|
|
23
|
-
|
|
36
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
|
|
37
|
+
return deps.sessionService.resumeRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
24
38
|
});
|
|
25
39
|
app.post("/api/translation/session/restart", async (request) => {
|
|
26
40
|
const project = await requireCurrentProject(deps.projectService);
|
|
27
|
-
const
|
|
41
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
|
|
42
|
+
const existing = await deps.sessionService.getRoleSession(project.repoRoot, taskSlug, "translator");
|
|
28
43
|
if (existing) {
|
|
29
44
|
await deps.translationService.stopSession(existing.id, { clearCache: true });
|
|
30
45
|
}
|
|
31
|
-
return deps.sessionService.
|
|
46
|
+
return deps.sessionService.restartRoleSession(project.repoRoot, taskSlug, "translator", request.body);
|
|
32
47
|
});
|
|
33
|
-
app.post("/api/translation/session/stop", async () => {
|
|
48
|
+
app.post("/api/translation/session/stop", async (request) => {
|
|
34
49
|
const project = await requireCurrentProject(deps.projectService);
|
|
35
|
-
const
|
|
50
|
+
const taskSlug = requireTaskSlug(request.body?.taskSlug, "Translator");
|
|
51
|
+
const session = await deps.sessionService.stopRoleSession(project.repoRoot, taskSlug, "translator");
|
|
36
52
|
await deps.translationService.stopSession(session.id);
|
|
37
53
|
return session;
|
|
38
54
|
});
|
|
@@ -73,6 +89,18 @@ export function registerTranslationWorkerRoutes(app, deps) {
|
|
|
73
89
|
return deps.translationWorkerService.promoteFileJob(project.repoRoot, request.params.jobId, targetPath);
|
|
74
90
|
});
|
|
75
91
|
}
|
|
92
|
+
function requireTaskSlug(value, roleLabel) {
|
|
93
|
+
const taskSlug = value?.trim();
|
|
94
|
+
if (!taskSlug) {
|
|
95
|
+
throw new VcmError({
|
|
96
|
+
code: "TOOL_SESSION_TASK_REQUIRED",
|
|
97
|
+
message: `${roleLabel} requires an active task.`,
|
|
98
|
+
statusCode: 409,
|
|
99
|
+
hint: "Create or select a task before using this session."
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return taskSlug;
|
|
103
|
+
}
|
|
76
104
|
async function requireCurrentProject(projectService) {
|
|
77
105
|
const project = await projectService.getCurrentProject();
|
|
78
106
|
if (!project) {
|
|
@@ -11,18 +11,21 @@ import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTransl
|
|
|
11
11
|
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
12
12
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
13
13
|
import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
|
|
14
|
+
import { ensureVcmMemoryBlock } from "../templates/harness/memory-block.js";
|
|
14
15
|
import { renderLegacyProjectCodingStandardsTemplate, renderProjectCodingStandardsProjectSection, renderProjectCodingStandardsRules } from "../templates/harness/project-coding-standards.js";
|
|
15
16
|
import { renderProjectGlossaryTemplate } from "../templates/harness/project-glossary.js";
|
|
16
17
|
import { renderLegacyProjectKnownIssuesTemplate, renderProjectKnownIssuesRules, renderProjectKnownIssuesSection } from "../templates/harness/project-known-issues.js";
|
|
17
18
|
import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
|
|
18
19
|
import { renderPullRequestTemplateHarnessRules } from "../templates/harness/pull-request-template.js";
|
|
19
20
|
import { renderTesterHarnessRules } from "../templates/harness/tester-agent.js";
|
|
21
|
+
import { renderVcmArchitectureInterviewSkillRules } from "../templates/harness/vcm-architecture-interview-skill.js";
|
|
20
22
|
import { renderVcmFinalAcceptanceSkillRules } from "../templates/harness/vcm-final-acceptance-skill.js";
|
|
21
23
|
import { renderVcmHarnessBootstrapSkillRules } from "../templates/harness/vcm-harness-bootstrap-skill.js";
|
|
22
24
|
import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/vcm-long-running-validation-skill.js";
|
|
23
25
|
import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
|
|
24
26
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
25
27
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
28
|
+
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
26
29
|
import { readVcmPackageVersion } from "../app-version.js";
|
|
27
30
|
const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
28
31
|
const APP_ROOT = path.resolve(CLI_DIR, "../../..");
|
|
@@ -69,10 +72,10 @@ const AGENT_FRONTMATTER = {
|
|
|
69
72
|
tools: "Read, Grep, Glob, Bash, Write"
|
|
70
73
|
},
|
|
71
74
|
translator: {
|
|
72
|
-
description: "VCM
|
|
75
|
+
description: "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."
|
|
73
76
|
},
|
|
74
77
|
"harness-engineer": {
|
|
75
|
-
description: "VCM
|
|
78
|
+
description: "VCM task-scoped harness maintenance role for harness diagnosis, diff proposals, and VCM issue drafts."
|
|
76
79
|
},
|
|
77
80
|
"vcm-coder-worker": {
|
|
78
81
|
description: "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.",
|
|
@@ -86,6 +89,7 @@ const MANAGED_FILES = [
|
|
|
86
89
|
commentStyle: "html",
|
|
87
90
|
category: "root-rules",
|
|
88
91
|
blankLineBeforeEnd: true,
|
|
92
|
+
memoryBlock: true,
|
|
89
93
|
content: renderRootClaudeHarnessRules()
|
|
90
94
|
},
|
|
91
95
|
{
|
|
@@ -119,6 +123,7 @@ const MANAGED_FILES = [
|
|
|
119
123
|
agentName: "project-manager",
|
|
120
124
|
commentStyle: "html",
|
|
121
125
|
category: "core-agent",
|
|
126
|
+
memoryBlock: true,
|
|
122
127
|
content: renderProjectManagerHarnessRules()
|
|
123
128
|
},
|
|
124
129
|
{
|
|
@@ -128,6 +133,7 @@ const MANAGED_FILES = [
|
|
|
128
133
|
commentStyle: "html",
|
|
129
134
|
category: "core-agent",
|
|
130
135
|
blankLineBeforeEnd: true,
|
|
136
|
+
memoryBlock: true,
|
|
131
137
|
content: renderArchitectHarnessRules()
|
|
132
138
|
},
|
|
133
139
|
{
|
|
@@ -136,6 +142,7 @@ const MANAGED_FILES = [
|
|
|
136
142
|
agentName: "coder",
|
|
137
143
|
commentStyle: "html",
|
|
138
144
|
category: "core-agent",
|
|
145
|
+
memoryBlock: true,
|
|
139
146
|
content: renderCoderHarnessRules()
|
|
140
147
|
},
|
|
141
148
|
{
|
|
@@ -144,6 +151,7 @@ const MANAGED_FILES = [
|
|
|
144
151
|
agentName: "tester",
|
|
145
152
|
commentStyle: "html",
|
|
146
153
|
category: "core-agent",
|
|
154
|
+
memoryBlock: true,
|
|
147
155
|
content: renderTesterHarnessRules()
|
|
148
156
|
},
|
|
149
157
|
{
|
|
@@ -159,6 +167,7 @@ const MANAGED_FILES = [
|
|
|
159
167
|
agentName: "gate-reviewer",
|
|
160
168
|
commentStyle: "html",
|
|
161
169
|
category: "gate-reviewer-agent",
|
|
170
|
+
memoryBlock: true,
|
|
162
171
|
content: renderGateReviewerAgentRules()
|
|
163
172
|
},
|
|
164
173
|
{
|
|
@@ -175,6 +184,7 @@ const MANAGED_FILES = [
|
|
|
175
184
|
agentName: "harness-engineer",
|
|
176
185
|
commentStyle: "html",
|
|
177
186
|
category: "agent-harness-engineer",
|
|
187
|
+
memoryBlock: true,
|
|
178
188
|
content: renderHarnessEngineerHarnessRules()
|
|
179
189
|
},
|
|
180
190
|
{
|
|
@@ -201,6 +211,12 @@ const DURABLE_DOC_TEMPLATES = [
|
|
|
201
211
|
},
|
|
202
212
|
];
|
|
203
213
|
const WHOLE_FILES = [
|
|
214
|
+
{
|
|
215
|
+
path: ".ai/tools/check-durable-docs",
|
|
216
|
+
category: "durable-docs-tool",
|
|
217
|
+
mode: 0o755,
|
|
218
|
+
templatePath: "scripts/harness-tools/check-durable-docs"
|
|
219
|
+
},
|
|
204
220
|
{
|
|
205
221
|
path: ".ai/tools/generate-module-index",
|
|
206
222
|
category: "generated-context-tool",
|
|
@@ -213,6 +229,12 @@ const WHOLE_FILES = [
|
|
|
213
229
|
mode: 0o755,
|
|
214
230
|
templatePath: "scripts/harness-tools/generate-public-surface"
|
|
215
231
|
},
|
|
232
|
+
{
|
|
233
|
+
path: ".claude/skills/vcm-architecture-interview/SKILL.md",
|
|
234
|
+
category: "skill",
|
|
235
|
+
mode: 0o644,
|
|
236
|
+
content: renderSkillFile("VCM Architecture Interview Skill", "vcm-architecture-interview", "Use when Architect must confirm user-owned behavior and contract decisions before architecture planning.", renderVcmArchitectureInterviewSkillRules())
|
|
237
|
+
},
|
|
216
238
|
{
|
|
217
239
|
path: ".claude/skills/vcm-final-acceptance/SKILL.md",
|
|
218
240
|
category: "skill",
|
|
@@ -237,6 +259,12 @@ const WHOLE_FILES = [
|
|
|
237
259
|
mode: 0o644,
|
|
238
260
|
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())
|
|
239
261
|
},
|
|
262
|
+
{
|
|
263
|
+
path: ".claude/skills/vcm-task-state/SKILL.md",
|
|
264
|
+
category: "skill",
|
|
265
|
+
mode: 0o644,
|
|
266
|
+
content: renderSkillFile("VCM Task State Skill", "vcm-task-state", "Use only as project-manager to declare the current task workflow checkpoint to VCM.", renderVcmTaskStateSkillRules())
|
|
267
|
+
},
|
|
240
268
|
{
|
|
241
269
|
path: ".claude/skills/vcm-gate-review/SKILL.md",
|
|
242
270
|
category: "skill",
|
|
@@ -261,6 +289,12 @@ const WHOLE_FILES = [
|
|
|
261
289
|
mode: 0o755,
|
|
262
290
|
content: renderRequestGateReviewTool()
|
|
263
291
|
},
|
|
292
|
+
{
|
|
293
|
+
path: ".ai/tools/update-task-state",
|
|
294
|
+
category: "runtime-tool",
|
|
295
|
+
mode: 0o755,
|
|
296
|
+
content: renderUpdateTaskStateTool()
|
|
297
|
+
},
|
|
264
298
|
{
|
|
265
299
|
path: ".ai/tools/run-long-check",
|
|
266
300
|
category: "runtime-tool",
|
|
@@ -481,6 +515,7 @@ function fixedDirectories() {
|
|
|
481
515
|
".claude/skills/vcm-harness-bootstrap/",
|
|
482
516
|
".claude/skills/vcm-long-running-validation/",
|
|
483
517
|
".claude/skills/vcm-route-message/",
|
|
518
|
+
".claude/skills/vcm-task-state/",
|
|
484
519
|
".claude/skills/vcm-gate-review/",
|
|
485
520
|
".claude/skills/vcm-report-harness-issue/",
|
|
486
521
|
".claude/skills/vcm-propose-memory/",
|
|
@@ -541,6 +576,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
541
576
|
?? `${currentContent.trimEnd()}\n\n${block}\n`;
|
|
542
577
|
}
|
|
543
578
|
}
|
|
579
|
+
if (definition.memoryBlock) {
|
|
580
|
+
nextContent = ensureVcmMemoryBlock(nextContent);
|
|
581
|
+
}
|
|
544
582
|
await writeIfChanged({
|
|
545
583
|
targetPath,
|
|
546
584
|
relativePath: definition.path,
|