vibe-coding-master 0.7.16 → 0.7.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/backend/adapters/claude-adapter.js +4 -1
- package/dist/backend/api/session-routes.js +5 -0
- package/dist/backend/api/usage-analytics-routes.js +31 -0
- package/dist/backend/cli/install-vcm-harness.js +51 -2
- package/dist/backend/server.js +25 -5
- package/dist/backend/services/architect-restart-service.js +136 -0
- package/dist/backend/services/claude-hook-service.js +6 -0
- package/dist/backend/services/gate-review-service.js +40 -0
- package/dist/backend/services/harness-service.js +51 -4
- package/dist/backend/services/message-service.js +5 -0
- package/dist/backend/services/session-service.js +34 -6
- package/dist/backend/services/task-close-service.js +1 -0
- package/dist/backend/services/usage-analytics-service.js +346 -0
- package/dist/backend/templates/harness/architect-agent.js +18 -10
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +23 -0
- package/dist/backend/templates/harness/claude-root.js +1 -1
- package/dist/backend/templates/harness/gate-review.js +7 -2
- package/dist/backend/templates/harness/project-manager-agent.js +1 -1
- package/dist/backend/templates/harness/restart-architect-skill.js +75 -0
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +31 -5
- package/dist/backend/templates/harness/vcm-route-message-skill.js +3 -3
- package/dist/shared/types/usage-analytics.js +1 -0
- package/dist-frontend/assets/index-CStWyouh.js +97 -0
- package/dist-frontend/assets/{index-CiEUp9Si.css → index-Ci7z8tW3.css} +1 -1
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/dist-frontend/assets/index-BAE_pjXJ.js +0 -97
package/README.md
CHANGED
|
@@ -228,6 +228,18 @@ such as a Dev Container or VM.
|
|
|
228
228
|
Model and effort can be selected before start/resume/restart. Changes affect the
|
|
229
229
|
next launched process, not a currently running Claude Code process.
|
|
230
230
|
|
|
231
|
+
## Usage Analytics
|
|
232
|
+
|
|
233
|
+
Open `Usage Analytics` in the sidebar `Task` section to inspect native Claude
|
|
234
|
+
Code usage for the active task. The report shows task totals and breakdowns by
|
|
235
|
+
role and model for input, output, cache-read, and cache-creation tokens plus
|
|
236
|
+
estimated USD cost. It combines every restart and resumed Claude session for
|
|
237
|
+
all seven roles. CCR/GPT usage is excluded.
|
|
238
|
+
|
|
239
|
+
VCM retains only aggregate task data in
|
|
240
|
+
`<task-worktree>/.ai/vcm/telemetry/usage.json`. The file is temporary runtime
|
|
241
|
+
state and is removed with the task worktree when the task is closed.
|
|
242
|
+
|
|
231
243
|
### GPT Through Claude Code Router
|
|
232
244
|
|
|
233
245
|
VCM can launch its normal Claude Code sessions with `GPT-5.6 Sol (CCR)` through
|
|
@@ -18,7 +18,7 @@ export function createClaudeAdapter(runner) {
|
|
|
18
18
|
}
|
|
19
19
|
return result.stdout.trim();
|
|
20
20
|
},
|
|
21
|
-
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride) {
|
|
21
|
+
buildRoleStartCommand(role, command = "claude", permissionMode = "default", claudeSessionId, resume = false, model = "default", effort = "default", settingsOverride, appendSystemPrompt) {
|
|
22
22
|
const args = ["--agent", role];
|
|
23
23
|
const sessionSettings = { ...settingsOverride };
|
|
24
24
|
if (claudeSessionId) {
|
|
@@ -39,6 +39,9 @@ export function createClaudeAdapter(runner) {
|
|
|
39
39
|
if (permissionMode !== "default") {
|
|
40
40
|
args.push("--permission-mode", permissionMode);
|
|
41
41
|
}
|
|
42
|
+
if (appendSystemPrompt) {
|
|
43
|
+
args.push("--append-system-prompt", appendSystemPrompt);
|
|
44
|
+
}
|
|
42
45
|
return {
|
|
43
46
|
command,
|
|
44
47
|
args,
|
|
@@ -10,6 +10,11 @@ export function registerSessionRoutes(app, deps) {
|
|
|
10
10
|
const role = parseRole(request.params.role);
|
|
11
11
|
return deps.sessionService.startRoleSession(project.repoRoot, request.params.taskSlug, role, request.body);
|
|
12
12
|
});
|
|
13
|
+
app.post("/api/tasks/:taskSlug/sessions/architect/restart-after-planning", async (request, reply) => {
|
|
14
|
+
const project = await requireCurrentProject(deps.projectService);
|
|
15
|
+
const result = await deps.architectRestartService.schedule(project.repoRoot, request.params.taskSlug);
|
|
16
|
+
return reply.code(202).send(result);
|
|
17
|
+
});
|
|
13
18
|
app.post("/api/tasks/:taskSlug/sessions/:role/stop", async (request) => {
|
|
14
19
|
const project = await requireCurrentProject(deps.projectService);
|
|
15
20
|
const role = parseRole(request.params.role);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { VcmError } from "../errors.js";
|
|
2
|
+
import { getTaskRuntimeRepoRoot } from "../services/task-service.js";
|
|
3
|
+
export function registerUsageAnalyticsRoutes(app, deps) {
|
|
4
|
+
app.post("/api/telemetry/v1/logs", async (request) => {
|
|
5
|
+
const project = await deps.projectService.getCurrentProject();
|
|
6
|
+
if (!project) {
|
|
7
|
+
return {};
|
|
8
|
+
}
|
|
9
|
+
const tasks = await deps.taskService.listTasks(project.repoRoot);
|
|
10
|
+
const activeTask = tasks.find((task) => task.cleanupStatus !== "cleaned");
|
|
11
|
+
if (!activeTask) {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
15
|
+
await deps.usageAnalyticsService.ingest(getTaskRuntimeRepoRoot(activeTask), config.stateRoot, request.body);
|
|
16
|
+
return {};
|
|
17
|
+
});
|
|
18
|
+
app.get("/api/tasks/:taskSlug/usage-analytics", async (request) => {
|
|
19
|
+
const project = await deps.projectService.getCurrentProject();
|
|
20
|
+
if (!project) {
|
|
21
|
+
throw new VcmError({
|
|
22
|
+
code: "PROJECT_NOT_CONNECTED",
|
|
23
|
+
message: "Connect a repository before loading task usage analytics.",
|
|
24
|
+
statusCode: 409
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
const task = await deps.taskService.loadTask(project.repoRoot, request.params.taskSlug);
|
|
28
|
+
const config = await deps.projectService.loadConfig(project.repoRoot);
|
|
29
|
+
return deps.usageAnalyticsService.getReport(getTaskRuntimeRepoRoot(task), config.stateRoot, task.taskSlug);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
|
|
8
8
|
import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
|
|
9
9
|
import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
|
|
10
|
+
import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
|
|
10
11
|
import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
|
|
11
12
|
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
12
13
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
@@ -27,6 +28,7 @@ import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-
|
|
|
27
28
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
28
29
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
29
30
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
31
|
+
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
30
32
|
import { readVcmPackageVersion } from "../app-version.js";
|
|
31
33
|
const CLI_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
32
34
|
const APP_ROOT = path.resolve(CLI_DIR, "../../..");
|
|
@@ -59,7 +61,8 @@ const AGENT_FRONTMATTER = {
|
|
|
59
61
|
description: "User-facing VCM orchestration role for task clarification, role routing, handoffs, acceptance, and PR preparation."
|
|
60
62
|
},
|
|
61
63
|
architect: {
|
|
62
|
-
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."
|
|
64
|
+
description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.",
|
|
65
|
+
tools: "Read, Grep, Glob, Bash, Edit, Write, Agent"
|
|
63
66
|
},
|
|
64
67
|
coder: {
|
|
65
68
|
description: "VCM implementation role for scoped code changes and focused tests.",
|
|
@@ -81,6 +84,11 @@ const AGENT_FRONTMATTER = {
|
|
|
81
84
|
"vcm-coder-worker": {
|
|
82
85
|
description: "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.",
|
|
83
86
|
model: "inherit"
|
|
87
|
+
},
|
|
88
|
+
"vcm-architect-scaffold-worker": {
|
|
89
|
+
description: "Foreground Architect worker for exact scaffold execution and scaffold validation.",
|
|
90
|
+
model: "opus",
|
|
91
|
+
effort: "xhigh"
|
|
84
92
|
}
|
|
85
93
|
};
|
|
86
94
|
const MANAGED_FILES = [
|
|
@@ -195,6 +203,14 @@ const MANAGED_FILES = [
|
|
|
195
203
|
commentStyle: "html",
|
|
196
204
|
category: "agent-coder-worker",
|
|
197
205
|
content: renderCoderWorkerHarnessRules()
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
path: ".claude/agents/vcm-architect-scaffold-worker.md",
|
|
209
|
+
title: "VCM Architect Scaffold Worker Agent",
|
|
210
|
+
agentName: "vcm-architect-scaffold-worker",
|
|
211
|
+
commentStyle: "html",
|
|
212
|
+
category: "agent-architect-scaffold-worker",
|
|
213
|
+
content: renderArchitectScaffoldWorkerHarnessRules()
|
|
198
214
|
}
|
|
199
215
|
];
|
|
200
216
|
const DURABLE_DOC_TEMPLATES = [
|
|
@@ -284,6 +300,12 @@ const WHOLE_FILES = [
|
|
|
284
300
|
mode: 0o644,
|
|
285
301
|
content: renderSkillFile("VCM Propose Memory Skill", "vcm-propose-memory", "Use only when VCM requests a role memory proposal during Task Harness Review.", renderVcmProposeMemorySkillRules())
|
|
286
302
|
},
|
|
303
|
+
{
|
|
304
|
+
path: ".claude/skills/restart-architect/SKILL.md",
|
|
305
|
+
category: "skill",
|
|
306
|
+
mode: 0o644,
|
|
307
|
+
content: renderSkillFile("Restart Architect Skill", "restart-architect", "Use after Architect completes and commits architecture planning and scaffold work.", renderRestartArchitectSkillRules())
|
|
308
|
+
},
|
|
287
309
|
{
|
|
288
310
|
path: ".ai/tools/request-gate-review",
|
|
289
311
|
category: "runtime-tool",
|
|
@@ -302,6 +324,12 @@ const WHOLE_FILES = [
|
|
|
302
324
|
mode: 0o755,
|
|
303
325
|
content: renderCheckScaffoldLedgerTool()
|
|
304
326
|
},
|
|
327
|
+
{
|
|
328
|
+
path: ".ai/tools/request-architect-restart",
|
|
329
|
+
category: "runtime-tool",
|
|
330
|
+
mode: 0o755,
|
|
331
|
+
content: renderRequestArchitectRestartTool()
|
|
332
|
+
},
|
|
305
333
|
{
|
|
306
334
|
path: ".ai/tools/run-long-check",
|
|
307
335
|
category: "runtime-tool",
|
|
@@ -526,6 +554,7 @@ function fixedDirectories() {
|
|
|
526
554
|
".claude/skills/vcm-gate-review/",
|
|
527
555
|
".claude/skills/vcm-report-harness-issue/",
|
|
528
556
|
".claude/skills/vcm-propose-memory/",
|
|
557
|
+
".claude/skills/restart-architect/",
|
|
529
558
|
".ai/vcm/translations/",
|
|
530
559
|
".ai/vcm/gate-reviews/",
|
|
531
560
|
".ai/tools/",
|
|
@@ -586,6 +615,9 @@ async function installManagedFile({ projectRoot, definition, dryRun, operations
|
|
|
586
615
|
if (definition.memoryBlock) {
|
|
587
616
|
nextContent = ensureVcmMemoryBlock(nextContent);
|
|
588
617
|
}
|
|
618
|
+
if (definition.agentName === "architect") {
|
|
619
|
+
nextContent = ensureAgentTool(nextContent, "Agent");
|
|
620
|
+
}
|
|
589
621
|
await writeIfChanged({
|
|
590
622
|
targetPath,
|
|
591
623
|
relativePath: definition.path,
|
|
@@ -627,10 +659,27 @@ function renderNewManagedFile(definition, block) {
|
|
|
627
659
|
const frontmatter = AGENT_FRONTMATTER[definition.agentName];
|
|
628
660
|
const tools = frontmatter.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
|
|
629
661
|
const model = frontmatter.model ? `\nmodel: ${frontmatter.model}` : "";
|
|
630
|
-
|
|
662
|
+
const effort = frontmatter.effort ? `\neffort: ${frontmatter.effort}` : "";
|
|
663
|
+
return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: ${tools}${model}${effort}\n---\n\n# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
631
664
|
}
|
|
632
665
|
return `# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
633
666
|
}
|
|
667
|
+
function ensureAgentTool(content, requiredTool) {
|
|
668
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
669
|
+
if (!frontmatterMatch) {
|
|
670
|
+
return content;
|
|
671
|
+
}
|
|
672
|
+
const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
|
|
673
|
+
if (!toolsMatch) {
|
|
674
|
+
return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
|
|
675
|
+
}
|
|
676
|
+
const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
677
|
+
if (tools.includes(requiredTool)) {
|
|
678
|
+
return content;
|
|
679
|
+
}
|
|
680
|
+
const nextTools = [...tools, requiredTool].join(", ");
|
|
681
|
+
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
682
|
+
}
|
|
634
683
|
function migrateLegacyManagedFile(definition, currentContent, block) {
|
|
635
684
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
636
685
|
if (!legacyContent) {
|
package/dist/backend/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createGitAdapter } from "./adapters/git-adapter.js";
|
|
|
12
12
|
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
|
+
import { createArchitectRestartService } from "./services/architect-restart-service.js";
|
|
15
16
|
import { createClaudeTranscriptService } from "./services/claude-transcript-service.js";
|
|
16
17
|
import { createGateReviewService } from "./services/gate-review-service.js";
|
|
17
18
|
import { createHarnessFeedbackService } from "./services/harness-feedback-service.js";
|
|
@@ -42,6 +43,7 @@ import { createTaskWorkflowService } from "./services/task-workflow-service.js";
|
|
|
42
43
|
import { createTaskLaunchService } from "./services/task-launch-service.js";
|
|
43
44
|
import { createTerminalInterruptService } from "./services/terminal-interrupt-service.js";
|
|
44
45
|
import { createTranslationService } from "./services/translation-service.js";
|
|
46
|
+
import { createUsageAnalyticsService } from "./services/usage-analytics-service.js";
|
|
45
47
|
import { createTurnReconcilerService } from "./services/turn-reconciler-service.js";
|
|
46
48
|
import { createDiagnosticsService } from "./services/diagnostics-service.js";
|
|
47
49
|
import { registerAppSettingsRoutes } from "./api/app-settings-routes.js";
|
|
@@ -57,6 +59,7 @@ import { registerRuntimeStateRoutes } from "./api/runtime-state-routes.js";
|
|
|
57
59
|
import { registerSessionRoutes } from "./api/session-routes.js";
|
|
58
60
|
import { registerTaskRoutes } from "./api/task-routes.js";
|
|
59
61
|
import { registerTranslationRoutes } from "./api/translation-routes.js";
|
|
62
|
+
import { registerUsageAnalyticsRoutes } from "./api/usage-analytics-routes.js";
|
|
60
63
|
import { registerTerminalWs } from "./ws/terminal-ws.js";
|
|
61
64
|
import { toVcmError } from "./errors.js";
|
|
62
65
|
import { readVcmPackageVersion } from "./app-version.js";
|
|
@@ -132,7 +135,8 @@ export async function createServer(deps, options = {}) {
|
|
|
132
135
|
sessionService: deps.sessionService,
|
|
133
136
|
commandDispatcher: deps.commandDispatcher,
|
|
134
137
|
translationService: deps.translationService,
|
|
135
|
-
roundService: deps.roundService
|
|
138
|
+
roundService: deps.roundService,
|
|
139
|
+
architectRestartService: deps.architectRestartService
|
|
136
140
|
});
|
|
137
141
|
registerArtifactRoutes(app, {
|
|
138
142
|
projectService: deps.projectService,
|
|
@@ -155,6 +159,11 @@ export async function createServer(deps, options = {}) {
|
|
|
155
159
|
sessionService: deps.sessionService,
|
|
156
160
|
translationService: deps.translationService
|
|
157
161
|
});
|
|
162
|
+
registerUsageAnalyticsRoutes(app, {
|
|
163
|
+
projectService: deps.projectService,
|
|
164
|
+
taskService: deps.taskService,
|
|
165
|
+
usageAnalyticsService: deps.usageAnalyticsService
|
|
166
|
+
});
|
|
158
167
|
registerGatewayRoutes(app, { gatewayService: deps.gatewayService });
|
|
159
168
|
registerTerminalWs(app, {
|
|
160
169
|
runtime: deps.runtime,
|
|
@@ -264,12 +273,18 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
264
273
|
sessionService,
|
|
265
274
|
artifactService
|
|
266
275
|
});
|
|
276
|
+
const architectRestartService = createArchitectRestartService({
|
|
277
|
+
fs,
|
|
278
|
+
taskService,
|
|
279
|
+
sessionService
|
|
280
|
+
});
|
|
267
281
|
const messageService = createMessageService({
|
|
268
282
|
fs,
|
|
269
283
|
runtime,
|
|
270
284
|
sessionService,
|
|
271
285
|
taskService,
|
|
272
|
-
taskWorkflowService
|
|
286
|
+
taskWorkflowService,
|
|
287
|
+
onRouteDelivered: ({ repoRoot, taskSlug, message }) => architectRestartService.recordRouteDelivered(repoRoot, taskSlug, message)
|
|
273
288
|
});
|
|
274
289
|
const taskLaunchService = createTaskLaunchService({
|
|
275
290
|
projectService,
|
|
@@ -312,6 +327,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
312
327
|
roundService,
|
|
313
328
|
appSettings
|
|
314
329
|
});
|
|
330
|
+
const usageAnalyticsService = createUsageAnalyticsService({ fs });
|
|
315
331
|
const gatewayChannels = createGatewayChannelRegistry([
|
|
316
332
|
createWeixinIlinkChannel(),
|
|
317
333
|
createLarkChannel()
|
|
@@ -331,7 +347,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
331
347
|
translationService,
|
|
332
348
|
roundService,
|
|
333
349
|
projectService,
|
|
334
|
-
taskWorkflowService
|
|
350
|
+
taskWorkflowService,
|
|
351
|
+
architectRestartService
|
|
335
352
|
});
|
|
336
353
|
const gatewayService = createGatewayService({
|
|
337
354
|
fs,
|
|
@@ -368,7 +385,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
368
385
|
autoMemoryService,
|
|
369
386
|
gatewayService,
|
|
370
387
|
jobGuard: createJobGuardService(),
|
|
371
|
-
translationWorkerService
|
|
388
|
+
translationWorkerService,
|
|
389
|
+
architectRestartService
|
|
372
390
|
});
|
|
373
391
|
const turnReconciler = createTurnReconcilerService({
|
|
374
392
|
sessionService,
|
|
@@ -412,6 +430,7 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
412
430
|
taskService,
|
|
413
431
|
taskCloseService,
|
|
414
432
|
taskWorkflowService,
|
|
433
|
+
architectRestartService,
|
|
415
434
|
sessionService,
|
|
416
435
|
artifactService,
|
|
417
436
|
harnessService,
|
|
@@ -431,7 +450,8 @@ export function createDefaultServerDeps(options = {}) {
|
|
|
431
450
|
runtimeRecoveryService,
|
|
432
451
|
terminalInterruptService,
|
|
433
452
|
runtime,
|
|
434
|
-
diagnosticsService
|
|
453
|
+
diagnosticsService,
|
|
454
|
+
usageAnalyticsService
|
|
435
455
|
};
|
|
436
456
|
}
|
|
437
457
|
export function getDefaultStaticDir() {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
3
|
+
import { VcmError } from "../errors.js";
|
|
4
|
+
import { getTaskRuntimeRepoRoot } from "./task-service.js";
|
|
5
|
+
const ARCHITECT_ROLE = "architect";
|
|
6
|
+
const PM_ROLE = "project-manager";
|
|
7
|
+
const COMPLETE_PLAN_PATTERN = /^Planning Result:\s*complete\s*$/im;
|
|
8
|
+
export const ARCHITECT_RESTORE_PROMPT = `This Architect session continues the current task after completed architecture planning.
|
|
9
|
+
|
|
10
|
+
Before performing any assigned work, read:
|
|
11
|
+
- .ai/vcm/handoffs/architecture-brief.md
|
|
12
|
+
- .ai/vcm/handoffs/architecture-evidence.md
|
|
13
|
+
- .ai/vcm/handoffs/architecture-plan.md
|
|
14
|
+
- the current scaffold commit and worktree state
|
|
15
|
+
- the latest Gate Review report when present
|
|
16
|
+
|
|
17
|
+
Treat the current artifacts and worktree as the source of truth. Do not repeat the completed interview or planning work unless current evidence contradicts them.`;
|
|
18
|
+
export function createArchitectRestartService(deps) {
|
|
19
|
+
const pendingByTask = new Map();
|
|
20
|
+
return {
|
|
21
|
+
async schedule(repoRoot, taskSlug) {
|
|
22
|
+
const session = await requireRunningArchitect(repoRoot, taskSlug);
|
|
23
|
+
await requireCompletePlan(repoRoot, taskSlug);
|
|
24
|
+
const key = taskKey(repoRoot, taskSlug);
|
|
25
|
+
const existing = pendingByTask.get(key);
|
|
26
|
+
if (existing?.sessionId === session.id) {
|
|
27
|
+
return { taskSlug, sessionId: session.id, status: "scheduled" };
|
|
28
|
+
}
|
|
29
|
+
pendingByTask.set(key, {
|
|
30
|
+
repoRoot,
|
|
31
|
+
taskSlug,
|
|
32
|
+
sessionId: session.id,
|
|
33
|
+
stopped: false,
|
|
34
|
+
executing: false
|
|
35
|
+
});
|
|
36
|
+
return { taskSlug, sessionId: session.id, status: "scheduled" };
|
|
37
|
+
},
|
|
38
|
+
async recordArchitectStop(repoRoot, taskSlug, sessionId) {
|
|
39
|
+
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
40
|
+
if (!pending || pending.sessionId !== sessionId) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
pending.stopped = true;
|
|
44
|
+
await tryRestart(pending);
|
|
45
|
+
},
|
|
46
|
+
async recordRouteDelivered(repoRoot, taskSlug, message) {
|
|
47
|
+
if (!isArchitectToPm(message)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
51
|
+
if (!pending) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
pending.deliveredMessageId = message.id;
|
|
55
|
+
await tryRestart(pending);
|
|
56
|
+
},
|
|
57
|
+
async recordRouteAccepted(repoRoot, taskSlug, message) {
|
|
58
|
+
if (!isArchitectToPm(message)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const pending = pendingByTask.get(taskKey(repoRoot, taskSlug));
|
|
62
|
+
if (!pending) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
pending.acceptedMessageId = message.id;
|
|
66
|
+
await tryRestart(pending);
|
|
67
|
+
},
|
|
68
|
+
clear(repoRoot, taskSlug) {
|
|
69
|
+
pendingByTask.delete(taskKey(repoRoot, taskSlug));
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
async function requireRunningArchitect(repoRoot, taskSlug) {
|
|
73
|
+
const session = await deps.sessionService.getRoleSession(repoRoot, taskSlug, ARCHITECT_ROLE);
|
|
74
|
+
if (!session || session.status !== "running") {
|
|
75
|
+
throw new VcmError({
|
|
76
|
+
code: "ARCHITECT_SESSION_NOT_RUNNING",
|
|
77
|
+
message: "Architect session is not running.",
|
|
78
|
+
statusCode: 409
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
return session;
|
|
82
|
+
}
|
|
83
|
+
async function requireCompletePlan(repoRoot, taskSlug) {
|
|
84
|
+
const task = await deps.taskService.loadTask(repoRoot, taskSlug);
|
|
85
|
+
const planPath = resolveRepoPath(getTaskRuntimeRepoRoot(task), path.posix.join(task.handoffDir, "architecture-plan.md"));
|
|
86
|
+
if (!(await deps.fs.pathExists(planPath))) {
|
|
87
|
+
throw incompletePlanError("architecture-plan.md does not exist.");
|
|
88
|
+
}
|
|
89
|
+
const content = await deps.fs.readText(planPath);
|
|
90
|
+
if (!COMPLETE_PLAN_PATTERN.test(content)) {
|
|
91
|
+
throw incompletePlanError("architecture-plan.md is not marked complete.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function tryRestart(pending) {
|
|
95
|
+
if (pending.executing
|
|
96
|
+
|| !pending.stopped
|
|
97
|
+
|| !pending.deliveredMessageId
|
|
98
|
+
|| pending.deliveredMessageId !== pending.acceptedMessageId) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const session = await deps.sessionService.getRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE);
|
|
102
|
+
if (!session
|
|
103
|
+
|| session.id !== pending.sessionId
|
|
104
|
+
|| session.status !== "running"
|
|
105
|
+
|| session.activityStatus !== "idle") {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
pending.executing = true;
|
|
109
|
+
try {
|
|
110
|
+
await requireCompletePlan(pending.repoRoot, pending.taskSlug);
|
|
111
|
+
await deps.sessionService.restartRoleSession(pending.repoRoot, pending.taskSlug, ARCHITECT_ROLE, {
|
|
112
|
+
permissionMode: session.permissionMode,
|
|
113
|
+
model: session.model,
|
|
114
|
+
effort: session.effort,
|
|
115
|
+
appendSystemPrompt: ARCHITECT_RESTORE_PROMPT
|
|
116
|
+
});
|
|
117
|
+
pendingByTask.delete(taskKey(pending.repoRoot, pending.taskSlug));
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
pending.executing = false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function isArchitectToPm(message) {
|
|
125
|
+
return message.fromRole === ARCHITECT_ROLE && message.toRole === PM_ROLE;
|
|
126
|
+
}
|
|
127
|
+
function taskKey(repoRoot, taskSlug) {
|
|
128
|
+
return `${repoRoot}\0${taskSlug}`;
|
|
129
|
+
}
|
|
130
|
+
function incompletePlanError(reason) {
|
|
131
|
+
return new VcmError({
|
|
132
|
+
code: "ARCHITECT_PLAN_INCOMPLETE",
|
|
133
|
+
message: `Architect restart cannot be scheduled. ${reason}`,
|
|
134
|
+
statusCode: 409
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -227,6 +227,9 @@ export function createClaudeHookService(deps) {
|
|
|
227
227
|
role: input.role,
|
|
228
228
|
prompt: stringOrUndefined(input.event.prompt)
|
|
229
229
|
});
|
|
230
|
+
if (submitted) {
|
|
231
|
+
await deps.architectRestartService?.recordRouteAccepted(context.project.repoRoot, context.taskSlug, submitted);
|
|
232
|
+
}
|
|
230
233
|
return {
|
|
231
234
|
ok: true,
|
|
232
235
|
eventName,
|
|
@@ -437,6 +440,9 @@ export function createClaudeHookService(deps) {
|
|
|
437
440
|
occurredAt: session.lastTurnEndedAt ?? session.updatedAt
|
|
438
441
|
});
|
|
439
442
|
}
|
|
443
|
+
if (eventName === "Stop" && input.role === "architect" && session) {
|
|
444
|
+
await deps.architectRestartService?.recordArchitectStop(context.project.repoRoot, context.taskSlug, session.id);
|
|
445
|
+
}
|
|
440
446
|
if (options.notifyGateway && session && input.role === "project-manager") {
|
|
441
447
|
void deps.gatewayService?.handlePmStop({
|
|
442
448
|
repoRoot: context.project.repoRoot,
|
|
@@ -56,6 +56,7 @@ const CODE_DIFF_ANALYSIS_FIELDS = [
|
|
|
56
56
|
const SOURCE_ARTIFACTS = {
|
|
57
57
|
"architecture-plan": [
|
|
58
58
|
".ai/vcm/handoffs/architecture-brief.md",
|
|
59
|
+
".ai/vcm/handoffs/architecture-evidence.md",
|
|
59
60
|
".ai/vcm/handoffs/architecture-plan.md"
|
|
60
61
|
],
|
|
61
62
|
"validation-adequacy": [
|
|
@@ -228,6 +229,30 @@ export function createGateReviewService(deps) {
|
|
|
228
229
|
message: architectureBriefError
|
|
229
230
|
};
|
|
230
231
|
}
|
|
232
|
+
const architectureEvidenceError = await readArchitectureEvidenceError(deps.fs, context.taskRepoRoot);
|
|
233
|
+
if (architectureEvidenceError) {
|
|
234
|
+
index = applyGateState(index, gate, {
|
|
235
|
+
status: "failed",
|
|
236
|
+
decision: undefined,
|
|
237
|
+
error: architectureEvidenceError,
|
|
238
|
+
exceptionReason: undefined,
|
|
239
|
+
requestId: undefined,
|
|
240
|
+
requestPath: undefined,
|
|
241
|
+
inputHash: undefined,
|
|
242
|
+
requestedAt: undefined,
|
|
243
|
+
startedAt: undefined,
|
|
244
|
+
completedAt: now(),
|
|
245
|
+
callbackStatus: "not_sent",
|
|
246
|
+
callbackError: undefined
|
|
247
|
+
}, now(), true);
|
|
248
|
+
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
249
|
+
return {
|
|
250
|
+
status: "failed_to_start",
|
|
251
|
+
gate,
|
|
252
|
+
record: index.gates[gate],
|
|
253
|
+
message: architectureEvidenceError
|
|
254
|
+
};
|
|
255
|
+
}
|
|
231
256
|
}
|
|
232
257
|
const coreInput = await readCoreInputArtifact(deps.fs, context.taskRepoRoot, gate);
|
|
233
258
|
if (coreInput && coreInput.status !== "ready") {
|
|
@@ -961,6 +986,21 @@ async function readArchitectureBriefError(fs, taskRepoRoot) {
|
|
|
961
986
|
}
|
|
962
987
|
return undefined;
|
|
963
988
|
}
|
|
989
|
+
async function readArchitectureEvidenceError(fs, taskRepoRoot) {
|
|
990
|
+
const relativePath = ".ai/vcm/handoffs/architecture-evidence.md";
|
|
991
|
+
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
992
|
+
if (!await fs.pathExists(absolutePath)) {
|
|
993
|
+
return `${relativePath} is missing. Complete architecture evidence before requesting architecture-plan review.`;
|
|
994
|
+
}
|
|
995
|
+
const content = await fs.readText(absolutePath);
|
|
996
|
+
if (content.trim().length === 0) {
|
|
997
|
+
return `${relativePath} is empty. Complete architecture evidence before requesting architecture-plan review.`;
|
|
998
|
+
}
|
|
999
|
+
if (!/^\s*Architecture Evidence Status\s*:\s*complete\s*$/im.test(content)) {
|
|
1000
|
+
return `${relativePath} is incomplete. Finish current-worktree evidence before requesting architecture-plan review.`;
|
|
1001
|
+
}
|
|
1002
|
+
return undefined;
|
|
1003
|
+
}
|
|
964
1004
|
async function commandStdout(runner, cwd, args) {
|
|
965
1005
|
const result = await runner.run("git", args, { cwd });
|
|
966
1006
|
return result.exitCode === 0 ? result.stdout : "";
|
|
@@ -4,6 +4,7 @@ import { promisify } from "node:util";
|
|
|
4
4
|
import { renderArchitectHarnessRules } from "../templates/harness/architect-agent.js";
|
|
5
5
|
import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
|
|
6
6
|
import { renderCoderWorkerHarnessRules } from "../templates/harness/coder-worker-agent.js";
|
|
7
|
+
import { renderArchitectScaffoldWorkerHarnessRules } from "../templates/harness/architect-scaffold-worker-agent.js";
|
|
7
8
|
import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTranslatorAgentRules, renderVcmGateReviewSkillRules } from "../templates/harness/gate-review.js";
|
|
8
9
|
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
9
10
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
@@ -24,6 +25,7 @@ import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-
|
|
|
24
25
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
25
26
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
26
27
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
28
|
+
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
27
29
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
28
30
|
import { VcmError } from "../errors.js";
|
|
29
31
|
import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
|
|
@@ -172,6 +174,14 @@ const HARNESS_FILES = [
|
|
|
172
174
|
ownership: "whole-file",
|
|
173
175
|
renderRules: renderVcmProposeMemorySkillRules
|
|
174
176
|
},
|
|
177
|
+
{
|
|
178
|
+
kind: "skill-restart-architect",
|
|
179
|
+
path: ".claude/skills/restart-architect/SKILL.md",
|
|
180
|
+
title: "Restart Architect Skill",
|
|
181
|
+
frontmatter: renderSkillFrontmatter("restart-architect", "Use after Architect completes and commits architecture planning and scaffold work."),
|
|
182
|
+
ownership: "whole-file",
|
|
183
|
+
renderRules: renderRestartArchitectSkillRules
|
|
184
|
+
},
|
|
175
185
|
{
|
|
176
186
|
kind: "agent-gate-reviewer",
|
|
177
187
|
path: ".claude/agents/gate-reviewer.md",
|
|
@@ -202,6 +212,13 @@ const HARNESS_FILES = [
|
|
|
202
212
|
frontmatter: renderAgentFrontmatter("vcm-coder-worker", "Bounded VCM implementation worker for assigned modules, files, and VCM:CODE markers from Coder.", { model: "inherit" }),
|
|
203
213
|
renderRules: renderCoderWorkerHarnessRules
|
|
204
214
|
},
|
|
215
|
+
{
|
|
216
|
+
kind: "agent-architect-scaffold-worker",
|
|
217
|
+
path: ".claude/agents/vcm-architect-scaffold-worker.md",
|
|
218
|
+
title: "VCM Architect Scaffold Worker Agent",
|
|
219
|
+
frontmatter: renderAgentFrontmatter("vcm-architect-scaffold-worker", "Foreground Architect worker for exact scaffold execution and scaffold validation.", { model: "opus", effort: "xhigh" }),
|
|
220
|
+
renderRules: renderArchitectScaffoldWorkerHarnessRules
|
|
221
|
+
},
|
|
205
222
|
{
|
|
206
223
|
kind: "tool-request-gate-review",
|
|
207
224
|
path: ".ai/tools/request-gate-review",
|
|
@@ -223,6 +240,13 @@ const HARNESS_FILES = [
|
|
|
223
240
|
ownership: "raw-file",
|
|
224
241
|
renderRules: renderCheckScaffoldLedgerTool
|
|
225
242
|
},
|
|
243
|
+
{
|
|
244
|
+
kind: "tool-request-architect-restart",
|
|
245
|
+
path: ".ai/tools/request-architect-restart",
|
|
246
|
+
title: "Request Architect Restart Tool",
|
|
247
|
+
ownership: "raw-file",
|
|
248
|
+
renderRules: renderRequestArchitectRestartTool
|
|
249
|
+
},
|
|
226
250
|
{
|
|
227
251
|
kind: "agent-project-manager",
|
|
228
252
|
path: ".claude/agents/project-manager.md",
|
|
@@ -237,7 +261,7 @@ const HARNESS_FILES = [
|
|
|
237
261
|
title: "Architect Agent",
|
|
238
262
|
memoryBlock: true,
|
|
239
263
|
blankLineBeforeEnd: true,
|
|
240
|
-
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."),
|
|
264
|
+
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent" }),
|
|
241
265
|
renderRules: renderArchitectHarnessRules
|
|
242
266
|
},
|
|
243
267
|
{
|
|
@@ -1192,6 +1216,9 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1192
1216
|
};
|
|
1193
1217
|
}
|
|
1194
1218
|
const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
|
|
1219
|
+
const nextContent = definition.kind === "agent-architect"
|
|
1220
|
+
? ensureAgentTool(insertedContent, "Agent")
|
|
1221
|
+
: insertedContent;
|
|
1195
1222
|
return {
|
|
1196
1223
|
definition,
|
|
1197
1224
|
status: {
|
|
@@ -1206,13 +1233,16 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1206
1233
|
action: "insert",
|
|
1207
1234
|
reason: "File exists but does not contain VCM managed rules."
|
|
1208
1235
|
},
|
|
1209
|
-
nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(
|
|
1236
|
+
nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(nextContent) : nextContent
|
|
1210
1237
|
};
|
|
1211
1238
|
}
|
|
1212
1239
|
const managedVersion = match[1] ? Number(match[1]) : undefined;
|
|
1213
1240
|
const currentBlock = match[0];
|
|
1214
1241
|
const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
|
|
1215
|
-
const
|
|
1242
|
+
const memoryUpdatedContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
|
|
1243
|
+
const nextContent = definition.kind === "agent-architect"
|
|
1244
|
+
? ensureAgentTool(memoryUpdatedContent, "Agent")
|
|
1245
|
+
: memoryUpdatedContent;
|
|
1216
1246
|
const action = currentContent === nextContent ? "ok" : "update";
|
|
1217
1247
|
return {
|
|
1218
1248
|
definition,
|
|
@@ -1323,6 +1353,22 @@ function renderNewHarnessFile(definition, block, contentAfterBlock = definition.
|
|
|
1323
1353
|
const suffix = contentAfterBlock?.trim();
|
|
1324
1354
|
return `${frontmatter}# ${definition.title}\n\n${block}${suffix ? `\n\n${suffix}` : ""}\n`;
|
|
1325
1355
|
}
|
|
1356
|
+
function ensureAgentTool(content, requiredTool) {
|
|
1357
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
1358
|
+
if (!frontmatterMatch) {
|
|
1359
|
+
return content;
|
|
1360
|
+
}
|
|
1361
|
+
const toolsMatch = frontmatterMatch[0].match(/^tools:\s*(.*)$/m);
|
|
1362
|
+
if (!toolsMatch) {
|
|
1363
|
+
return content.replace(/^(---\r?\n[\s\S]*?)(\r?\n---)/, `$1\ntools: ${requiredTool}$2`);
|
|
1364
|
+
}
|
|
1365
|
+
const tools = toolsMatch[1].split(",").map((tool) => tool.trim()).filter(Boolean);
|
|
1366
|
+
if (tools.includes(requiredTool)) {
|
|
1367
|
+
return content;
|
|
1368
|
+
}
|
|
1369
|
+
const nextTools = [...tools, requiredTool].join(", ");
|
|
1370
|
+
return content.replace(frontmatterMatch[0], frontmatterMatch[0].replace(toolsMatch[0], `tools: ${nextTools}`));
|
|
1371
|
+
}
|
|
1326
1372
|
function migrateLegacyHarnessFile(definition, currentContent, block) {
|
|
1327
1373
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
1328
1374
|
if (!legacyContent) {
|
|
@@ -1454,7 +1500,8 @@ function isPlainObject(value) {
|
|
|
1454
1500
|
function renderAgentFrontmatter(name, description, options = {}) {
|
|
1455
1501
|
const tools = options.tools ?? "Read, Grep, Glob, Bash, Edit, Write";
|
|
1456
1502
|
const model = options.model ? `\nmodel: ${options.model}` : "";
|
|
1457
|
-
|
|
1503
|
+
const effort = options.effort ? `\neffort: ${options.effort}` : "";
|
|
1504
|
+
return `---\nname: ${name}\ndescription: ${description}\ntools: ${tools}${model}${effort}\n---`;
|
|
1458
1505
|
}
|
|
1459
1506
|
function renderSkillFrontmatter(name, description) {
|
|
1460
1507
|
return `---\nname: ${name}\ndescription: ${description}\n---`;
|