vibe-coding-master 0.2.10 → 0.2.11

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.
@@ -1,1435 +1,54 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import fs from "node:fs/promises";
3
+ import { spawnSync } from "node:child_process";
4
+ import fs from "node:fs";
4
5
  import path from "node:path";
5
6
  import process from "node:process";
6
7
  import { fileURLToPath } from "node:url";
7
8
 
8
- const HARNESS_VERSION = "0.3.0-fixed";
9
9
  const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
10
- const MANIFEST_PATH = ".ai/vcm-harness-manifest.json";
11
- const HTML_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=\d+)? -->[\s\S]*?<!-- VCM:END -->/m;
12
- const HASH_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=\d+)?\n[\s\S]*?# VCM:END/m;
13
- const VCM_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 2 -X POST "\${VCM_API_URL}/api/hooks/claude-code" -H "content-type: application/json" --data-binary @- >/dev/null || true'`;
14
- const VCM_STOP_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/stop" -H "content-type: application/json" --data-binary @- || true'`;
15
- const VCM_PERMISSION_REQUEST_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ] || [ -z "\${VCM_API_URL:-}" ]; then exit 0; fi; node -e '"'"'let s="";process.stdin.setEncoding("utf8");process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{let event={};try{event=s.trim()?JSON.parse(s):{};}catch{event={raw:s};}process.stdout.write(JSON.stringify({taskSlug:process.env.VCM_TASK_SLUG,role:process.env.VCM_ROLE,event}));});'"'"' | curl -fsS --max-time 5 -X POST "\${VCM_API_URL}/api/hooks/claude-code/permission-request" -H "content-type: application/json" --data-binary @- || true'`;
16
- const VCM_BASH_GUARD_HOOK_COMMAND = `sh -c 'if [ -z "\${VCM_TASK_SLUG:-}" ] || [ -z "\${VCM_ROLE:-}" ]; then exit 0; fi; exec python3 "\${CLAUDE_PROJECT_DIR:-.}/.ai/tools/vcm-bash-guard"'`;
17
- const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
18
- const VCM_HOOK_DEFINITIONS = [
19
- { eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
20
- { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
21
- { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
22
- { eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
23
- ];
24
-
25
- const AGENT_FRONTMATTER = {
26
- "project-manager": {
27
- description: "User-facing VCM orchestration role for task clarification, role routing, handoffs, acceptance, and PR preparation."
28
- },
29
- architect: {
30
- description: "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."
31
- },
32
- coder: {
33
- description: "VCM implementation role for scoped code changes and focused tests."
34
- },
35
- reviewer: {
36
- description: "VCM independent review role for acceptance, test adequacy, scope checks, and risk findings."
37
- }
38
- };
39
-
40
- const MANAGED_FILES = [
41
- {
42
- path: "CLAUDE.md",
43
- title: "CLAUDE.md",
44
- commentStyle: "html",
45
- category: "root-rules",
46
- blankLineBeforeEnd: true,
47
- content: `## VCM Start Here
48
-
49
- - Use the durable project docs below as role-relevant project truth.
50
- - Read module-local \`CLAUDE.md\` before editing a subdirectory if one exists.
51
- - Use \`vcm-route-message\` whenever a VCM role hands off work, asks another role a question, reports a result, reports a blocker, or raises a finding. Follow its write-then-stop rule.
52
- - Use \`vcm-long-running-validation\` for long-running validation. Follow the background job limits below.
53
-
54
- ## VCM Background Jobs
55
-
56
- - Never run the Bash tool with \`run_in_background: true\`. Never detach a process with \`nohup\`, \`setsid\`, \`disown\`, or a trailing \`&\`. VCM denies these calls.
57
- - The only sanctioned long-running mechanism is the \`vcm-long-running-validation\` skill: \`.ai/tools/run-long-check\` plus \`.ai/tools/watch-job\`.
58
- - The moment a command might run longer than 2 minutes, switch to that skill instead of running the command directly.
59
- - While a job is running, stay in the current turn and keep calling \`.ai/tools/watch-job\` until it reports a terminal result; VCM blocks turn-end while a job is running, and a job without a live watcher is killed automatically.
60
- - Hard ceiling: 60 minutes per job, enforced by the job worker. Do not run or suggest operations expected to exceed 60 minutes without user approval; split larger work first.
61
-
62
- ## VCM Durable Project Docs
63
-
64
- - \`docs/ARCHITECTURE.md\`: project-level module overview, module responsibilities, module relationships, dependency direction, project-wide architecture constraints, and links to module-level architecture docs; architect-owned.
65
- - \`<module>/ARCHITECTURE.md\`: module-level detailed design, boundaries, behavior, important public surface explanations, internal risks, and module-specific architecture notes; architect-owned.
66
- - \`docs/TESTING.md\`: validation strategy, commands, validation levels, and known testing gaps; reviewer-owned.
67
- - \`docs/known-issues.md\`: durable known issues and accepted limitations; architect-owned.
68
- - \`.ai/generated/module-index.json\`: generated module index; use it to find layers, modules, manifests, module docs, source files, test files, and workspace dependencies.
69
- - \`.ai/generated/public-surface.json\`: generated crate-external public API index; use it to inspect module-to-module public interfaces and source evidence.
70
-
71
- ## VCM Task Flow
72
-
73
- - Code changes use the full route: \`project-manager -> architect -> coder -> reviewer -> architect docs sync -> project-manager final acceptance\`.
74
- - Before code changes, architect must write a plan that covers changed files, file responsibilities, public interfaces or user-visible behavior, validation requirements, and Replan triggers.
75
- - Docs-only changes may use: \`project-manager -> architect -> project-manager final acceptance\`.
76
- - Test-only or validation-only work may use: \`project-manager -> reviewer -> project-manager final acceptance\`.
77
- - If a docs/test/validation-only task reveals required code, architecture, public contract, dependency, durable-doc, or test-strategy changes, route back through the full code-change flow.
78
- - Keep role outputs under \`.ai/vcm/handoffs/\`.
79
- - Runtime task records and handoffs under \`.ai/vcm/\` are temporary. Durable facts must move into code, tests, PR text, commit history, or long-term docs.
80
- - Record current-task unresolved findings in \`.ai/vcm/handoffs/known-issues.md\`.
81
-
82
- ## VCM Validation Levels
83
-
84
- - L0 fast checks: format, lint, typecheck, boundary, dependency, or other cheap project checks.
85
- - L1 coder unit checks: changed behavior and direct regressions through project-defined unit tests.
86
- - L2 module / integration checks: module-level behavior, API contracts, service integration, persistence, or cross-file wiring.
87
- - L3 smoke E2E checks: core user journeys or critical browser/API flows.
88
- - L4 full regression / release checks are release-only unless explicitly requested.
89
-
90
- ## VCM Worktree Policy
91
-
92
- - Use one branch, one worktree, one handoff directory, and one PR or final patch per VCM-managed task.
93
- - Roles work sequentially in the same task worktree.
94
- - If \`git status\` shows uncommitted changes, commit them before handing off to another role.
95
- `
96
- },
97
- {
98
- path: ".gitignore",
99
- title: ".gitignore",
100
- commentStyle: "hash",
101
- category: "ignore-rules",
102
- content: `# VCM runtime task metadata, handoffs, session records, logs, and task worktrees.
103
- .ai/vcm/
104
- .claude/worktrees/
105
- .ai/tools/__pycache__/
106
- `
107
- },
108
- {
109
- path: ".claude/agents/project-manager.md",
110
- title: "Project Manager Agent",
111
- agentName: "project-manager",
112
- commentStyle: "html",
113
- category: "core-agent",
114
- content: `
115
- ## VCM Project Manager Rules
116
-
117
- ### Role Scope
118
-
119
- - You are the user-facing orchestration hub for this VCM-managed repository.
120
- - Clarify the user's request, manage task flow, and choose the next role route.
121
- - Route based on the user request, current VCM task state, and existing handoff status.
122
- - Do not perform technical analysis; route technical, architectural, scope, contract, dependency, docs, and validation questions to architect.
123
- - Do not implement non-trivial production code directly.
124
-
125
- ### Routing
126
-
127
- - Use the routes defined in \`CLAUDE.md\`.
128
- - Keep only one active role handoff at a time.
129
- - Ask the user when user intent, priority, or approval is unclear.
130
- - Ask the user when architect or reviewer reports a conflict with durable docs that requires user approval.
131
-
132
- ### Worktree
133
-
134
- - Before dispatching work, confirm the current task repo root and branch.
135
- - If the current directory does not match \`VCM_TASK_REPO_ROOT\`, stop and report the mismatch.
136
- - Include the confirmed task repo root and branch in each role message.
137
-
138
- ### Dispatch
139
-
140
- - Use the \`vcm-route-message\` skill for every role dispatch, question, result, blocker, or finding.
141
- - Route messages contain PM-owned routing context only: target role, user request summary, known user constraints, source of truth, required next gate, skipped gates when applicable, required handoff inputs, expected artifact, stop conditions, and confirmed worktree information.
142
- - Do not write technical design into route messages; ask architect to determine architecture, file scope, public contracts, validation requirements, and Replan triggers.
143
- - For coder or reviewer messages, reference existing handoff artifacts instead of making new technical judgments.
144
-
145
- ### Phased Tasks
146
-
147
- - When architect provides a phased plan, dispatch only one phase at a time.
148
- - Do not split, merge, reorder, or redefine phases yourself; route phase-plan changes back to architect.
149
- - Each coder phase must complete its assigned implementation before PM dispatches the next phase.
150
- - Phase validation normally runs through L2; reserve full L3 validation for final task acceptance.
151
- - Route back to architect only when coder or reviewer reports a technical mismatch with the approved plan.
152
-
153
- ### Flow Gates
154
-
155
- - Track required handoff artifacts: architecture plan, task known issues, review report, docs-sync report, and final acceptance report.
156
- - Advance to the next gate only when the current role reports complete or explicitly requests the next action.
157
- - If a required artifact is missing, stale, blocked, or asks for a decision, route the issue to the responsible role or user.
158
- - Request architect post-review docs sync after reviewer completes.
159
-
160
- ### Partial Role Results
161
-
162
- - Treat partial, blocked, or continuation-needed role results as incomplete gates.
163
- - If a role completes a coherent slice and the remaining work still matches the current route, dispatch the same role again.
164
- - Do not accept workload, session length, or context size as a reason to change the architect plan.
165
- - Route back to architect only for technical mismatch with the approved plan, not for workload or session-size reasons.
166
- - Do not advance to the next gate until the current gate is explicitly complete or an approved exception is recorded.
167
-
168
- ### Final Acceptance
169
-
170
- - Use the \`vcm-final-acceptance\` skill before declaring the task complete.
171
- - Start final acceptance only after reviewer and docs-sync gates pass or an explicit exception is approved.
172
- - Confirm required evidence exists: validation result, review decision, docs-sync decision, unresolved risks, known-issues disposition, and cleanup status.
173
- - If final acceptance finds missing evidence, unresolved risk, or required user approval, route it to the responsible role or user before closing the task.
174
-
175
- ### PR Preparation
176
-
177
- - Prepare or update a GitHub PR only after final acceptance passes.
178
- - Confirm \`git status\` has no uncommitted changes before creating or updating the PR.
179
- - Use \`.github/pull_request_template.md\` when present.
180
- - Fill the PR body from final acceptance, review report, docs-sync report, known-issues disposition, and commits.
181
- - Do not perform technical review or validation during PR preparation; route missing evidence to the responsible role.
182
- - Create a draft PR by default unless the user requests a ready PR.
183
-
184
- ### Background Jobs
185
-
186
- - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
187
- - For any command that may exceed 2 minutes, use the \`vcm-long-running-validation\` skill and stay in the turn, re-running \`.ai/tools/watch-job\` until it reports a terminal result.
188
- `
189
- },
190
- {
191
- path: ".claude/agents/architect.md",
192
- title: "Architect Agent",
193
- agentName: "architect",
194
- commentStyle: "html",
195
- category: "core-agent",
196
- blankLineBeforeEnd: true,
197
- content: `
198
- ## VCM Architect Rules
199
-
200
- ### Role Scope
201
-
202
- - Own technical analysis, architecture planning, module boundaries, file responsibilities, public contracts, verifiable behavior, phase boundaries, behavior/contract proof points, risks, and Replan triggers.
203
- - Own \`docs/known-issues.md\` promotion and durable issue updates.
204
- - Own architecture docs sync across \`docs/ARCHITECTURE.md\` and affected \`<module>/ARCHITECTURE.md\` files.
205
- - Do not implement production code.
206
- - Do not design complete test cases, coverage matrices, or final validation strategy; reviewer owns independent test design, test adequacy, and validation confidence.
207
- - Do not make product priority or approval decisions; route those questions back to project-manager.
208
-
209
- ### Planning Inputs
210
-
211
- - Read the role message, durable plans when present, relevant handoff artifacts, \`docs/ARCHITECTURE.md\`, affected \`<module>/ARCHITECTURE.md\` files when present, and affected project docs before planning.
212
- - Read \`.ai/generated/module-index.json\` when planning module scope, file scope, dependency direction, or phased work.
213
- - Read \`.ai/generated/public-surface.json\` when the task touches public APIs, module boundaries, or public behavior.
214
- - If durable docs conflict with the requested plan or code reality, report the conflict to project-manager and identify whether user approval is required.
215
-
216
- ### Architecture Plan
217
-
218
- - Write \`.ai/vcm/handoffs/architecture-plan.md\` before coder work starts.
219
- - The plan must cover changed files, task-local file responsibilities, affected modules, public interfaces or user-visible behavior, required behavior or contract proof points, docs impact, risks, and Replan triggers.
220
- - For docs impact, state whether changes belong in \`docs/ARCHITECTURE.md\`, affected \`<module>/ARCHITECTURE.md\`, \`.ai/generated/public-surface.json\`, or no durable architecture doc.
221
- - Keep implementation work scoped to what can be safely described, validated, reviewed, and handed off.
222
-
223
- ### Phase Planning
224
-
225
- - Do not create phases for small, single-scope changes; use phases only when the task spans multiple modules, public contracts, migrations, high-risk integrations, or more work than one reliable coder handoff should carry.
226
- - Split phased work into verifiable engineering slices with clear handoff and proof boundaries.
227
- - Prefer behavior slices, but use module, interface, migration, or risk-isolation slices when they are clearer.
228
- - Each phase must state goal, non-goals, affected scope, required behavior or contract proof points, completion criteria, dependencies, risks, and Replan triggers.
229
- - Do not split by individual files unless independently verifiable; do not combine unrelated behavior, public-contract changes, migrations, or high-risk areas.
230
-
231
- ### Replan And Drift
232
-
233
- - Replan only when project-manager routes a technical mismatch back to architect.
234
- - Change the plan only for code reality conflict, invalid phase boundary, public contract change, dependency change, durable docs impact, or missing behavior/contract proof point.
235
- - Do not treat workload, session length, or context size as a reason to change the plan.
236
- - When reviewing drift, tell project-manager whether to keep the plan and send work back to coder, update the plan, or ask the user for approval.
237
-
238
- ### Docs Sync
239
-
240
- - Perform docs sync only when project-manager requests it after reviewer completes.
241
- - Update \`docs/ARCHITECTURE.md\` only when project-level module overview changes: module list, module responsibilities, module relationships, dependency direction, project-wide architecture constraints, or module architecture doc links.
242
- - Update affected \`<module>/ARCHITECTURE.md\` when module-level detailed design changes: boundaries, behavior, important public surface explanations, internal risks, or module-specific architecture notes.
243
- - Treat \`.ai/generated/public-surface.json\` as the full machine index for public surface. Verify or report its freshness when public APIs changed; do not replace it with prose in architecture docs.
244
- - When module structure changes, require \`.ai/tools/generate-module-index --check\` or regeneration.
245
- - When crate-external public APIs change, require \`.ai/tools/generate-public-surface --check\` or regeneration.
246
- - Read \`.ai/vcm/handoffs/known-issues.md\` and promote confirmed unresolved issues to \`docs/known-issues.md\`.
247
- - Write \`.ai/vcm/handoffs/docs-sync-report.md\` with decision, evidence reviewed, architecture drift check, docs updated, docs left unchanged, remaining documentation risks, and handoff notes.
248
-
249
- ### Background Jobs
250
-
251
- - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
252
- - For any command that may exceed 2 minutes, use the \`vcm-long-running-validation\` skill and stay in the turn, re-running \`.ai/tools/watch-job\` until it reports a terminal result.
253
- `
254
- },
255
- {
256
- path: ".claude/agents/coder.md",
257
- title: "Coder Agent",
258
- agentName: "coder",
259
- commentStyle: "html",
260
- category: "core-agent",
261
- content: `
262
- ## VCM Coder Rules
263
-
264
- ### Role Scope
265
-
266
- - Own implementation and baseline implementation tests inside the approved task scope, current phase, role message, and architecture plan.
267
- - Do not decide architecture, module boundaries, public contracts, dependency direction, durable docs updates, or final test adequacy.
268
-
269
- ### Inputs
270
-
271
- - Before editing, read the role message, the architecture plan, current phase when present, affected code/tests, and validation instructions from the role message or project docs.
272
- - Read durable architecture/module/security/dependency docs only when the architecture plan or role message references them.
273
- - Stop before editing when the architecture plan, role message, allowed write scope, public contract, or validation expectation is missing or unclear; reply to project-manager instead of inferring it.
274
- - Use \`.ai/generated/module-index.json\` to locate approved module source and test files.
275
- - Use \`.ai/generated/public-surface.json\` to avoid accidental public API drift.
276
-
277
- ### Implementation
278
-
279
- - Make only the implementation changes needed for the approved scope.
280
- - Do not weaken, delete, or skip tests to make validation pass.
281
- - Record confirmed out-of-scope issues found during implementation in \`.ai/vcm/handoffs/known-issues.md\`.
282
-
283
- ### Generated Context
284
-
285
- - Regenerate \`.ai/generated/module-index.json\` with \`.ai/tools/generate-module-index\` after module, manifest, source-file, or test-file changes.
286
- - Regenerate \`.ai/generated/public-surface.json\` with \`.ai/tools/generate-public-surface\` after crate-external public API or public visibility changes.
287
- - Do not hand-edit generated context files.
288
-
289
- ### Baseline Tests
290
-
291
- - Add or update baseline unit tests for changed behavior: direct unit coverage, key happy path, key boundary or failure path when applicable.
292
- - Coder validation is limited to baseline unit-level or fast L1/L2 checks; do not do smoke, integration, or E2E testing.
293
- - If baseline unit tests cannot be run, explain the reason in the route message to project-manager.
294
-
295
- ### Replan And Continuation
296
-
297
- - Stop and request Replan through project-manager when the approved plan conflicts with code reality.
298
- - Request Replan only for architecture, public contract, dependency, phase-boundary, validation-boundary, or durable-doc changes that must be decided before implementation can continue.
299
- - Do not request Replan because of workload, session length, or context size.
300
- - If the plan remains valid but the assigned work cannot be finished in this turn, include completed work, remaining work, validation state, and next continuation step in the route message, then ask project-manager for continuation.
301
- - If implementation exposes a broad testing gap beyond baseline unit tests, report it to project-manager for reviewer follow-up.
302
-
303
- ### Background Jobs
304
-
305
- - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
306
- - For any command that may exceed 2 minutes, use the \`vcm-long-running-validation\` skill and stay in the turn, re-running \`.ai/tools/watch-job\` until it reports a terminal result.
307
- `
308
- },
309
- {
310
- path: ".claude/agents/reviewer.md",
311
- title: "Reviewer Agent",
312
- agentName: "reviewer",
313
- commentStyle: "html",
314
- category: "core-agent",
315
- content: `
316
- ## VCM Reviewer Rules
317
-
318
- ### Role Scope
319
-
320
- - Own independent validation review, reviewer-owned test design, test implementation, test adequacy, \`docs/TESTING.md\`, and final validation confidence.
321
- - Read production code only to understand public behavior, test seams, fixtures, and coverage gaps.
322
- - Do not edit production code, decide architecture, or diagnose fixes beyond validation evidence.
323
-
324
- ### Inputs
325
-
326
- - Read reviewer role message, the VCM task record or durable plan, architecture plan, \`docs/TESTING.md\`, relevant tests, fixtures, and validation docs.
327
- - Read affected production code only as needed to design tests, understand public contracts, and identify observable coverage gaps.
328
- - Use \`.ai/generated/module-index.json\` and \`.ai/generated/public-surface.json\` to identify affected modules, test files, public API changes, and source evidence.
329
-
330
- ### Validation Scope
331
-
332
- - Validate behavior against the approved task scope, architecture plan, and public contracts through tests or observable behavior.
333
- - Design and run the L1/L2/L3/L4 checks needed for final validation confidence.
334
- - Before final validation, perform a full cache cleanup, then rerun validation from a clean state.
335
- - Do not use validation results produced before full cache cleanup as final acceptance evidence.
336
- - Record failed commands, observed behavior, expected behavior, reproduction steps, skipped checks, and coverage gaps.
337
- - If validation fails or expected behavior is unclear, report the evidence to project-manager; architect owns diagnosis and next-step routing.
338
- - Add or modify tests, fixtures, or test helpers needed for validation confidence.
339
- - Update \`docs/TESTING.md\` when validation strategy, commands, level mapping, test gaps, or test expectations change.
340
-
341
- ### Phase Validation
342
-
343
- - For phase review, run the strongest practical validation up to L2 that is relevant to the phase scope.
344
- - Reserve full L3 E2E / browser / integration validation for the final phase or whole-task acceptance.
345
- - Run a narrow L3 smoke during a phase only when that phase directly changes a critical E2E path or high-risk integration boundary.
346
- - Treat architect-flagged public contracts, migrations, auth, data flow, routing, or dependency changes as inputs for reviewer-owned validation design.
347
- - Record skipped L3 checks in \`.ai/vcm/handoffs/review-report.md\` with the reason and the planned final validation point.
348
-
349
- ### Outputs
350
-
351
- - Write \`.ai/vcm/handoffs/review-report.md\` with decision, evidence reviewed, tests added or updated, commands run or checked, validation results, failed expectations, reproduction steps, skipped checks with reasons, coverage gaps, and required follow-ups.
352
- - Record confirmed unresolved issues in \`.ai/vcm/handoffs/known-issues.md\` only when they should survive current-task cleanup.
353
-
354
- ### Background Jobs
355
-
356
- - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
357
- - For any command that may exceed 2 minutes, use the \`vcm-long-running-validation\` skill and stay in the turn, re-running \`.ai/tools/watch-job\` until it reports a terminal result.
358
- `
359
- },
360
- {
361
- path: ".github/pull_request_template.md",
362
- title: "Pull Request Template",
363
- commentStyle: "html",
364
- category: "pull-request-template",
365
- content: `## Summary
366
-
367
- ## Validation
368
-
369
- - Commands run or checked:
370
- - Result:
371
-
372
- ## Review
373
-
374
- - Reviewer decision:
375
- - Final acceptance:
376
-
377
- ## Docs
378
-
379
- - Durable docs updated or confirmed unchanged:
380
- - Known issues disposition:
381
-
382
- ## Risks
383
-
384
- ## Checklist
385
-
386
- - [ ] Final acceptance completed.
387
- - [ ] Reviewer validation completed.
388
- - [ ] Durable docs updated or confirmed unchanged.
389
- - [ ] Known issues resolved or recorded.
390
- - [ ] No uncommitted changes remain.
391
- `
392
- }
393
- ];
394
-
395
- const DURABLE_DOC_TEMPLATES = [
396
- {
397
- path: "docs/ARCHITECTURE.md",
398
- content: `# Architecture
399
- `
400
- },
401
- {
402
- path: "docs/TESTING.md",
403
- content: `# Testing
404
- `
405
- },
406
- {
407
- path: "docs/known-issues.md",
408
- content: `# Known Issues
409
- `
410
- }
411
- ];
412
-
413
- const WHOLE_FILES = [
414
- {
415
- path: ".ai/tools/generate-module-index",
416
- category: "generated-context-tool",
417
- mode: 0o755,
418
- templatePath: "scripts/harness-tools/generate-module-index"
419
- },
420
- {
421
- path: ".ai/tools/generate-public-surface",
422
- category: "generated-context-tool",
423
- mode: 0o755,
424
- templatePath: "scripts/harness-tools/generate-public-surface"
425
- },
426
- {
427
- path: ".claude/skills/vcm-final-acceptance/SKILL.md",
428
- category: "skill",
429
- mode: 0o644,
430
- content: `---
431
- name: vcm-final-acceptance
432
- description: Use when project-manager is ready to decide whether a VCM-managed task can be accepted, returned for follow-up, or blocked for a decision.
433
- ---
434
-
435
- # VCM Final Acceptance Skill
436
-
437
- ## Purpose
438
-
439
- Use this skill when project-manager is ready to decide whether a VCM-managed task can be accepted, returned for follow-up, or blocked for a decision.
440
-
441
- This skill is a final evidence audit. It does not replace architect docs sync, reviewer validation acceptance, coder implementation responsibility, or user approval for high-risk decisions.
442
-
443
- Project-manager must not use this skill to perform technical design review, implementation review, source-code analysis, or test adequacy analysis. Missing or conflicting evidence must be routed to the responsible role.
444
-
445
- ## Required Inputs
446
-
447
- Read the relevant task evidence before deciding:
448
-
449
- - original user request, PM route message, or durable plan when present
450
- - \`.ai/vcm/handoffs/architecture-plan.md\` when the task required architect planning
451
- - \`.ai/vcm/handoffs/review-report.md\` when reviewer validation was required
452
- - \`.ai/vcm/handoffs/docs-sync-report.md\` when durable docs could be affected
453
- - \`.ai/vcm/handoffs/known-issues.md\` when unresolved findings were recorded
454
- - current \`git status\` and changed file list
455
- - relevant long-term docs only when needed to confirm that a docs-sync artifact exists and names the correct durable docs
456
-
457
- ## Evidence Audit
458
-
459
- Check whether the required role evidence exists, is current, and gives a clear decision.
460
-
461
- Acceptable evidence must show:
462
-
463
- - architect plan or docs-sync decision when architecture, public contracts, durable docs, or known issues changed
464
- - reviewer decision and validation evidence when code, behavior, tests, or generated context changed
465
- - known-issues disposition when unresolved findings were recorded
466
- - explicit user approval for accepted high-risk decisions or intentionally skipped required gates
467
-
468
- ## File Scope Audit
469
-
470
- Do not claim to prove that every diff hunk exactly matches the task.
471
-
472
- Review the changed file list only, then classify files:
473
-
474
- - expected files: directly named by the user request, route message, durable plan, or architecture plan
475
- - supporting files: tests, fixtures, generated context, docs, or wiring needed for expected files
476
- - approved deviations: files explained by Replan, reviewer follow-up, docs-sync, or explicit user / project-manager approval
477
- - unexplained files: files with no traceable reason in the task evidence
478
- - high-risk unexpected files: auth, permissions, payment, billing, schema, migrations, data deletion, secrets, dependencies, lockfiles, broad generated artifacts, or broad formatting churn
479
-
480
- Unexplained files must be routed for explanation, follow-up, or removal before normal acceptance.
481
-
482
- High-risk unexpected files require explicit user approval or architect Replan before acceptance.
483
-
484
- ## Acceptance Checks
485
-
486
- Check:
487
-
488
- - required route was followed, or an explicit exception is recorded
489
- - required handoff artifacts exist and are current
490
- - architecture plan completion, Replan, or architect follow-up decision is recorded
491
- - reviewer report records validation commands, results, skipped checks with reasons, and an acceptable decision
492
- - docs-sync report records docs updated, docs intentionally left unchanged, or required follow-up
493
- - known issues are either resolved, promoted to durable docs by architect, or explicitly accepted
494
- - temporary task state is ready to clean after durable facts are promoted
495
-
496
- ## Decisions
497
-
498
- Choose exactly one:
499
-
500
- - accepted
501
- - accepted-with-known-risks
502
- - needs-coder-follow-up
503
- - needs-architect-replan
504
- - needs-docs-sync
505
- - blocked-by-user-decision
506
-
507
- Do not accept when required role evidence is missing, reviewer findings are unresolved, docs sync is missing for durable changes, known-issues disposition is missing, or unexplained high-risk files remain.
508
-
509
- ## Output
510
-
511
- Write or update:
512
-
513
- \`\`\`text
514
- .ai/vcm/handoffs/final-acceptance.md
515
- \`\`\`
516
-
517
- Use this structure:
518
-
519
- \`\`\`md
520
- # Final Acceptance: <task>
521
-
522
- ## Decision
523
-
524
- accepted | accepted-with-known-risks | needs-coder-follow-up | needs-architect-replan | needs-docs-sync | blocked-by-user-decision
525
-
526
- ## Evidence Reviewed
527
-
528
- ## File Scope
529
-
530
- ### Expected Files
531
-
532
- ### Supporting Files
533
-
534
- ### Approved Deviations
535
-
536
- ### Unexplained Files
537
-
538
- ### High-Risk Unexpected Files
539
-
540
- ## Validation Summary
541
-
542
- ## Review And Docs Sync
543
-
544
- ## Cleanup Readiness
545
-
546
- ## Final User Summary
547
- \`\`\`
548
-
549
- The final user summary should be concise and include files changed, validation, docs updates, open risks, and next action.
550
- `
551
- },
552
- {
553
- path: ".claude/skills/vcm-harness-bootstrap/SKILL.md",
554
- category: "skill",
555
- mode: 0o644,
556
- content: `---
557
- name: vcm-harness-bootstrap
558
- description: Use when VCM needs AI-assisted project understanding to finish or refresh project-specific harness content.
559
- ---
560
-
561
- # VCM Harness Bootstrap Skill
562
-
563
- ## Purpose
564
-
565
- Use this skill when VCM needs AI-assisted project understanding to finish or refresh project-specific harness content.
566
-
567
- This skill is an operating procedure. It does not replace the deterministic VCM installer.
568
-
569
- ## Boundaries
570
-
571
- - Read the repository before drafting project-specific harness content.
572
- - Do not edit product source, product tests, package manifests, lockfiles, deployment config, or secrets.
573
- - Do not own managed-block writes, hook merging, manifest migrations, uninstall behavior, or deterministic skeleton creation; VCM backend owns those.
574
- - Do not create new validation wrapper tools during bootstrap.
575
-
576
- ## Procedure
577
-
578
- 1. Generate context when supported: run \`.ai/tools/generate-module-index\`, then run \`.ai/tools/generate-public-surface\` after \`module-index.json\` exists.
579
- 2. Inspect the project: read \`README.md\`, read \`CLAUDE.md\`, durable project docs, project manifests/config, source layout, tests, and existing validation commands.
580
- 3. Fill project context: add or update non-managed project facts in \`CLAUDE.md\` above the VCM managed block.
581
- 4. Fill durable docs: update \`docs/ARCHITECTURE.md\`, module-level \`ARCHITECTURE.md\` files, and \`docs/TESTING.md\` with detailed project-specific content.
582
- 5. Preserve user-authored content and VCM managed blocks.
583
- 6. Report evidence, unknowns, confirmation-needed areas, generation failures, and recommended deterministic VCM actions.
584
-
585
- ## Typical Outputs
586
-
587
- - \`CLAUDE.md\` project context and project constraints outside the VCM managed block
588
- - \`docs/ARCHITECTURE.md\`
589
- - \`docs/TESTING.md\`
590
- - \`docs/known-issues.md\` only for confirmed durable issues
591
- - module-level \`ARCHITECTURE.md\` files
592
- - \`.ai/generated/module-index.json\`
593
- - \`.ai/generated/public-surface.json\`
594
-
595
- ## Output Requirements
596
-
597
- ### \`CLAUDE.md\`
598
-
599
- - Write only project-specific facts outside the VCM managed block.
600
- - Include project type, architecture shape, important constraints, and local conventions when verified or strongly inferred.
601
- - Do not edit, duplicate, or summarize the VCM managed block.
602
-
603
- ### Generated Context
604
-
605
- - Run \`.ai/tools/generate-module-index\` when the generator exists and the project is supported.
606
- - Run \`.ai/tools/generate-public-surface\` only after \`.ai/generated/module-index.json\` exists.
607
- - If generation fails or the project is unsupported, report the reason. Do not invent generated artifacts.
608
-
609
- ### \`docs/ARCHITECTURE.md\`
610
-
611
- - Document the project-level module overview, module responsibilities, module relationships, dependency direction, and project-wide constraints.
612
- - Link to module-level \`ARCHITECTURE.md\` files when present.
613
- - Explain generated-context ownership, especially that \`.ai/generated/public-surface.json\` is the machine index for crate-external public APIs.
614
-
615
- ### Module-Level \`ARCHITECTURE.md\`
616
-
617
- - Create or update one module-level \`ARCHITECTURE.md\` for each clear module boundary.
618
- - Document module boundaries, responsibilities, allowed dependencies, important behavior, important public surface explanations, risks, and update triggers.
619
- - Keep complete public API listings in \`.ai/generated/public-surface.json\`; module docs should explain meaning and design intent, not duplicate the full generated index.
620
-
621
- ### \`docs/TESTING.md\`
622
-
623
- - Document validation levels, project-native validation commands, unit/integration test placement, generated-context freshness checks, and known testing gaps.
624
- - Keep reviewer ownership of validation strategy and testing documentation clear.
625
-
626
- ## Final Summary
627
-
628
- Include:
629
-
630
- - files reviewed
631
- - files drafted or updated
632
- - verified claims
633
- - inferred claims
634
- - unknowns
635
- - needs human confirmation
636
- - suggested validation commands
637
- - recommended next harness steps
638
- `
639
- },
640
- {
641
- path: ".claude/skills/vcm-long-running-validation/SKILL.md",
642
- category: "skill",
643
- mode: 0o644,
644
- content: `---
645
- name: vcm-long-running-validation
646
- description: Use for builds, browser checks, E2E tests, release suites, or any validation command that may take long enough for shell-completion callbacks to become unreliable.
647
- ---
648
-
649
- # VCM Long-Running Validation Skill
650
-
651
- Use this skill for builds, browser checks, E2E tests, release suites, or any command that may run longer than 2 minutes.
652
-
653
- ## Rule
654
-
655
- Never run the Bash tool with \`run_in_background: true\`, and never detach a process with \`nohup\`, \`setsid\`, \`disown\`, or a trailing \`&\`. VCM denies these calls.
656
-
657
- The only sanctioned long-running mechanism is \`.ai/tools/run-long-check\` plus \`.ai/tools/watch-job\` through this skill.
658
-
659
- The hard ceiling is 60 minutes per job, enforced by the job worker itself. Do not run or suggest operations expected to exceed 60 minutes without user approval; split larger work first.
660
-
661
- ## Protocol
662
-
663
- 1. Start the command with an explicit ceiling: \`.ai/tools/run-long-check --timeout <duration> -- <command>\`. Pick the ceiling from \`docs/TESTING.md\` guidance or a realistic estimate, never above 60m. The tool prints the job id and creates job state under \`.ai/vcm/jobs/<job-id>/\`.
664
- 2. In the same turn, run \`.ai/tools/watch-job <job-id>\`. The default watch window is 8 minutes.
665
- 3. If watch-job exits 125, the job is still running: run \`.ai/tools/watch-job <job-id>\` again immediately. Do not end the turn between windows.
666
- 4. Repeat until watch-job reports a terminal result.
667
- 5. Read the final status and the relevant log tail.
668
- 6. Record command, result, duration, and required follow-up wherever the caller normally records command evidence.
669
-
670
- Example:
671
-
672
- \`\`\`bash
673
- .ai/tools/run-long-check --timeout 30m -- cargo test --workspace
674
- .ai/tools/watch-job <job-id>
675
- .ai/tools/watch-job <job-id> # repeat while the exit code is 125
676
- \`\`\`
677
-
678
- ## Exit Codes
679
-
680
- Treat watch-job exit codes as explicit results:
681
-
682
- - \`0\`: success.
683
- - \`1\`: failed; read the log tails.
684
- - \`124\`: timeout; the job hit its ceiling and was killed; not passed.
685
- - \`125\`: still running; call watch-job again now.
686
- - \`4\`: orphaned or stale; the job lost foreground supervision and was killed, or its worker died; not passed.
687
- - \`2\`: usage error or unknown job id.
688
-
689
- ## Supervision
690
-
691
- A running job requires a live foreground watcher:
692
-
693
- - watch-job renews the job supervision lease while it runs.
694
- - If no watcher renews the lease for about 2 minutes, the worker kills the command process group and records \`orphaned\`. A job cannot keep running unsupervised.
695
- - VCM also blocks ending the turn while a job is running. Stay in the turn and keep watching.
696
- - Only one validation job may run at a time; run-long-check refuses to start a second one.
697
-
698
- ## Job Files
699
-
700
- \`\`\`text
701
- .ai/vcm/jobs/<job-id>/command.json
702
- .ai/vcm/jobs/<job-id>/status.json
703
- .ai/vcm/jobs/<job-id>/stdout.log
704
- .ai/vcm/jobs/<job-id>/stderr.log
705
- .ai/vcm/jobs/<job-id>/lease
706
- \`\`\`
707
-
708
- ## Timeout
709
-
710
- Timeout is not "unknown". It is a command result.
711
-
712
- On timeout the worker stops the command process group and records \`timeout\` in \`status.json\`; watch-job reports it with exit code 124:
713
-
714
- - summarize the latest log tail
715
- - report whether the timed-out process was stopped
716
- - do not mark the command as passed
717
- - do not retry in the background
718
-
719
- ## Cleanup
720
-
721
- \`.ai/vcm/jobs/**\` is runtime state. Delete it after the command result and useful log evidence have been recorded where needed.
722
- `
723
- },
724
- {
725
- path: ".claude/skills/vcm-route-message/SKILL.md",
726
- category: "skill",
727
- mode: 0o644,
728
- content: `---
729
- name: vcm-route-message
730
- description: Use when a VCM role needs to hand off work, ask a question, report a result, report a blocker, or raise a finding to another VCM role.
731
- ---
732
-
733
- # VCM Route Message Skill
734
-
735
- ## Purpose
736
-
737
- Use this skill when a VCM role needs to hand work, ask a question, report a result, report a blocker, or raise a finding to another VCM role.
738
-
739
- This skill writes a route file. It does not deliver the message. VCM backend delivery is triggered later by Claude Code hooks.
740
-
741
- ## Route Policy
742
-
743
- Use only routes allowed by the current VCM role rules and task approval.
744
-
745
- Allowed message types:
746
-
747
- - task
748
- - question
749
- - revise
750
- - cancel
751
- - result
752
- - blocked
753
- - finding
754
-
755
- ## Route File
756
-
757
- Write or update exactly one file:
758
-
759
- \`\`\`text
760
- .ai/vcm/handoffs/messages/<from-role>-<to-role>.md
761
- \`\`\`
762
-
763
- The file name is authoritative. Do not put from/to in frontmatter and do not create alternate message paths.
764
-
765
- If the same route file already contains a not-yet-delivered message, update that file instead of creating a fragmented follow-up.
766
-
767
- ## Message Format
768
-
769
- \`\`\`md
770
- ---
771
- type: task
772
- artifact_refs:
773
- - .ai/vcm/handoffs/architecture-plan.md
774
- - docs/plans/example.md
775
- ---
776
-
777
- Summary:
778
- ...
779
-
780
- Request or result:
781
- ...
782
-
783
- Evidence:
784
- ...
785
-
786
- Expected next action:
787
- ...
788
- \`\`\`
789
-
790
- Use the smallest body that is complete. Include artifact refs instead of copying long documents.
791
-
792
- ## Required Body Content
793
-
794
- - why this message exists
795
- - what the target role should do or what result is being reported
796
- - source of truth or artifact references
797
- - validation or documentation state when relevant
798
- - blocker, decision needed, or next step when relevant
799
-
800
- ## Turn Rule
801
-
802
- After writing or updating the route file, end the current Claude Code turn immediately.
803
-
804
- Do not:
805
-
806
- - send another message to the same target role in the same turn
807
- - poll route files
808
- - start a shell loop
809
- - wait for another role's answer
810
- - paste directly into another role terminal
811
- - use Claude Code Task/Subagent for VCM role delegation
812
-
813
- VCM scans pending route files after the Stop hook and delivers later replies in a new turn.
814
-
815
- ## Recovery
816
-
817
- If delivery is manual, blocked, or the target role is busy, leave the route file non-empty. Do not clear it yourself unless the user or VCM controller has explicitly confirmed manual handling.
818
- `
819
- },
820
- {
821
- path: ".ai/tools/run-long-check",
822
- category: "runtime-tool",
823
- mode: 0o755,
824
- templatePath: "scripts/harness-tools/run-long-check"
825
- },
826
- {
827
- path: ".ai/tools/watch-job",
828
- category: "runtime-tool",
829
- mode: 0o755,
830
- templatePath: "scripts/harness-tools/watch-job"
831
- },
832
- {
833
- path: ".ai/tools/vcm-bash-guard",
834
- category: "runtime-tool",
835
- mode: 0o755,
836
- templatePath: "scripts/harness-tools/vcm-bash-guard"
837
- }
838
- ];
839
-
840
- const LEGACY_FLAT_SKILL_FILES = [
841
- {
842
- path: ".claude/skills/vcm-final-acceptance.md",
843
- replacementPath: ".claude/skills/vcm-final-acceptance/SKILL.md"
844
- },
845
- {
846
- path: ".claude/skills/vcm-harness-bootstrap.md",
847
- replacementPath: ".claude/skills/vcm-harness-bootstrap/SKILL.md"
848
- },
849
- {
850
- path: ".claude/skills/vcm-long-running-validation.md",
851
- replacementPath: ".claude/skills/vcm-long-running-validation/SKILL.md"
852
- },
853
- {
854
- path: ".claude/skills/vcm-route-message.md",
855
- replacementPath: ".claude/skills/vcm-route-message/SKILL.md"
856
- }
857
- ];
858
-
859
- async function main() {
860
- const args = parseArgs(process.argv.slice(2));
861
- if (args.help) {
862
- printUsage();
863
- return;
864
- }
865
- if (!args.projectRoot) {
866
- fail("Missing project root.");
867
- }
868
-
869
- const projectRoot = path.resolve(args.projectRoot);
870
- const dryRun = args.dryRun;
871
- const operations = [];
872
-
873
- await assertDirectory(projectRoot, "Project root");
874
-
875
- const manifest = await buildManifest(projectRoot);
876
- await installManifest({ projectRoot, manifest, dryRun, operations });
877
- for (const definition of MANAGED_FILES) {
878
- await installManagedFile({ projectRoot, definition, dryRun, operations });
879
- }
880
- for (const template of DURABLE_DOC_TEMPLATES) {
881
- await installDurableDocTemplate({ projectRoot, template, dryRun, operations });
882
- }
883
- await installClaudeSettings({ projectRoot, dryRun, operations });
884
- for (const directory of fixedDirectories()) {
885
- await ensureDirectory({ projectRoot, relativePath: directory, dryRun, operations });
886
- }
887
- for (const file of WHOLE_FILES) {
888
- await installWholeFile({ projectRoot, file, dryRun, operations });
889
- }
890
- await removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations });
891
-
892
- printReport({ projectRoot, dryRun, operations });
893
- }
894
-
895
- function parseArgs(argv) {
896
- const args = {
897
- dryRun: false,
898
- help: false,
899
- projectRoot: undefined
900
- };
901
-
902
- for (let index = 0; index < argv.length; index += 1) {
903
- const arg = argv[index];
904
- if (arg === "--help" || arg === "-h") {
905
- args.help = true;
906
- continue;
907
- }
908
- if (arg === "--dry-run") {
909
- args.dryRun = true;
910
- continue;
911
- }
912
- if (arg.startsWith("--")) {
913
- fail(`Unknown option: ${arg}`);
914
- }
915
- if (args.projectRoot) {
916
- fail(`Unexpected argument: ${arg}`);
917
- }
918
- args.projectRoot = arg;
919
- }
920
-
921
- return args;
922
- }
923
-
924
- function printUsage() {
925
- console.log(`Usage:
926
- node scripts/install-vcm-harness.mjs <project-root>
927
- node scripts/install-vcm-harness.mjs <project-root> --dry-run
928
-
929
- Installs only fixed VCM harness content.
930
-
931
- This deterministic installer handles VCM-owned managed blocks, VCM-owned whole
932
- files, VCM Claude settings hooks, generic long-running helper tools, and the
933
- harness manifest. It also creates blank durable project doc templates when
934
- missing and installs Rust generated-context generator tools. It does not copy
935
- example project docs, generated context artifacts, module-level architecture
936
- docs, or task runtime handoff artifacts.`);
937
- }
938
-
939
- async function buildManifest(projectRoot) {
940
- const current = await readOptionalJson(path.join(projectRoot, MANIFEST_PATH));
941
- const now = new Date().toISOString();
942
- return {
943
- schemaVersion: 1,
944
- manager: "vcm",
945
- harnessVersion: HARNESS_VERSION,
946
- installMode: "fixed",
947
- installedAt: typeof current?.installedAt === "string" ? current.installedAt : now,
948
- updatedAt: now,
949
- runtimeRoots: [
950
- ".ai/vcm/",
951
- ".claude/worktrees/"
952
- ],
953
- entries: [
954
- manifestEntry(MANIFEST_PATH, "file", "harness-manifest", "whole-file"),
955
- ...MANAGED_FILES.map((file) => managedEntry(file)),
956
- {
957
- path: ".claude/settings.json",
958
- entryType: "file",
959
- category: "claude-settings",
960
- ownership: "json-merge",
961
- source: "vcm-template",
962
- lifecycle: "long-term",
963
- jsonOwnership: {
964
- topLevelKeys: ["hooks", "env"],
965
- hookMatchers: ["VCM"],
966
- envKeys: ["BASH_DEFAULT_TIMEOUT_MS"]
967
- },
968
- uninstall: {
969
- action: "remove-owned-json-keys",
970
- requiresConfirmation: false
971
- }
972
- },
973
- ...fixedDirectories().map((directory) => manifestEntry(directory, "directory", directoryCategory(directory), "vcm-created")),
974
- ...WHOLE_FILES.map((file) => ({
975
- path: file.path,
976
- entryType: "file",
977
- category: file.category,
978
- ownership: "whole-file",
979
- source: "vcm-template",
980
- lifecycle: file.category === "runtime-tool" ? "conditional-long-term" : "long-term",
981
- uninstall: {
982
- action: "delete-file-if-unchanged",
983
- requiresConfirmation: false
984
- }
985
- })),
986
- derivedGeneratedContextEntry(".ai/generated/module-index.json", ".ai/tools/generate-module-index"),
987
- derivedGeneratedContextEntry(".ai/generated/public-surface.json", ".ai/tools/generate-public-surface")
988
- ]
989
- };
990
- }
991
-
992
- function derivedGeneratedContextEntry(pathName, source) {
993
- return {
994
- path: pathName,
995
- entryType: "file",
996
- category: "generated-context",
997
- ownership: "derived-artifact",
998
- source,
999
- lifecycle: "derived",
1000
- uninstall: {
1001
- action: "delete-derived-artifact",
1002
- requiresConfirmation: false
1003
- }
1004
- };
1005
- }
1006
-
1007
- function manifestEntry(pathName, entryType, category, ownership) {
1008
- return {
1009
- path: pathName,
1010
- entryType,
1011
- category,
1012
- ownership,
1013
- source: "vcm-template",
1014
- lifecycle: "long-term"
1015
- };
1016
- }
1017
-
1018
- function managedEntry(file) {
1019
- return {
1020
- path: file.path,
1021
- entryType: "file",
1022
- category: file.category,
1023
- ownership: "managed-block",
1024
- source: "vcm-template",
1025
- lifecycle: "long-term",
1026
- marker: {
1027
- type: file.commentStyle === "hash" ? "hash-comment" : "html",
1028
- begin: file.commentStyle === "hash" ? "# VCM:BEGIN version=1" : "<!-- VCM:BEGIN version=1 -->",
1029
- end: file.commentStyle === "hash" ? "# VCM:END" : "<!-- VCM:END -->"
1030
- },
1031
- uninstall: {
1032
- action: "remove-managed-block",
1033
- requiresConfirmation: false
1034
- }
1035
- };
1036
- }
1037
-
1038
- function fixedDirectories() {
1039
- return [
1040
- ".claude/agents/",
1041
- ".claude/skills/",
1042
- ".claude/skills/vcm-final-acceptance/",
1043
- ".claude/skills/vcm-harness-bootstrap/",
1044
- ".claude/skills/vcm-long-running-validation/",
1045
- ".claude/skills/vcm-route-message/",
1046
- ".ai/tools/",
1047
- ".ai/generated/"
1048
- ];
1049
- }
1050
-
1051
- function directoryCategory(directory) {
1052
- if (directory === ".claude/agents/") {
1053
- return "agent-directory";
1054
- }
1055
- if (directory === ".claude/skills/") {
1056
- return "skill-directory";
1057
- }
1058
- if (directory.startsWith(".claude/skills/")) {
1059
- return "skill-directory";
1060
- }
1061
- if (directory === ".ai/generated/") {
1062
- return "generated-context-directory";
1063
- }
1064
- return "harness-tool-directory";
1065
- }
1066
-
1067
- async function installManifest({ projectRoot, manifest, dryRun, operations }) {
1068
- const targetPath = path.join(projectRoot, MANIFEST_PATH);
1069
- const currentManifest = await readOptionalJson(targetPath);
1070
-
1071
- if (currentManifest && manifestBodyEqual(currentManifest, manifest)) {
1072
- operations.push(skip(MANIFEST_PATH, "unchanged"));
1073
- return;
1074
- }
1075
-
1076
- await writeIfChanged({
1077
- targetPath,
1078
- relativePath: MANIFEST_PATH,
1079
- content: `${JSON.stringify(manifest, null, 2)}\n`,
1080
- mode: 0o644,
1081
- dryRun,
1082
- operations,
1083
- action: "write fixed harness manifest"
1084
- });
1085
- }
1086
-
1087
- async function installManagedFile({ projectRoot, definition, dryRun, operations }) {
1088
- const targetPath = resolveInside(projectRoot, definition.path);
1089
- const block = renderManagedBlock(definition);
1090
- const currentContent = await readOptionalText(targetPath);
1091
- let nextContent;
1092
-
1093
- if (currentContent === undefined || currentContent.trim() === "") {
1094
- nextContent = renderNewManagedFile(definition, block);
1095
- } else {
1096
- const pattern = definition.commentStyle === "hash" ? HASH_BLOCK_PATTERN : HTML_BLOCK_PATTERN;
1097
- nextContent = pattern.test(currentContent)
1098
- ? currentContent.replace(pattern, block)
1099
- : `${currentContent.trimEnd()}\n\n${block}\n`;
1100
- }
1101
-
1102
- await writeIfChanged({
1103
- targetPath,
1104
- relativePath: definition.path,
1105
- content: ensureTrailingNewline(nextContent),
1106
- mode: 0o644,
1107
- dryRun,
1108
- operations,
1109
- action: "install fixed managed block"
1110
- });
1111
- }
1112
-
1113
- async function installDurableDocTemplate({ projectRoot, template, dryRun, operations }) {
1114
- const targetPath = resolveInside(projectRoot, template.path);
1115
- const currentContent = await readOptionalText(targetPath);
1116
- if (currentContent !== undefined) {
1117
- operations.push(skip(template.path, "exists"));
1118
- return;
1119
- }
1120
-
1121
- await writeIfChanged({
1122
- targetPath,
1123
- relativePath: template.path,
1124
- content: ensureTrailingNewline(template.content),
1125
- mode: 0o644,
1126
- dryRun,
1127
- operations,
1128
- action: "create durable doc template"
1129
- });
1130
- }
1131
-
1132
- function renderManagedBlock(definition) {
1133
- const body = definition.content.trimEnd();
1134
- const endSpacing = definition.blankLineBeforeEnd ? "\n\n" : "\n";
1135
- if (definition.commentStyle === "hash") {
1136
- return `# VCM:BEGIN version=1\n${body}${endSpacing}# VCM:END`;
1137
- }
1138
- return `<!-- VCM:BEGIN version=1 -->\n${body}${endSpacing}<!-- VCM:END -->`;
1139
- }
1140
-
1141
- function renderNewManagedFile(definition, block) {
1142
- if (definition.agentName) {
1143
- const frontmatter = AGENT_FRONTMATTER[definition.agentName];
1144
- return `---\nname: ${definition.agentName}\ndescription: ${frontmatter.description}\ntools: Read, Grep, Glob, Bash, Edit, Write\n---\n\n# ${definition.title}\n\n${block}\n`;
1145
- }
1146
- return `# ${definition.title}\n\n${block}\n`;
1147
- }
1148
-
1149
- async function installClaudeSettings({ projectRoot, dryRun, operations }) {
1150
- const targetPath = path.join(projectRoot, ".claude/settings.json");
1151
- const current = await readOptionalJson(targetPath) ?? {};
1152
- if (!isPlainObject(current)) {
1153
- fail(`Target JSON is not an object: ${targetPath}`);
1154
- }
1155
-
1156
- const next = mergeVcmHooks(current);
1157
- await writeIfChanged({
1158
- targetPath,
1159
- relativePath: ".claude/settings.json",
1160
- content: `${JSON.stringify(next, null, 2)}\n`,
1161
- mode: 0o644,
1162
- dryRun,
1163
- operations,
1164
- action: "merge VCM Claude hooks"
1165
- });
1166
- }
1167
-
1168
- function mergeVcmHooks(settings) {
1169
- const next = structuredClone(settings);
1170
- const hooks = isPlainObject(next.hooks) ? { ...next.hooks } : {};
1171
-
1172
- for (const [eventName, eventMatchers] of Object.entries(hooks)) {
1173
- if (!Array.isArray(eventMatchers)) {
1174
- continue;
1175
- }
1176
- const remaining = eventMatchers.filter((matcher) => !isOwnedHookMatcher(matcher));
1177
- if (remaining.length > 0) {
1178
- hooks[eventName] = remaining;
1179
- } else {
1180
- delete hooks[eventName];
1181
- }
1182
- }
1183
-
1184
- for (const definition of VCM_HOOK_DEFINITIONS) {
1185
- hooks[definition.eventName] = [
1186
- ...(Array.isArray(hooks[definition.eventName]) ? hooks[definition.eventName] : []),
1187
- {
1188
- ...(definition.matcher ? { matcher: definition.matcher } : {}),
1189
- hooks: [
1190
- {
1191
- type: "command",
1192
- command: definition.command,
1193
- timeout: definition.timeout
1194
- }
1195
- ]
1196
- }
1197
- ];
1198
- }
1199
-
1200
- next.hooks = hooks;
1201
-
1202
- const env = isPlainObject(next.env) ? { ...next.env } : {};
1203
- env.BASH_DEFAULT_TIMEOUT_MS = VCM_BASH_DEFAULT_TIMEOUT_MS;
1204
- next.env = env;
1205
-
1206
- return next;
1207
- }
1208
-
1209
- function isOwnedHookMatcher(matcher) {
1210
- if (!isPlainObject(matcher) || !Array.isArray(matcher.hooks)) {
1211
- return false;
1212
- }
1213
- return matcher.hooks.some((hook) => {
1214
- if (!isPlainObject(hook)) {
1215
- return false;
1216
- }
1217
- const command = typeof hook.command === "string" ? hook.command : "";
1218
- return command.includes("VCM") ||
1219
- command.includes("/api/hooks/claude-code") ||
1220
- command.includes("hook-event");
1221
- });
1222
- }
1223
-
1224
- async function ensureDirectory({ projectRoot, relativePath, dryRun, operations }) {
1225
- const targetPath = resolveInside(projectRoot, relativePath);
1226
- if (await pathExists(targetPath)) {
1227
- operations.push(skip(relativePath, "exists"));
1228
- return;
1229
- }
1230
- if (dryRun) {
1231
- operations.push(plan(relativePath, "create fixed directory"));
1232
- return;
1233
- }
1234
- await fs.mkdir(targetPath, { recursive: true });
1235
- operations.push(done(relativePath, "created fixed directory"));
1236
- }
1237
-
1238
- async function installWholeFile({ projectRoot, file, dryRun, operations }) {
1239
- const content = await wholeFileContent(file);
1240
- await writeIfChanged({
1241
- targetPath: resolveInside(projectRoot, file.path),
1242
- relativePath: file.path,
1243
- content: ensureTrailingNewline(content),
1244
- mode: file.mode,
1245
- dryRun,
1246
- operations,
1247
- action: "write fixed VCM file"
1248
- });
1249
- }
1250
-
1251
- async function removeLegacyFlatSkillFiles({ projectRoot, dryRun, operations }) {
1252
- const wholeFilesByPath = new Map(WHOLE_FILES.map((file) => [file.path, file]));
1253
- for (const legacy of LEGACY_FLAT_SKILL_FILES) {
1254
- const targetPath = resolveInside(projectRoot, legacy.path);
1255
- const currentContent = await readOptionalText(targetPath);
1256
- if (currentContent === undefined) {
1257
- continue;
1258
- }
1259
-
1260
- const replacement = wholeFilesByPath.get(legacy.replacementPath);
1261
- if (!replacement) {
1262
- operations.push(skip(legacy.path, "missing replacement skill definition"));
1263
- continue;
1264
- }
1265
-
1266
- const replacementContent = ensureTrailingNewline(await wholeFileContent(replacement));
1267
- const legacyExpectedContent = ensureTrailingNewline(stripSkillFrontmatter(replacementContent));
1268
- if (currentContent !== replacementContent && currentContent !== legacyExpectedContent) {
1269
- operations.push(skip(legacy.path, "legacy flat skill file differs; left in place"));
1270
- continue;
1271
- }
1272
-
1273
- if (dryRun) {
1274
- operations.push(plan(legacy.path, "delete legacy flat skill file"));
1275
- continue;
1276
- }
1277
-
1278
- await fs.rm(targetPath, { force: true });
1279
- operations.push(done(legacy.path, "deleted legacy flat skill file"));
1280
- }
1281
- }
1282
-
1283
- function stripSkillFrontmatter(content) {
1284
- return content.replace(/^---\n[\s\S]*?\n---\n\n/, "");
1285
- }
1286
-
1287
- async function wholeFileContent(file) {
1288
- if (typeof file.content === "string") {
1289
- return file.content;
1290
- }
1291
- if (typeof file.templatePath === "string") {
1292
- const templateAbsolutePath = path.join(SCRIPT_DIR, "..", file.templatePath);
1293
- const content = await readOptionalText(templateAbsolutePath);
1294
- if (content === undefined) {
1295
- fail(`Missing bundled harness template: ${file.templatePath}`);
1296
- }
1297
- return content;
1298
- }
1299
- fail(`Whole file entry has no content: ${file.path}`);
1300
- }
1301
-
1302
- async function writeIfChanged({ targetPath, relativePath, content, mode, dryRun, operations, action }) {
1303
- const currentContent = await readOptionalText(targetPath);
1304
- if (currentContent === content) {
1305
- operations.push(skip(relativePath, "unchanged"));
1306
- return;
1307
- }
1308
-
1309
- if (dryRun) {
1310
- operations.push(plan(relativePath, action));
1311
- return;
1312
- }
1313
-
1314
- await fs.mkdir(path.dirname(targetPath), { recursive: true });
1315
- await fs.writeFile(targetPath, content, "utf8");
1316
- if (mode !== undefined) {
1317
- await fs.chmod(targetPath, mode);
1318
- }
1319
- operations.push(done(relativePath, action));
10
+ const APP_ROOT = path.resolve(SCRIPT_DIR, "..");
11
+ const DIST_CLI = path.join(APP_ROOT, "dist/backend/cli/install-vcm-harness.js");
12
+ const SOURCE_CLI = path.join(APP_ROOT, "src/backend/cli/install-vcm-harness.ts");
13
+ const SOURCE_TEMPLATE_DIR = path.join(APP_ROOT, "src/backend/templates/harness");
14
+ const TSX_BIN = path.join(APP_ROOT, "node_modules/.bin", process.platform === "win32" ? "tsx.cmd" : "tsx");
15
+
16
+ const argv = process.argv.slice(2);
17
+ let command = process.execPath;
18
+ let args = [DIST_CLI, ...argv];
19
+
20
+ const canRunSource = fs.existsSync(SOURCE_CLI) && fs.existsSync(TSX_BIN);
21
+ const hasDist = fs.existsSync(DIST_CLI);
22
+ const sourceIsNewer = canRunSource && hasDist && latestSourceMtime() > fs.statSync(DIST_CLI).mtimeMs;
23
+
24
+ if (canRunSource && (!hasDist || sourceIsNewer)) {
25
+ command = TSX_BIN;
26
+ args = [SOURCE_CLI, ...argv];
27
+ } else if (!hasDist) {
28
+ console.error("VCM fixed harness install failed: compiled CLI not found. Run npm run build first.");
29
+ process.exit(1);
1320
30
  }
1321
31
 
1322
- async function readOptionalJson(absolutePath) {
1323
- const content = await readOptionalText(absolutePath);
1324
- if (content === undefined || content.trim() === "") {
1325
- return undefined;
1326
- }
1327
- try {
1328
- return JSON.parse(content);
1329
- } catch (error) {
1330
- fail(`Invalid JSON file: ${absolutePath}\n${error.message}`);
1331
- }
1332
- }
32
+ const result = spawnSync(command, args, {
33
+ stdio: "inherit",
34
+ env: process.env
35
+ });
1333
36
 
1334
- async function readOptionalText(absolutePath) {
1335
- return fs.readFile(absolutePath, "utf8").catch((error) => {
1336
- if (error.code === "ENOENT") {
1337
- return undefined;
1338
- }
1339
- throw error;
1340
- });
37
+ if (result.error) {
38
+ console.error(`VCM fixed harness install failed: ${result.error.message}`);
39
+ process.exit(1);
1341
40
  }
1342
41
 
1343
- async function assertDirectory(absolutePath, label) {
1344
- const stat = await fs.stat(absolutePath).catch((error) => {
1345
- if (error.code === "ENOENT") {
1346
- fail(`${label} not found: ${absolutePath}`);
1347
- }
1348
- throw error;
1349
- });
1350
- if (!stat.isDirectory()) {
1351
- fail(`${label} is not a directory: ${absolutePath}`);
1352
- }
1353
- }
42
+ process.exit(result.status ?? 1);
1354
43
 
1355
- async function pathExists(absolutePath) {
1356
- return fs.stat(absolutePath).then(
1357
- () => true,
1358
- (error) => {
1359
- if (error.code === "ENOENT") {
1360
- return false;
44
+ function latestSourceMtime() {
45
+ const sourceFiles = [SOURCE_CLI];
46
+ if (fs.existsSync(SOURCE_TEMPLATE_DIR)) {
47
+ for (const entry of fs.readdirSync(SOURCE_TEMPLATE_DIR)) {
48
+ if (entry.endsWith(".ts")) {
49
+ sourceFiles.push(path.join(SOURCE_TEMPLATE_DIR, entry));
1361
50
  }
1362
- throw error;
1363
51
  }
1364
- );
1365
- }
1366
-
1367
- function manifestBodyEqual(left, right) {
1368
- const normalizedLeft = { ...left };
1369
- const normalizedRight = { ...right };
1370
- delete normalizedLeft.installedAt;
1371
- delete normalizedLeft.updatedAt;
1372
- delete normalizedRight.installedAt;
1373
- delete normalizedRight.updatedAt;
1374
- return JSON.stringify(normalizedLeft) === JSON.stringify(normalizedRight);
1375
- }
1376
-
1377
- function resolveInside(root, relativePath) {
1378
- if (path.isAbsolute(relativePath)) {
1379
- fail(`Path must be relative: ${relativePath}`);
1380
- }
1381
- const normalized = path.normalize(relativePath);
1382
- if (normalized === ".." || normalized.startsWith(`..${path.sep}`)) {
1383
- fail(`Path escapes root: ${relativePath}`);
1384
- }
1385
- const resolved = path.resolve(root, normalized);
1386
- if (!isInside(root, resolved) && resolved !== root) {
1387
- fail(`Path escapes root: ${relativePath}`);
1388
52
  }
1389
- return resolved;
53
+ return Math.max(...sourceFiles.map((file) => fs.statSync(file).mtimeMs));
1390
54
  }
1391
-
1392
- function isInside(root, candidate) {
1393
- const relative = path.relative(root, candidate);
1394
- return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
1395
- }
1396
-
1397
- function ensureTrailingNewline(value) {
1398
- return value.endsWith("\n") ? value : `${value}\n`;
1399
- }
1400
-
1401
- function plan(pathName, action) {
1402
- return { status: "plan", path: pathName, action };
1403
- }
1404
-
1405
- function done(pathName, action) {
1406
- return { status: "done", path: pathName, action };
1407
- }
1408
-
1409
- function skip(pathName, reason) {
1410
- return { status: "skip", path: pathName, action: reason };
1411
- }
1412
-
1413
- function printReport({ projectRoot, dryRun, operations }) {
1414
- console.log(`${dryRun ? "Dry-run" : "Applied"} VCM fixed harness install`);
1415
- console.log(`Project: ${projectRoot}`);
1416
-
1417
- for (const operation of operations) {
1418
- console.log(`${operation.status.toUpperCase()} ${operation.path} - ${operation.action}`);
1419
- }
1420
-
1421
- if (dryRun) {
1422
- console.log("No files changed. Re-run without --dry-run to apply.");
1423
- }
1424
- }
1425
-
1426
- function isPlainObject(value) {
1427
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1428
- }
1429
-
1430
- function fail(message) {
1431
- console.error(`VCM fixed harness install failed: ${message}`);
1432
- process.exit(1);
1433
- }
1434
-
1435
- await main();