vibe-coding-master 0.2.7 → 0.2.9

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.
Files changed (34) hide show
  1. package/README.md +47 -13
  2. package/dist/backend/api/claude-hook-routes.js +7 -1
  3. package/dist/backend/gateway/gateway-command-parser.js +4 -0
  4. package/dist/backend/gateway/gateway-service.js +263 -32
  5. package/dist/backend/gateway/gateway-settings-service.js +34 -0
  6. package/dist/backend/server.js +3 -1
  7. package/dist/backend/services/claude-hook-service.js +33 -3
  8. package/dist/backend/services/harness-service.js +37 -32
  9. package/dist/backend/services/job-guard-service.js +126 -0
  10. package/dist/backend/services/message-service.js +1 -1
  11. package/dist/backend/templates/harness/architect-agent.js +19 -8
  12. package/dist/backend/templates/harness/claude-root.js +7 -11
  13. package/dist/backend/templates/harness/coder-agent.js +45 -17
  14. package/dist/backend/templates/harness/gitignore.js +3 -2
  15. package/dist/backend/templates/harness/project-manager-agent.js +16 -11
  16. package/dist/backend/templates/harness/reviewer-agent.js +25 -28
  17. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +42 -31
  18. package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +37 -18
  19. package/dist-frontend/assets/index-DC9-SB7F.css +32 -0
  20. package/dist-frontend/assets/{index-DHuS-DYr.js → index-DDrcJLOG.js} +37 -37
  21. package/dist-frontend/index.html +2 -2
  22. package/docs/full-harness-baseline.md +7 -5
  23. package/docs/gateway-design.md +40 -6
  24. package/docs/product-design.md +11 -2
  25. package/docs/vcm-cc-best-practices.md +11 -4
  26. package/package.json +1 -1
  27. package/scripts/harness-tools/run-long-check +401 -0
  28. package/scripts/harness-tools/vcm-bash-guard +107 -0
  29. package/scripts/harness-tools/watch-job +204 -0
  30. package/scripts/install-vcm-harness.mjs +93 -387
  31. package/scripts/uninstall-vcm-harness.mjs +18 -1
  32. package/scripts/verify-package.mjs +3 -0
  33. package/dist/backend/templates/harness/known-issues-doc.js +0 -22
  34. package/dist-frontend/assets/index-7lq6YPCq.css +0 -32
@@ -19,6 +19,7 @@ import { createWeixinIlinkChannel } from "./gateway/channels/weixin-ilink-channe
19
19
  import { createGatewayAuditLog } from "./gateway/gateway-audit-log.js";
20
20
  import { createGatewayService } from "./gateway/gateway-service.js";
21
21
  import { createGatewaySettingsService } from "./gateway/gateway-settings-service.js";
22
+ import { createJobGuardService } from "./services/job-guard-service.js";
22
23
  import { createProjectService } from "./services/project-service.js";
23
24
  import { createSessionRegistry } from "./runtime/session-registry.js";
24
25
  import { createSessionService } from "./services/session-service.js";
@@ -218,7 +219,8 @@ export function createDefaultServerDeps(options = {}) {
218
219
  roundService,
219
220
  translationService,
220
221
  appSettings,
221
- gatewayService
222
+ gatewayService,
223
+ jobGuard: createJobGuardService()
222
224
  });
223
225
  return {
224
226
  appSettings,
@@ -34,6 +34,11 @@ export function createClaudeHookService(deps) {
34
34
  throwUnsupportedEvent(eventName);
35
35
  }
36
36
  const context = await getHookContext(input);
37
+ deps.jobGuard?.notePromptSubmitted({
38
+ repoRoot: context.project.repoRoot,
39
+ taskSlug: input.taskSlug,
40
+ role: input.role
41
+ });
37
42
  const session = await deps.sessionService.recordClaudeHookEvent(context.project.repoRoot, {
38
43
  taskSlug: input.taskSlug,
39
44
  role: input.role,
@@ -81,12 +86,33 @@ export function createClaudeHookService(deps) {
81
86
  acceptedMessageId: submitted?.id
82
87
  };
83
88
  }
84
- async function handleStopHook(input) {
89
+ async function processStopHook(input, options) {
85
90
  const eventName = parseHookEvent(input.event.hook_event_name ?? "Stop");
86
91
  if (eventName !== "Stop") {
87
92
  throwUnsupportedEvent(eventName);
88
93
  }
89
94
  const context = await getHookContext(input);
95
+ if (options.allowBlock && deps.jobGuard) {
96
+ const verdict = await deps.jobGuard.evaluateStop({
97
+ repoRoot: context.project.repoRoot,
98
+ taskSlug: input.taskSlug,
99
+ role: input.role,
100
+ taskRepoRoot: context.taskRepoRoot
101
+ });
102
+ if (verdict.behavior === "block") {
103
+ // The role turn stays alive: skip all turn-end bookkeeping so the
104
+ // round keeps running and no route dispatch happens yet.
105
+ return {
106
+ ok: true,
107
+ eventName,
108
+ taskSlug: input.taskSlug,
109
+ role: input.role,
110
+ sessionUpdated: false,
111
+ dispatchedCount: 0,
112
+ stopDecision: { behavior: "block", reason: verdict.reason }
113
+ };
114
+ }
115
+ }
90
116
  const scopedRouteDispatchInput = {
91
117
  repoRoot: context.project.repoRoot,
92
118
  taskRepoRoot: context.taskRepoRoot,
@@ -194,9 +220,13 @@ export function createClaudeHookService(deps) {
194
220
  if (eventName === "UserPromptSubmit") {
195
221
  return handleUserPromptSubmitHook(input);
196
222
  }
197
- return handleStopHook(input);
223
+ // Legacy combined endpoint: the installed hook discards the response,
224
+ // so a block decision could not be enforced. Never block here.
225
+ return processStopHook(input, { allowBlock: false });
226
+ },
227
+ handleStopHook(input) {
228
+ return processStopHook(input, { allowBlock: true });
198
229
  },
199
- handleStopHook,
200
230
  handlePermissionRequestHook
201
231
  };
202
232
  }
@@ -6,7 +6,6 @@ import { renderArchitectHarnessRules } from "../templates/harness/architect-agen
6
6
  import { renderCoderHarnessRules } from "../templates/harness/coder-agent.js";
7
7
  import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
8
8
  import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
9
- import { renderKnownIssuesDocHarnessRules } from "../templates/harness/known-issues-doc.js";
10
9
  import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
11
10
  import { renderPullRequestTemplateHarnessRules } from "../templates/harness/pull-request-template.js";
12
11
  import { renderReviewerHarnessRules } from "../templates/harness/reviewer-agent.js";
@@ -26,13 +25,22 @@ const MANAGED_BLOCK_PATTERN = /<!-- VCM:BEGIN(?:\s+version=(\d+))? -->[\s\S]*?<!
26
25
  const HASH_MANAGED_BLOCK_PATTERN = /# VCM:BEGIN(?:\s+version=(\d+))?\n[\s\S]*?# VCM:END/m;
27
26
  const CLAUDE_SETTINGS_PATH = ".claude/settings.json";
28
27
  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'`;
28
+ 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'`;
29
29
  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'`;
30
- const VCM_HOOK_EVENTS = ["UserPromptSubmit", "Stop", "PermissionRequest"];
30
+ 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"'`;
31
+ const VCM_BASH_DEFAULT_TIMEOUT_MS = "600000";
32
+ const VCM_HOOK_DEFINITIONS = [
33
+ { eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
34
+ { eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
35
+ { eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
36
+ { eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
37
+ ];
31
38
  const HARNESS_FILES = [
32
39
  {
33
40
  kind: "root-claude",
34
41
  path: "CLAUDE.md",
35
42
  title: "CLAUDE.md",
43
+ blankLineBeforeEnd: true,
36
44
  renderRules: renderRootClaudeHarnessRules
37
45
  },
38
46
  {
@@ -42,12 +50,6 @@ const HARNESS_FILES = [
42
50
  commentStyle: "hash",
43
51
  renderRules: renderGitignoreHarnessRules
44
52
  },
45
- {
46
- kind: "docs-known-issues",
47
- path: "docs/known-issues.md",
48
- title: "Known Issues",
49
- renderRules: renderKnownIssuesDocHarnessRules
50
- },
51
53
  {
52
54
  kind: "pull-request-template",
53
55
  path: ".github/pull_request_template.md",
@@ -97,6 +99,7 @@ const HARNESS_FILES = [
97
99
  kind: "agent-architect",
98
100
  path: ".claude/agents/architect.md",
99
101
  title: "Architect Agent",
102
+ blankLineBeforeEnd: true,
100
103
  frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."),
101
104
  renderRules: renderArchitectHarnessRules
102
105
  },
@@ -111,7 +114,7 @@ const HARNESS_FILES = [
111
114
  kind: "agent-reviewer",
112
115
  path: ".claude/agents/reviewer.md",
113
116
  title: "Reviewer Agent",
114
- frontmatter: renderAgentFrontmatter("reviewer", "VCM independent review role for acceptance, test adequacy, scope checks, docs gaps, and risk findings."),
117
+ frontmatter: renderAgentFrontmatter("reviewer", "VCM independent review role for acceptance, test adequacy, scope checks, and risk findings."),
115
118
  renderRules: renderReviewerHarnessRules
116
119
  }
117
120
  ];
@@ -344,10 +347,11 @@ function renderHarnessStatus(analyses) {
344
347
  };
345
348
  }
346
349
  function renderManagedBlock(definition, rules) {
350
+ const endSpacing = definition.blankLineBeforeEnd ? "\n\n" : "\n";
347
351
  if (definition.commentStyle === "hash") {
348
- return `# VCM:BEGIN version=${VCM_HARNESS_VERSION}\n${rules.trimEnd()}\n# VCM:END`;
352
+ return `# VCM:BEGIN version=${VCM_HARNESS_VERSION}\n${rules.trimEnd()}${endSpacing}# VCM:END`;
349
353
  }
350
- return `<!-- VCM:BEGIN version=${VCM_HARNESS_VERSION} -->\n${rules.trimEnd()}\n<!-- VCM:END -->`;
354
+ return `<!-- VCM:BEGIN version=${VCM_HARNESS_VERSION} -->\n${rules.trimEnd()}${endSpacing}<!-- VCM:END -->`;
351
355
  }
352
356
  function getManagedBlockPattern(definition) {
353
357
  return definition.commentStyle === "hash"
@@ -427,29 +431,30 @@ function withVcmClaudeHooks(settings) {
427
431
  delete hooks[eventName];
428
432
  }
429
433
  }
430
- for (const eventName of VCM_HOOK_EVENTS) {
431
- const existingMatchers = Array.isArray(hooks[eventName])
432
- ? hooks[eventName]
434
+ for (const definition of VCM_HOOK_DEFINITIONS) {
435
+ const existingMatchers = Array.isArray(hooks[definition.eventName])
436
+ ? hooks[definition.eventName]
433
437
  : [];
434
- hooks[eventName] = [
438
+ hooks[definition.eventName] = [
435
439
  ...existingMatchers.filter((entry) => !isVcmHookMatcher(entry)),
436
- createVcmHookMatcher(eventName)
440
+ {
441
+ ...(definition.matcher ? { matcher: definition.matcher } : {}),
442
+ hooks: [
443
+ {
444
+ type: "command",
445
+ command: definition.command,
446
+ timeout: definition.timeout
447
+ }
448
+ ]
449
+ }
437
450
  ];
438
451
  }
452
+ const env = isPlainObject(settings.env) ? { ...settings.env } : {};
453
+ env.BASH_DEFAULT_TIMEOUT_MS = VCM_BASH_DEFAULT_TIMEOUT_MS;
439
454
  return {
440
455
  ...settings,
441
- hooks
442
- };
443
- }
444
- function createVcmHookMatcher(eventName) {
445
- return {
446
- hooks: [
447
- {
448
- type: "command",
449
- command: eventName === "PermissionRequest" ? VCM_PERMISSION_REQUEST_HOOK_COMMAND : VCM_HOOK_COMMAND,
450
- timeout: 5
451
- }
452
- ]
456
+ hooks,
457
+ env
453
458
  };
454
459
  }
455
460
  function isVcmHookMatcher(value) {
@@ -460,12 +465,12 @@ function isVcmHookMatcher(value) {
460
465
  if (!isPlainObject(hook)) {
461
466
  return false;
462
467
  }
463
- return typeof hook.command === "string" && hook.command.includes("hook-event");
464
- }) || value.hooks.some((hook) => {
465
- if (!isPlainObject(hook)) {
468
+ if (typeof hook.command !== "string") {
466
469
  return false;
467
470
  }
468
- return typeof hook.command === "string" && hook.command.includes("/api/hooks/claude-code");
471
+ return hook.command.includes("VCM") ||
472
+ hook.command.includes("/api/hooks/claude-code") ||
473
+ hook.command.includes("hook-event");
469
474
  });
470
475
  }
471
476
  function isPlainObject(value) {
@@ -0,0 +1,126 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const ACTIVE_JOB_STATUSES = new Set(["queued", "starting", "running"]);
4
+ const QUEUED_JOB_FRESH_MS = 120_000;
5
+ export const MAX_CONSECUTIVE_STOP_BLOCKS = 3;
6
+ export function createJobGuardService(deps = {}) {
7
+ const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive;
8
+ const now = deps.now ?? Date.now;
9
+ const blockStates = new Map();
10
+ async function findActiveJobs(taskRepoRoot) {
11
+ const jobsRoot = path.join(taskRepoRoot, ".ai/vcm/jobs");
12
+ let entries;
13
+ try {
14
+ entries = await fs.readdir(jobsRoot);
15
+ }
16
+ catch {
17
+ return [];
18
+ }
19
+ const jobs = [];
20
+ for (const entry of entries.sort()) {
21
+ const statusPath = path.join(jobsRoot, entry, "status.json");
22
+ let status;
23
+ try {
24
+ status = JSON.parse(await fs.readFile(statusPath, "utf8"));
25
+ }
26
+ catch {
27
+ continue;
28
+ }
29
+ if (typeof status.status !== "string" || !ACTIVE_JOB_STATUSES.has(status.status)) {
30
+ continue;
31
+ }
32
+ const processId = numberOrUndefined(status.processId);
33
+ const workerPid = numberOrUndefined(status.workerPid);
34
+ const pid = processId ?? workerPid;
35
+ if (pid !== undefined) {
36
+ if (!isProcessAlive(pid)) {
37
+ continue;
38
+ }
39
+ }
40
+ else {
41
+ // queued entry whose worker has not reported a pid yet: only trust it briefly
42
+ try {
43
+ const stat = await fs.stat(statusPath);
44
+ if (now() - stat.mtimeMs > QUEUED_JOB_FRESH_MS) {
45
+ continue;
46
+ }
47
+ }
48
+ catch {
49
+ continue;
50
+ }
51
+ }
52
+ let leaseMtimeMs;
53
+ try {
54
+ leaseMtimeMs = (await fs.stat(path.join(jobsRoot, entry, "lease"))).mtimeMs;
55
+ }
56
+ catch {
57
+ leaseMtimeMs = undefined;
58
+ }
59
+ jobs.push({
60
+ jobId: typeof status.jobId === "string" ? status.jobId : entry,
61
+ status: status.status,
62
+ startedAt: typeof status.startedAt === "string" ? status.startedAt : undefined,
63
+ timeoutSeconds: numberOrUndefined(status.timeoutSeconds),
64
+ processId,
65
+ workerPid,
66
+ leaseMtimeMs
67
+ });
68
+ }
69
+ return jobs;
70
+ }
71
+ return {
72
+ findActiveJobs,
73
+ async evaluateStop(input) {
74
+ const key = stateKey(input);
75
+ const jobs = await findActiveJobs(input.taskRepoRoot);
76
+ if (jobs.length === 0) {
77
+ blockStates.delete(key);
78
+ return { behavior: "allow" };
79
+ }
80
+ const leaseMtimeMs = jobs.reduce((latest, job) => job.leaseMtimeMs !== undefined && (latest === undefined || job.leaseMtimeMs > latest)
81
+ ? job.leaseMtimeMs
82
+ : latest, undefined);
83
+ let state = blockStates.get(key) ?? { count: 0 };
84
+ const watcherProgressed = state.count > 0
85
+ && leaseMtimeMs !== undefined
86
+ && state.lastLeaseMtimeMs !== undefined
87
+ && leaseMtimeMs > state.lastLeaseMtimeMs;
88
+ if (watcherProgressed) {
89
+ state = { count: 0 };
90
+ }
91
+ if (state.count >= MAX_CONSECUTIVE_STOP_BLOCKS) {
92
+ // The role keeps trying to stop without watching; let it stop so the
93
+ // round can settle. The job worker lease will reap the job itself.
94
+ blockStates.delete(key);
95
+ return { behavior: "allow" };
96
+ }
97
+ blockStates.set(key, { count: state.count + 1, lastLeaseMtimeMs: leaseMtimeMs });
98
+ return { behavior: "block", reason: buildBlockReason(jobs) };
99
+ },
100
+ notePromptSubmitted(input) {
101
+ blockStates.delete(stateKey(input));
102
+ }
103
+ };
104
+ }
105
+ function stateKey(input) {
106
+ return `${input.repoRoot}::${input.taskSlug}::${input.role}`;
107
+ }
108
+ function buildBlockReason(jobs) {
109
+ const first = jobs[0];
110
+ const listing = jobs.map((job) => `${job.jobId} (${job.status})`).join(", ");
111
+ return `VCM: validation job ${listing} is still running. Do not end the turn while a validation job is running. `
112
+ + `Run \`.ai/tools/watch-job ${first.jobId}\` again now and keep watching until it reports a terminal result `
113
+ + `(success, failed, timeout, or orphaned), then record the result.`;
114
+ }
115
+ function numberOrUndefined(value) {
116
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
117
+ }
118
+ function defaultIsProcessAlive(pid) {
119
+ try {
120
+ process.kill(pid, 0);
121
+ return true;
122
+ }
123
+ catch (error) {
124
+ return error.code === "EPERM";
125
+ }
126
+ }
@@ -19,7 +19,7 @@ export function createMessageService(deps) {
19
19
  if (!(await deps.fs.pathExists(statePath))) {
20
20
  return {
21
21
  taskSlug: input.taskSlug,
22
- mode: "manual",
22
+ mode: "auto",
23
23
  updatedAt: now()
24
24
  };
25
25
  }
@@ -1,24 +1,28 @@
1
1
  export function renderArchitectHarnessRules() {
2
- return `## VCM Architect Rules
2
+ return `
3
+ ## VCM Architect Rules
3
4
 
4
5
  ### Role Scope
5
6
 
6
7
  - Own technical analysis, architecture planning, module boundaries, file responsibilities, public contracts, verifiable behavior, phase boundaries, behavior/contract proof points, risks, and Replan triggers.
8
+ - Own \`docs/known-issues.md\` promotion and durable issue updates.
9
+ - Own architecture docs sync across \`docs/ARCHITECTURE.md\` and affected \`<module>/ARCHITECTURE.md\` files.
7
10
  - Do not implement production code.
8
11
  - Do not design complete test cases, coverage matrices, or final validation strategy; reviewer owns independent test design, test adequacy, and validation confidence.
9
12
  - Do not make product priority or approval decisions; route those questions back to project-manager.
10
- - Reply to project-manager with the \`vcm-route-message\` skill.
11
13
 
12
14
  ### Planning Inputs
13
15
 
14
- - Read \`CLAUDE.md\`, the role command, durable plans when present, relevant handoff artifacts, and affected project docs before planning.
15
- - Use \`docs/ARCHITECTURE.md\`, \`docs/MODULE_MAP.md\`, \`docs/SECURITY.md\`, and \`docs/DEPENDENCY_RULES.md\` as durable project truth when relevant.
16
+ - 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.
17
+ - Read \`.ai/generated/module-index.json\` when planning module scope, file scope, dependency direction, or phased work.
18
+ - Read \`.ai/generated/public-surface.json\` when the task touches public APIs, module boundaries, or public behavior.
16
19
  - If durable docs conflict with the requested plan or code reality, report the conflict to project-manager and identify whether user approval is required.
17
20
 
18
21
  ### Architecture Plan
19
22
 
20
23
  - Write \`.ai/vcm/handoffs/architecture-plan.md\` before coder work starts.
21
- - The plan must cover changed files, file responsibilities, public interfaces or user-visible behavior, required behavior or contract proof points, docs impact, risks, and Replan triggers.
24
+ - 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.
25
+ - 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.
22
26
  - Keep implementation work scoped to what can be safely described, validated, reviewed, and handed off.
23
27
 
24
28
  ### Phase Planning
@@ -39,10 +43,17 @@ export function renderArchitectHarnessRules() {
39
43
  ### Docs Sync
40
44
 
41
45
  - Perform docs sync only when project-manager requests it after reviewer completes.
42
- - Check whether final code changed durable project truth: architecture, module map, public contracts, security constraints, dependency rules, or durable plans.
43
- - Update affected durable docs when project truth changed; otherwise state which docs were checked and why they remain current.
44
- - Treat reviewer-reported docs gaps as inputs; resolve technical docs drift or report conflicts back to project-manager.
46
+ - 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.
47
+ - Update affected \`<module>/ARCHITECTURE.md\` when module-level detailed design changes: boundaries, behavior, important public surface explanations, internal risks, or module-specific architecture notes.
48
+ - 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.
49
+ - When module structure changes, require \`.ai/tools/generate-module-index --check\` or regeneration.
50
+ - When crate-external public APIs change, require \`.ai/tools/generate-public-surface --check\` or regeneration.
45
51
  - Read \`.ai/vcm/handoffs/known-issues.md\` and promote confirmed unresolved issues to \`docs/known-issues.md\`.
46
52
  - 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.
53
+
54
+ ### Background Jobs
55
+
56
+ - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
57
+ - 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.
47
58
  `;
48
59
  }
@@ -3,16 +3,16 @@ export function renderRootClaudeHarnessRules() {
3
3
 
4
4
  - Use the durable project docs below as role-relevant project truth.
5
5
  - Read module-local \`CLAUDE.md\` before editing a subdirectory if one exists.
6
+ - 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.
6
7
  - Use \`vcm-long-running-validation\` for long-running validation. Follow the background job limits below.
7
8
 
8
9
  ## VCM Background Jobs
9
10
 
10
- - Do not start background jobs.
11
- - The only allowed background job is \`.ai/tools/run-long-check\` when used through the \`vcm-long-running-validation\` skill.
12
- - \`vcm-long-running-validation\` has a hard maximum timeout of 60 minutes.
13
- - Do not run or suggest operations expected to exceed 60 minutes without user approval.
14
- - Design every single validation/build operation to complete within 60 minutes; split anything larger before running it.
15
- - Do not end the current turn only to wait for a long-running shell callback.
11
+ - 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.
12
+ - The only sanctioned long-running mechanism is the \`vcm-long-running-validation\` skill: \`.ai/tools/run-long-check\` plus \`.ai/tools/watch-job\`.
13
+ - The moment a command might run longer than 2 minutes, switch to that skill instead of running the command directly.
14
+ - 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.
15
+ - 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.
16
16
 
17
17
  ## VCM Durable Project Docs
18
18
 
@@ -33,23 +33,19 @@ export function renderRootClaudeHarnessRules() {
33
33
  - Keep role outputs under \`.ai/vcm/handoffs/\`.
34
34
  - 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.
35
35
  - Record current-task unresolved findings in \`.ai/vcm/handoffs/known-issues.md\`.
36
- - Use the \`vcm-route-message\` skill when writing or updating VCM role messages.
37
- - Use the \`vcm-final-acceptance\` skill before declaring a VCM-managed task complete.
38
36
 
39
37
  ## VCM Validation Levels
40
38
 
41
39
  - L0 fast checks: format, lint, typecheck, boundary, dependency, or other cheap project checks.
42
- - L1 focused unit / contract checks: changed behavior, public function contracts, and direct regressions.
40
+ - L1 coder unit checks: changed behavior and direct regressions through project-defined unit tests.
43
41
  - L2 module / integration checks: module-level behavior, API contracts, service integration, persistence, or cross-file wiring.
44
42
  - L3 smoke E2E checks: core user journeys or critical browser/API flows.
45
43
  - L4 full regression / release checks are release-only unless explicitly requested.
46
- - Coder normally runs baseline unit-level checks plus \`check-fast\`; reviewer decides final validation sufficiency and whether L2/L3 is required.
47
44
 
48
45
  ## VCM Worktree Policy
49
46
 
50
47
  - Use one branch, one worktree, one handoff directory, and one PR or final patch per VCM-managed task.
51
48
  - Roles work sequentially in the same task worktree.
52
49
  - If \`git status\` shows uncommitted changes, commit them before handing off to another role.
53
-
54
50
  `;
55
51
  }
@@ -1,21 +1,49 @@
1
1
  export function renderCoderHarnessRules() {
2
- return `## VCM Coder Rules
3
-
4
- - Implement only the approved task scope and architecture plan.
5
- - Read \`CLAUDE.md\`, the route message or durable plan, architecture plan, relevant project docs, and cited handoff artifacts before editing.
6
- - Add or update direct validation for changed behavior when practical.
7
- - Record confirmed out-of-scope implementation issues in \`.ai/vcm/handoffs/known-issues.md\`; do not update \`docs/known-issues.md\` directly.
8
- - Do not update durable docs unless architect explicitly assigns a mechanical edit.
9
- - Do not change module boundaries, public contracts, dependency direction, durable docs, or test strategy without project-manager/architect Replan.
2
+ return `
3
+ ## VCM Coder Rules
4
+
5
+ ### Role Scope
6
+
7
+ - Own implementation and baseline implementation tests inside the approved task scope, current phase, role message, and architecture plan.
8
+ - Do not decide architecture, module boundaries, public contracts, dependency direction, durable docs updates, or final test adequacy.
9
+
10
+ ### Inputs
11
+
12
+ - 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.
13
+ - Read durable architecture/module/security/dependency docs only when the architecture plan or role message references them.
14
+ - 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.
15
+ - Use \`.ai/generated/module-index.json\` to locate approved module source and test files.
16
+ - Use \`.ai/generated/public-surface.json\` to avoid accidental public API drift.
17
+
18
+ ### Implementation
19
+
20
+ - Make only the implementation changes needed for the approved scope.
10
21
  - Do not weaken, delete, or skip tests to make validation pass.
11
- - Run \`.ai/tools/check-fast\` before handoff.
12
- - Run focused validation from \`CLAUDE.md\`, the role command, and project docs when changed behavior requires it; record skipped checks with reasons.
13
- - Stop and reply to project-manager when the approved plan conflicts with code reality.
14
- - Stop before editing when the required architecture plan, route message, approved scope, public contract, or validation expectation is missing or unclear.
15
- - If implementation requires architecture, public contract, dependency, durable docs, or test strategy changes, stop and request Replan instead of continuing.
16
- - Reply through \`.ai/vcm/handoffs/messages/coder-project-manager.md\`.
17
- - Use the \`vcm-route-message\` skill when writing or updating a role message.
18
- - After writing a route file, end the current turn immediately; do not poll, loop, or wait for another role's answer.
19
- - For slow validation, use the \`vcm-long-running-validation\` skill instead of ending the turn to wait for a shell callback.
22
+ - Record confirmed out-of-scope issues found during implementation in \`.ai/vcm/handoffs/known-issues.md\`.
23
+
24
+ ### Generated Context
25
+
26
+ - Regenerate \`.ai/generated/module-index.json\` with \`.ai/tools/generate-module-index\` after module, manifest, source-file, or test-file changes.
27
+ - Regenerate \`.ai/generated/public-surface.json\` with \`.ai/tools/generate-public-surface\` after crate-external public API or public visibility changes.
28
+ - Do not hand-edit generated context files.
29
+
30
+ ### Baseline Tests
31
+
32
+ - Add or update baseline unit tests for changed behavior: direct unit coverage, key happy path, key boundary or failure path when applicable.
33
+ - Coder validation is limited to baseline unit-level or fast L1/L2 checks; do not do smoke, integration, or E2E testing.
34
+ - If baseline unit tests cannot be run, explain the reason in the route message to project-manager.
35
+
36
+ ### Replan And Continuation
37
+
38
+ - Stop and request Replan through project-manager when the approved plan conflicts with code reality.
39
+ - Request Replan only for architecture, public contract, dependency, phase-boundary, validation-boundary, or durable-doc changes that must be decided before implementation can continue.
40
+ - Do not request Replan because of workload, session length, or context size.
41
+ - 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.
42
+ - If implementation exposes a broad testing gap beyond baseline unit tests, report it to project-manager for reviewer follow-up.
43
+
44
+ ### Background Jobs
45
+
46
+ - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
47
+ - 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.
20
48
  `;
21
49
  }
@@ -1,7 +1,8 @@
1
1
  export function renderGitignoreHarnessRules() {
2
2
  return [
3
- "# VCM local app state, task metadata, session records, and task worktrees.",
3
+ "# VCM runtime task metadata, handoffs, session records, logs, and task worktrees.",
4
4
  ".ai/vcm/",
5
- ".claude/worktrees/"
5
+ ".claude/worktrees/",
6
+ ".ai/tools/__pycache__/"
6
7
  ].join("\n");
7
8
  }
@@ -1,5 +1,6 @@
1
1
  export function renderProjectManagerHarnessRules() {
2
- return `## VCM Project Manager Rules
2
+ return `
3
+ ## VCM Project Manager Rules
3
4
 
4
5
  ### Role Scope
5
6
 
@@ -20,22 +21,21 @@ export function renderProjectManagerHarnessRules() {
20
21
 
21
22
  - Before dispatching work, confirm the current task repo root and branch.
22
23
  - If the current directory does not match \`VCM_TASK_REPO_ROOT\`, stop and report the mismatch.
23
- - Include the confirmed task repo root and branch in each role command \`## Worktree\` section.
24
+ - Include the confirmed task repo root and branch in each role message.
24
25
 
25
- ### Dispatch Artifacts
26
+ ### Dispatch
26
27
 
27
- - Before dispatching a role, update that role's command under \`.ai/vcm/handoffs/role-commands/\`.
28
- - Role commands 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.
29
- - Do not write technical design into role commands; ask architect to determine architecture, file scope, public contracts, validation requirements, and Replan triggers.
30
- - For coder or reviewer commands, reference existing handoff artifacts instead of making new technical judgments.
31
- - Dispatch the handoff with the \`vcm-route-message\` skill and follow its write-then-stop rule.
28
+ - Use the \`vcm-route-message\` skill for every role dispatch, question, result, blocker, or finding.
29
+ - 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.
30
+ - Do not write technical design into route messages; ask architect to determine architecture, file scope, public contracts, validation requirements, and Replan triggers.
31
+ - For coder or reviewer messages, reference existing handoff artifacts instead of making new technical judgments.
32
32
 
33
33
  ### Phased Tasks
34
34
 
35
35
  - When architect provides a phased plan, dispatch only one phase at a time.
36
36
  - Do not split, merge, reorder, or redefine phases yourself; route phase-plan changes back to architect.
37
- - Each coder phase must complete its assigned implementation, phase validation, and handoff artifacts before PM dispatches the next phase.
38
- - Phase validation normally runs through L2; reserve full L3 validation for final task acceptance unless architect requires phase-level L3.
37
+ - Each coder phase must complete its assigned implementation before PM dispatches the next phase.
38
+ - Phase validation normally runs through L2; reserve full L3 validation for final task acceptance.
39
39
  - Route back to architect only when coder or reviewer reports a technical mismatch with the approved plan.
40
40
 
41
41
  ### Flow Gates
@@ -48,7 +48,7 @@ export function renderProjectManagerHarnessRules() {
48
48
  ### Partial Role Results
49
49
 
50
50
  - Treat partial, blocked, or continuation-needed role results as incomplete gates.
51
- - If a role completes a coherent slice and the remaining work still matches the current role command, update the same role command and dispatch the same role again.
51
+ - If a role completes a coherent slice and the remaining work still matches the current route, dispatch the same role again.
52
52
  - Do not accept workload, session length, or context size as a reason to change the architect plan.
53
53
  - Route back to architect only for technical mismatch with the approved plan, not for workload or session-size reasons.
54
54
  - Do not advance to the next gate until the current gate is explicitly complete or an approved exception is recorded.
@@ -68,5 +68,10 @@ export function renderProjectManagerHarnessRules() {
68
68
  - Fill the PR body from final acceptance, review report, docs-sync report, known-issues disposition, and commits.
69
69
  - Do not perform technical review or validation during PR preparation; route missing evidence to the responsible role.
70
70
  - Create a draft PR by default unless the user requests a ready PR.
71
+
72
+ ### Background Jobs
73
+
74
+ - Never background a Bash command: no \`run_in_background\`, \`nohup\`, \`setsid\`, \`disown\`, or trailing \`&\`.
75
+ - 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.
71
76
  `;
72
77
  }