vibe-coding-master 0.7.5 → 0.7.7
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 +40 -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 +57 -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 +47 -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-42EpETgd.js +97 -0
- package/dist-frontend/assets/index-D65x2x0F.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
|
@@ -181,6 +181,12 @@ By default:
|
|
|
181
181
|
Roles for the same task share the same task worktree. VCM does not create one
|
|
182
182
|
worktree per role.
|
|
183
183
|
|
|
184
|
+
Project Manager may record an advisory task checkpoint under
|
|
185
|
+
`.ai/vcm/workflow/state.json`. VCM restores and displays this context after a
|
|
186
|
+
restart, but it does not infer transitions or choose the next role. Current
|
|
187
|
+
artifacts, Gate Review state, Round/Turn state, and the role rules remain
|
|
188
|
+
authoritative.
|
|
189
|
+
|
|
184
190
|
Typical flow:
|
|
185
191
|
|
|
186
192
|
```text
|
|
@@ -253,8 +259,10 @@ Automatic mode:
|
|
|
253
259
|
- the UI switches to the target role before dispatch
|
|
254
260
|
- Claude Code hooks confirm whether the prompt was accepted
|
|
255
261
|
|
|
256
|
-
If the flow stops, VCM shows a pause alert.
|
|
257
|
-
controls the sound
|
|
262
|
+
If the flow stops, VCM always shows a blocking pause alert. `Pause alert sound`
|
|
263
|
+
only controls the looping sound. Enabling Gateway turns that preference off once;
|
|
264
|
+
it can be turned back on afterward. A new Gateway command closes an open pause
|
|
265
|
+
alert after its instruction is successfully submitted to PM.
|
|
258
266
|
|
|
259
267
|
## Gate Review Gates
|
|
260
268
|
|
|
@@ -280,8 +288,9 @@ findings back to the responsible role.
|
|
|
280
288
|
|
|
281
289
|
Conversation translation is controlled from the sidebar `Translation` section.
|
|
282
290
|
|
|
283
|
-
VCM uses
|
|
284
|
-
|
|
291
|
+
VCM uses a task-scoped Translator role and Claude transcript JSONL files, not
|
|
292
|
+
raw terminal text. Translation memory and completed file translations remain
|
|
293
|
+
project-level durable data.
|
|
285
294
|
|
|
286
295
|
Common controls:
|
|
287
296
|
|
|
@@ -385,8 +394,9 @@ Use it to:
|
|
|
385
394
|
- merge task harness commits back to the connected repository branch when
|
|
386
395
|
appropriate
|
|
387
396
|
|
|
388
|
-
Harness Engineer is
|
|
389
|
-
|
|
397
|
+
Harness Engineer is task-scoped and runs from the active task worktree. A new
|
|
398
|
+
task receives its own Harness Engineer session. Durable memory is versioned with
|
|
399
|
+
the project harness files.
|
|
390
400
|
|
|
391
401
|
### Auto Memory
|
|
392
402
|
|
|
@@ -396,20 +406,28 @@ Tester, and an enabled Gate Reviewer submit proposals in sequence through
|
|
|
396
406
|
`vcm-propose-memory`. Harness Engineer verifies and consolidates them before VCM
|
|
397
407
|
applies the result. Roles cannot edit active memory directly.
|
|
398
408
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
409
|
+
Shared memory is stored in the root `CLAUDE.md` `<VCM-memory>` block. Role memory
|
|
410
|
+
is stored in the matching `.claude/agents/*.md` block. VCM changes only block
|
|
411
|
+
contents and creates a dedicated commit in the active task worktree. Harness
|
|
412
|
+
Studio shows current memory and task-local applied history. Memory is applied
|
|
413
|
+
before user review; while the task worktree remains available, the user can edit
|
|
414
|
+
current memory or revert a recorded change through another commit.
|
|
403
415
|
|
|
404
416
|
Post-task processing is ordered by the backend:
|
|
405
417
|
|
|
406
418
|
```text
|
|
407
419
|
Final Acceptance
|
|
408
420
|
-> Review Task Harness
|
|
409
|
-
->
|
|
421
|
+
-> Workflow-role memory proposals, when Auto Memory is enabled
|
|
422
|
+
-> Harness Engineer memory review, when Auto Memory is enabled
|
|
410
423
|
-> Task Harness Retrospective
|
|
411
424
|
```
|
|
412
425
|
|
|
426
|
+
Memory proposal prompts sent to Project Manager, Architect, Coder, Tester, and
|
|
427
|
+
an enabled Gate Reviewer use their normal task sessions and participate in
|
|
428
|
+
Round/Turn tracking. Harness Engineer review and retrospective work remain tool
|
|
429
|
+
role activity and do not participate in Round completion.
|
|
430
|
+
|
|
413
431
|
When Auto Memory is disabled, Review Task Harness does not collect proposals or
|
|
414
432
|
ask Harness Engineer to update memory. When enabled, both automatic and manual
|
|
415
433
|
review requests complete the memory phase before retrospective analysis. A
|
|
@@ -420,12 +438,14 @@ can continue.
|
|
|
420
438
|
|
|
421
439
|
`Close Task` is destructive.
|
|
422
440
|
|
|
423
|
-
It stops
|
|
424
|
-
|
|
441
|
+
It stops every running session owned by the task, including Translator and
|
|
442
|
+
Harness Engineer, then removes task-owned worktree/branch state. Commit or
|
|
443
|
+
preserve anything important before closing.
|
|
425
444
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
445
|
+
Uncommitted changes, unmerged commits, and cleanup failures are reported as
|
|
446
|
+
warnings; they do not block logical task closure. VCM may discard the task
|
|
447
|
+
worktree and task branch even when they contain commits that are not present on
|
|
448
|
+
the connected repository branch.
|
|
429
449
|
|
|
430
450
|
## Troubleshooting
|
|
431
451
|
|
|
@@ -514,7 +534,11 @@ npm start
|
|
|
514
534
|
## Documentation
|
|
515
535
|
|
|
516
536
|
- `docs/ARCHITECTURE.md`: repository architecture
|
|
537
|
+
- `docs/CODING_STANDARDS.md`: shared implementation and test standards
|
|
538
|
+
- `docs/GLOSSARY.md`: allowed durable abbreviations
|
|
517
539
|
- `docs/TESTING.md`: validation strategy
|
|
540
|
+
- `docs/known-issues.md`: current unresolved durable issues
|
|
541
|
+
- `src/backend/gateway/ARCHITECTURE.md`: mobile gateway sub-area architecture
|
|
518
542
|
- `docs/vcm-cc-best-practices.md`: current VCM Claude Code harness practice
|
|
519
543
|
- `docs/v0.5-custom-workflow-plan.md`: deferred custom workflow proposal
|
|
520
544
|
- `docs/cc-best-practices.md`: archived generic Claude Code harness notes
|
|
@@ -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,61 +76,79 @@ 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);
|
|
131
142
|
const taskSlug = await normalizeOptionalTaskSlug(deps, project.repoRoot, request.query.taskSlug);
|
|
132
143
|
return deps.harnessFeedbackService.getState(project.repoRoot, taskSlug);
|
|
133
144
|
});
|
|
145
|
+
app.post("/api/projects/harness/feedback/send", async (request) => {
|
|
146
|
+
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
147
|
+
return deps.harnessFeedbackService.sendPendingFeedback(project.repoRoot, {
|
|
148
|
+
taskSlug: task.taskSlug,
|
|
149
|
+
feedbackPath: request.body?.feedbackPath ?? ""
|
|
150
|
+
});
|
|
151
|
+
});
|
|
134
152
|
app.post("/api/projects/harness/task-retrospective", async (request) => {
|
|
135
153
|
const { project, task } = await requireHarnessTaskContext(deps, request.body?.taskSlug);
|
|
136
154
|
const trigger = request.body?.trigger === "auto" ? "auto" : "manual";
|
|
@@ -183,6 +201,18 @@ export function registerHarnessRoutes(app, deps) {
|
|
|
183
201
|
return deps.autoMemoryService.retryFailedReview(project.repoRoot, task.worktreePath);
|
|
184
202
|
});
|
|
185
203
|
}
|
|
204
|
+
function requireTaskSlug(value, roleLabel) {
|
|
205
|
+
const taskSlug = value?.trim();
|
|
206
|
+
if (!taskSlug) {
|
|
207
|
+
throw new VcmError({
|
|
208
|
+
code: "TOOL_SESSION_TASK_REQUIRED",
|
|
209
|
+
message: `${roleLabel} requires an active task.`,
|
|
210
|
+
statusCode: 409,
|
|
211
|
+
hint: "Create or select a task before using this session."
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
return taskSlug;
|
|
215
|
+
}
|
|
186
216
|
function degradedHarnessStatus(error) {
|
|
187
217
|
return {
|
|
188
218
|
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) {
|