vibe-coding-master 0.4.19 → 0.4.21

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 (30) hide show
  1. package/dist/backend/adapters/git-adapter.js +15 -0
  2. package/dist/backend/api/harness-routes.js +17 -0
  3. package/dist/backend/api/task-routes.js +2 -2
  4. package/dist/backend/app-version.js +12 -0
  5. package/dist/backend/cli/install-vcm-harness.js +3 -2
  6. package/dist/backend/gateway/gateway-service.js +2 -5
  7. package/dist/backend/server.js +6 -2
  8. package/dist/backend/services/app-settings-service.js +2 -0
  9. package/dist/backend/services/claude-hook-service.js +188 -36
  10. package/dist/backend/services/diagnostics-service.js +2 -13
  11. package/dist/backend/services/harness-feedback-service.js +149 -2
  12. package/dist/backend/services/harness-service.js +159 -22
  13. package/dist/backend/services/round-service.js +58 -0
  14. package/dist/backend/services/session-service.js +6 -301
  15. package/dist/backend/templates/harness/architect-agent.js +37 -3
  16. package/dist/backend/templates/harness/coder-agent.js +6 -1
  17. package/dist/backend/templates/harness/harness-engineer-agent.js +31 -0
  18. package/dist/backend/templates/harness/project-manager-agent.js +7 -0
  19. package/dist/backend/templates/harness/reviewer-agent.js +10 -0
  20. package/dist/main.js +2 -11
  21. package/dist-frontend/assets/index-B3sODrcw.css +32 -0
  22. package/dist-frontend/assets/index-_aOTGZCq.js +96 -0
  23. package/dist-frontend/index.html +2 -2
  24. package/docs/full-harness-baseline.md +1 -2
  25. package/docs/gate-review-gates.md +8 -17
  26. package/docs/product-design.md +5 -6
  27. package/docs/v0.4-harness-optimization-plan.md +3 -2
  28. package/package.json +1 -1
  29. package/dist-frontend/assets/index-BrY-xd6U.js +0 -95
  30. package/dist-frontend/assets/index-D1LTJ-sY.css +0 -32
@@ -241,6 +241,36 @@ export function createRoundService(deps) {
241
241
  recordClaudeHookEvent(input) {
242
242
  return recordRoleTurnEvent(input);
243
243
  },
244
+ async setRoleRecovery(input) {
245
+ return withTaskLock(input, async () => {
246
+ const timestamp = now();
247
+ const state = await load(input);
248
+ const next = {
249
+ ...state,
250
+ roleRecovery: normalizeRoleRecovery(input.recovery),
251
+ updatedAt: timestamp
252
+ };
253
+ await save(input, next);
254
+ await updateSessionStatus(input, "running");
255
+ return toSessionRoundState(next, timestamp);
256
+ });
257
+ },
258
+ async clearRoleRecovery(input) {
259
+ return withTaskLock(input, async () => {
260
+ const timestamp = now();
261
+ const state = await load(input);
262
+ if (!state.roleRecovery || (input.role && state.roleRecovery.role !== input.role)) {
263
+ return toSessionRoundState(state, timestamp);
264
+ }
265
+ const next = {
266
+ ...state,
267
+ roleRecovery: undefined,
268
+ updatedAt: timestamp
269
+ };
270
+ await save(input, next);
271
+ return toSessionRoundState(next, timestamp);
272
+ });
273
+ },
244
274
  stopSession() { },
245
275
  stopTask(taskSlug) {
246
276
  clearSettleTimersForTask(taskSlug);
@@ -352,6 +382,7 @@ function toSessionRoundState(state, updatedAt) {
352
382
  totalCompletedTurnCount: state.totalCompletedTurnCount,
353
383
  totalCcActiveMs: state.totalCcActiveMs,
354
384
  currentRoundCcActiveMs: 0,
385
+ roleRecovery: state.roleRecovery,
355
386
  roles: [],
356
387
  updatedAt
357
388
  };
@@ -379,6 +410,7 @@ function toSessionRoundState(state, updatedAt) {
379
410
  totalCompletedTurnCount: state.totalCompletedTurnCount,
380
411
  totalCcActiveMs: state.totalCcActiveMs + activeDurationMs,
381
412
  currentRoundCcActiveMs,
413
+ roleRecovery: state.roleRecovery,
382
414
  roles: current.roles,
383
415
  updatedAt
384
416
  };
@@ -394,9 +426,35 @@ function normalizeRoundFile(input, taskSlug, updatedAt) {
394
426
  totalTurnCount: normalizeNumber(input.totalTurnCount ?? legacy.totalPromptSubmitCount),
395
427
  totalCompletedTurnCount: normalizeNumber(input.totalCompletedTurnCount ?? legacy.totalStopCount),
396
428
  totalCcActiveMs: normalizeNumber(input.totalCcActiveMs),
429
+ roleRecovery: normalizeRoleRecovery(input.roleRecovery),
397
430
  updatedAt: typeof input.updatedAt === "string" ? input.updatedAt : updatedAt
398
431
  };
399
432
  }
433
+ function normalizeRoleRecovery(input) {
434
+ if (!input || typeof input !== "object") {
435
+ return undefined;
436
+ }
437
+ const record = input;
438
+ if (typeof record.role !== "string" ||
439
+ (record.status !== "waiting" && record.status !== "retrying" && record.status !== "failed") ||
440
+ typeof record.lastFailureAt !== "string") {
441
+ return undefined;
442
+ }
443
+ return {
444
+ role: record.role,
445
+ status: record.status,
446
+ attempt: normalizeNumber(record.attempt),
447
+ maxAttempts: normalizeNumber(record.maxAttempts),
448
+ lastFailureAt: record.lastFailureAt,
449
+ error: typeof record.error === "string" ? record.error : undefined,
450
+ errorDetails: typeof record.errorDetails === "string" ? record.errorDetails : undefined,
451
+ lastAssistantMessage: typeof record.lastAssistantMessage === "string" ? record.lastAssistantMessage : undefined,
452
+ retryable: typeof record.retryable === "boolean" ? record.retryable : undefined,
453
+ nextRetryAt: typeof record.nextRetryAt === "string" ? record.nextRetryAt : undefined,
454
+ lastRetryAt: typeof record.lastRetryAt === "string" ? record.lastRetryAt : undefined,
455
+ failedAt: typeof record.failedAt === "string" ? record.failedAt : undefined
456
+ };
457
+ }
400
458
  function normalizeRound(input) {
401
459
  if (!input || typeof input.id !== "string") {
402
460
  return undefined;
@@ -1,23 +1,20 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
- import { CORE_VCM_ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
3
+ import { VCM_ROLE_NAMES, isDispatchableRole } from "../../shared/constants.js";
4
4
  import { VcmError } from "../errors.js";
5
5
  import { resolveRepoPath } from "../adapters/filesystem.js";
6
6
  import { submitTerminalInput } from "../runtime/terminal-submit.js";
7
7
  import { claudeTranscriptPath } from "./claude-transcript-service.js";
8
8
  import { readHarnessRevisionState } from "./harness-revision.js";
9
9
  import { getTaskRuntimeRepoRoot } from "./task-service.js";
10
- const GATE_REVIEWER_ROLE = "gate-reviewer";
11
10
  const TRANSLATOR_ROLE = "translator";
12
11
  const HARNESS_ENGINEER_ROLE = "harness-engineer";
13
12
  const TRANSLATION_DIR = ".ai/vcm/translations";
14
13
  const TRANSLATOR_SESSION_PATH = ".ai/vcm/translations/session.json";
15
14
  const HARNESS_ENGINEER_DIR = ".ai/vcm/harness-engineer";
16
15
  const HARNESS_ENGINEER_SESSION_PATH = ".ai/vcm/harness-engineer/session.json";
17
- const GATE_REVIEWER_SESSION_PATH = ".ai/vcm/gate-reviewer/session.json";
18
16
  const PROJECT_TRANSLATOR_SCOPE = "__project__";
19
17
  const PROJECT_HARNESS_ENGINEER_SCOPE = "__project_harness_engineer__";
20
- const PROJECT_GATE_REVIEWER_SCOPE = "__project_gate_reviewer__";
21
18
  export function createSessionService(deps) {
22
19
  const now = deps.now ?? (() => new Date().toISOString());
23
20
  async function readCurrentHarnessRevision(repoRoot) {
@@ -112,101 +109,6 @@ export function createSessionService(deps) {
112
109
  await persistRoleSessionRecord(deps.fs, repoRoot, taskRepoRoot, config.stateRoot, record);
113
110
  return withHarnessRevisionView(repoRoot, record);
114
111
  }
115
- async function launchProjectGateReviewerSession(repoRoot, input, launchMode, activeTaskSlug) {
116
- const live = toRoleSessionRecordView(getRegisteredProjectGateReviewerSession(deps.registry, deps.runtime), deps.runtime);
117
- if (live && live.status === "running") {
118
- return withHarnessRevisionView(repoRoot, await bindProjectGateReviewerSession(repoRoot, live, activeTaskSlug));
119
- }
120
- const config = await deps.projectService.loadConfig(repoRoot);
121
- const persisted = await loadPersistedProjectGateReviewerSession(deps.fs, repoRoot);
122
- const permissionMode = normalizeClaudePermissionMode(input.permissionMode ?? persisted?.permissionMode);
123
- const model = normalizeClaudeModel(input.model ?? persisted?.model);
124
- const effort = normalizeClaudeEffort(input.effort ?? persisted?.effort);
125
- const claudeSessionId = launchMode === "resume"
126
- ? persisted?.claudeSessionId
127
- : randomUUID();
128
- if (!claudeSessionId) {
129
- throw new VcmError({
130
- code: "GATE_REVIEWER_SESSION_MISSING",
131
- message: "Gate Reviewer does not have a session id to resume.",
132
- statusCode: 409,
133
- hint: "Start Gate Reviewer once before using Resume."
134
- });
135
- }
136
- const transcriptPath = launchMode === "resume" && persisted?.transcriptPath
137
- ? persisted.transcriptPath
138
- : claudeTranscriptPath(repoRoot, claudeSessionId);
139
- const activeTask = activeTaskSlug
140
- ? await deps.taskService.loadTask(repoRoot, activeTaskSlug)
141
- : undefined;
142
- const activeTaskRepoRoot = activeTask ? getTaskRuntimeRepoRoot(activeTask) : undefined;
143
- const startCommand = {
144
- ...deps.claude.buildRoleStartCommand(GATE_REVIEWER_ROLE, config.claudeCommand, permissionMode, claudeSessionId, launchMode === "resume", model, effort),
145
- cwd: repoRoot
146
- };
147
- const runtimeSession = await deps.runtime.createSession({
148
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
149
- role: GATE_REVIEWER_ROLE,
150
- command: startCommand.command,
151
- args: startCommand.args,
152
- cwd: startCommand.cwd,
153
- env: {
154
- VCM_API_URL: deps.apiUrl,
155
- VCM_BASE_REPO_ROOT: repoRoot,
156
- VCM_TASK_REPO_ROOT: activeTaskRepoRoot ?? repoRoot,
157
- VCM_TASK_SLUG: activeTaskSlug ?? PROJECT_GATE_REVIEWER_SCOPE,
158
- VCM_ROLE: GATE_REVIEWER_ROLE,
159
- VCM_SESSION_ID: claudeSessionId
160
- },
161
- cols: input.cols,
162
- rows: input.rows
163
- });
164
- const timestamp = now();
165
- const harnessRevision = await readCurrentHarnessRevision(repoRoot);
166
- const record = {
167
- id: runtimeSession.id,
168
- claudeSessionId,
169
- transcriptPath,
170
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
171
- role: GATE_REVIEWER_ROLE,
172
- status: runtimeSession.status,
173
- activityStatus: "idle",
174
- command: startCommand.display,
175
- permissionMode,
176
- model,
177
- effort,
178
- cwd: startCommand.cwd,
179
- terminalBackend: "node-pty",
180
- pid: runtimeSession.pid,
181
- startedAt: runtimeSession.startedAt,
182
- updatedAt: timestamp,
183
- lastOutputAt: runtimeSession.lastOutputAt,
184
- activeTaskSlug,
185
- activeTaskRepoRoot,
186
- harnessRevision,
187
- exitCode: runtimeSession.exitCode
188
- };
189
- deps.registry.upsert(record);
190
- await persistProjectGateReviewerSession(deps.fs, repoRoot, record);
191
- return withHarnessRevisionView(repoRoot, record);
192
- }
193
- async function bindProjectGateReviewerSession(repoRoot, record, activeTaskSlug) {
194
- if (!activeTaskSlug) {
195
- return record;
196
- }
197
- const task = await deps.taskService.loadTask(repoRoot, activeTaskSlug);
198
- const activeTaskRepoRoot = getTaskRuntimeRepoRoot(task);
199
- const updated = {
200
- ...record,
201
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
202
- activeTaskSlug,
203
- activeTaskRepoRoot,
204
- updatedAt: now()
205
- };
206
- deps.registry.upsert(updated);
207
- await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
208
- return updated;
209
- }
210
112
  async function launchProjectTranslatorSession(repoRoot, input, launchMode) {
211
113
  const taskContext = await resolveProjectToolTaskContext(repoRoot, input, "Translator");
212
114
  const live = toRoleSessionRecordView(getRegisteredProjectTranslatorSession(deps.registry, deps.runtime), deps.runtime);
@@ -379,8 +281,7 @@ export function createSessionService(deps) {
379
281
  }
380
282
  async function migrateRunningProjectToolSessionCwd(repoRoot, session, targetCwd) {
381
283
  if (session.role !== TRANSLATOR_ROLE
382
- && session.role !== HARNESS_ENGINEER_ROLE
383
- && session.role !== GATE_REVIEWER_ROLE) {
284
+ && session.role !== HARNESS_ENGINEER_ROLE) {
384
285
  return session;
385
286
  }
386
287
  if (samePath(session.cwd, targetCwd)) {
@@ -407,9 +308,7 @@ export function createSessionService(deps) {
407
308
  async function resumeProjectToolSessionAtCwd(repoRoot, session, targetCwd) {
408
309
  const live = toRoleSessionRecordView(session.role === TRANSLATOR_ROLE
409
310
  ? getRegisteredProjectTranslatorSession(deps.registry, deps.runtime)
410
- : session.role === HARNESS_ENGINEER_ROLE
411
- ? getRegisteredProjectHarnessEngineerSession(deps.registry, deps.runtime)
412
- : getRegisteredProjectGateReviewerSession(deps.registry, deps.runtime), deps.runtime);
311
+ : getRegisteredProjectHarnessEngineerSession(deps.registry, deps.runtime), deps.runtime);
413
312
  if (live?.status === "running") {
414
313
  return migrateRunningProjectToolSessionCwd(repoRoot, live, targetCwd);
415
314
  }
@@ -460,10 +359,6 @@ export function createSessionService(deps) {
460
359
  return migrateRunningProjectToolSessionCwd(repoRoot, resumed, targetCwd);
461
360
  }
462
361
  async function persistProjectScopedToolSession(repoRoot, session) {
463
- if (session.role === GATE_REVIEWER_ROLE) {
464
- await persistProjectGateReviewerSession(deps.fs, repoRoot, session);
465
- return;
466
- }
467
362
  if (session.role === TRANSLATOR_ROLE) {
468
363
  await persistTranslatorSession(deps.fs, repoRoot, session);
469
364
  return;
@@ -498,13 +393,6 @@ export function createSessionService(deps) {
498
393
  return withHarnessRevisionView(repoRoot, updated);
499
394
  }
500
395
  async function persistNotifiedHarnessSession(repoRoot, session) {
501
- if (session.role === GATE_REVIEWER_ROLE) {
502
- await persistProjectGateReviewerSession(deps.fs, repoRoot, {
503
- ...session,
504
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE
505
- });
506
- return;
507
- }
508
396
  if (session.role === TRANSLATOR_ROLE) {
509
397
  await persistTranslatorSession(deps.fs, repoRoot, {
510
398
  ...session,
@@ -524,68 +412,6 @@ export function createSessionService(deps) {
524
412
  await persistRoleSessionRecord(deps.fs, repoRoot, getTaskRuntimeRepoRoot(task), config.stateRoot, session);
525
413
  }
526
414
  return {
527
- startProjectGateReviewerSession(repoRoot, input = {}) {
528
- return launchProjectGateReviewerSession(repoRoot, input, "fresh");
529
- },
530
- resumeProjectGateReviewerSession(repoRoot, input = {}) {
531
- return launchProjectGateReviewerSession(repoRoot, input, "resume");
532
- },
533
- async stopProjectGateReviewerSession(repoRoot) {
534
- const existing = await this.getProjectGateReviewerSession(repoRoot);
535
- if (!existing) {
536
- throw new VcmError({
537
- code: "SESSION_MISSING",
538
- message: "Gate Reviewer session has not been started.",
539
- statusCode: 404
540
- });
541
- }
542
- if (deps.runtime.getSession(existing.id)) {
543
- await deps.runtime.stop(existing.id);
544
- }
545
- const updated = {
546
- ...existing,
547
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
548
- status: "exited",
549
- activityStatus: "idle",
550
- updatedAt: now()
551
- };
552
- deps.registry.upsert(updated);
553
- await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
554
- return updated;
555
- },
556
- async restartProjectGateReviewerSession(repoRoot, input = {}) {
557
- const existing = await this.getProjectGateReviewerSession(repoRoot);
558
- if (!existing) {
559
- return launchProjectGateReviewerSession(repoRoot, input, "fresh");
560
- }
561
- if (deps.runtime.getSession(existing.id)) {
562
- await deps.runtime.stop(existing.id);
563
- }
564
- deps.registry.remove(existing.id);
565
- return launchProjectGateReviewerSession(repoRoot, input, "fresh");
566
- },
567
- async getProjectGateReviewerSession(repoRoot) {
568
- const record = getRegisteredProjectGateReviewerSession(deps.registry, deps.runtime)
569
- ?? await loadPersistedProjectGateReviewerSession(deps.fs, repoRoot);
570
- const view = toRoleSessionRecordView(record, deps.runtime);
571
- return view ? withHarnessRevisionView(repoRoot, view) : undefined;
572
- },
573
- async ensureProjectGateReviewerSession(repoRoot, input = {}) {
574
- const existing = await this.getProjectGateReviewerSession(repoRoot);
575
- if (existing?.status === "running") {
576
- return existing;
577
- }
578
- if (existing?.claudeSessionId) {
579
- return this.resumeProjectGateReviewerSession(repoRoot, {
580
- permissionMode: input.permissionMode ?? existing.permissionMode,
581
- model: input.model ?? existing.model,
582
- effort: input.effort ?? existing.effort,
583
- cols: input.cols,
584
- rows: input.rows
585
- });
586
- }
587
- return this.startProjectGateReviewerSession(repoRoot, input);
588
- },
589
415
  startProjectTranslatorSession(repoRoot, input = {}) {
590
416
  return launchProjectTranslatorSession(repoRoot, input, "fresh");
591
417
  },
@@ -835,10 +661,6 @@ export function createSessionService(deps) {
835
661
  return notifyHarnessUpdatedForSession(repoRoot, current);
836
662
  },
837
663
  startRoleSession(repoRoot, taskSlug, role, input = {}) {
838
- if (role === GATE_REVIEWER_ROLE) {
839
- return launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug)
840
- .then((session) => scopeProjectRoleSession(session, taskSlug));
841
- }
842
664
  if (role === TRANSLATOR_ROLE) {
843
665
  return this.startProjectTranslatorSession(repoRoot, { ...input, taskSlug });
844
666
  }
@@ -848,10 +670,6 @@ export function createSessionService(deps) {
848
670
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
849
671
  },
850
672
  resumeRoleSession(repoRoot, taskSlug, role, input = {}) {
851
- if (role === GATE_REVIEWER_ROLE) {
852
- return launchProjectGateReviewerSession(repoRoot, input, "resume", taskSlug)
853
- .then((session) => scopeProjectRoleSession(session, taskSlug));
854
- }
855
673
  if (role === TRANSLATOR_ROLE) {
856
674
  return this.resumeProjectTranslatorSession(repoRoot, { ...input, taskSlug });
857
675
  }
@@ -861,9 +679,6 @@ export function createSessionService(deps) {
861
679
  return launchRoleSession(repoRoot, taskSlug, role, input, "resume");
862
680
  },
863
681
  async stopRoleSession(repoRoot, taskSlug, role) {
864
- if (role === GATE_REVIEWER_ROLE) {
865
- return scopeProjectRoleSession(await this.stopProjectGateReviewerSession(repoRoot), taskSlug);
866
- }
867
682
  if (role === TRANSLATOR_ROLE) {
868
683
  void taskSlug;
869
684
  return this.stopProjectTranslatorSession(repoRoot);
@@ -896,16 +711,6 @@ export function createSessionService(deps) {
896
711
  return updated;
897
712
  },
898
713
  async restartRoleSession(repoRoot, taskSlug, role, input = {}) {
899
- if (role === GATE_REVIEWER_ROLE) {
900
- const existing = await this.getProjectGateReviewerSession(repoRoot);
901
- if (existing && deps.runtime.getSession(existing.id)) {
902
- await deps.runtime.stop(existing.id);
903
- }
904
- if (existing) {
905
- deps.registry.remove(existing.id);
906
- }
907
- return scopeProjectRoleSession(await launchProjectGateReviewerSession(repoRoot, input, "fresh", taskSlug), taskSlug);
908
- }
909
714
  if (role === TRANSLATOR_ROLE) {
910
715
  return this.restartProjectTranslatorSession(repoRoot, { ...input, taskSlug });
911
716
  }
@@ -923,14 +728,6 @@ export function createSessionService(deps) {
923
728
  return launchRoleSession(repoRoot, taskSlug, role, input, "fresh");
924
729
  },
925
730
  async getRoleSession(repoRoot, taskSlug, role) {
926
- if (role === GATE_REVIEWER_ROLE) {
927
- const session = await this.getProjectGateReviewerSession(repoRoot);
928
- if (!session) {
929
- return undefined;
930
- }
931
- const scoped = scopeProjectRoleSession(await bindProjectGateReviewerSession(repoRoot, session, taskSlug), taskSlug);
932
- return scoped ? withHarnessRevisionView(repoRoot, scoped) : undefined;
933
- }
934
731
  if (role === TRANSLATOR_ROLE) {
935
732
  void taskSlug;
936
733
  return this.getProjectTranslatorSession(repoRoot);
@@ -956,7 +753,7 @@ export function createSessionService(deps) {
956
753
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
957
754
  const taskRepoRoot = getTaskRuntimeRepoRoot(task);
958
755
  const persistedTaskSession = await loadPersistedTaskSessionRecord(deps.fs, taskRepoRoot, config.stateRoot, taskSlug);
959
- for (const role of CORE_VCM_ROLE_NAMES) {
756
+ for (const role of VCM_ROLE_NAMES) {
960
757
  const record = deps.registry.getByRole(taskSlug, role)
961
758
  ?? normalizePersistedRoleRecord(persistedTaskSession?.roles[role]?.record);
962
759
  const session = toRoleSessionRecordView(record, deps.runtime);
@@ -964,10 +761,6 @@ export function createSessionService(deps) {
964
761
  sessions.push(session);
965
762
  }
966
763
  }
967
- const gateReviewerSession = scopeProjectRoleSession(await this.getProjectGateReviewerSession(repoRoot), taskSlug);
968
- if (gateReviewerSession) {
969
- sessions.push(gateReviewerSession);
970
- }
971
764
  return Promise.all(sessions.map((session) => withHarnessRevisionView(repoRoot, session)));
972
765
  },
973
766
  async notifyRoleHarnessUpdated(repoRoot, taskSlug, role) {
@@ -982,31 +775,6 @@ export function createSessionService(deps) {
982
775
  return notifyHarnessUpdatedForSession(repoRoot, current);
983
776
  },
984
777
  async recordRoleHookEvent(repoRoot, input) {
985
- if (input.role === GATE_REVIEWER_ROLE) {
986
- const current = await this.getProjectGateReviewerSession(repoRoot);
987
- if (!current || (!input.allowSessionMismatch && !matchesRoleHookSession(current, input))) {
988
- return undefined;
989
- }
990
- const timestamp = now();
991
- const isTurnEnd = isTurnEndHook(input.eventName);
992
- const isCompact = isCompactHook(input.eventName);
993
- const updated = {
994
- ...current,
995
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE,
996
- claudeSessionId: input.sessionId ?? current.claudeSessionId,
997
- transcriptPath: input.transcriptPath ?? current.transcriptPath,
998
- cwd: input.cwd ?? current.cwd,
999
- activityStatus: isTurnEnd ? "idle" : isCompact ? current.activityStatus : "running",
1000
- lastHookEventAt: timestamp,
1001
- lastTurnEndedAt: isTurnEnd ? timestamp : current.lastTurnEndedAt,
1002
- lastTurnStartedAt: isTurnEnd || isCompact ? current.lastTurnStartedAt : timestamp,
1003
- lastCompactAt: isCompact ? timestamp : current.lastCompactAt,
1004
- updatedAt: timestamp
1005
- };
1006
- deps.registry.upsert(updated);
1007
- await persistProjectGateReviewerSession(deps.fs, repoRoot, updated);
1008
- return scopeProjectRoleSession(updated, input.taskSlug);
1009
- }
1010
778
  if (input.role === TRANSLATOR_ROLE) {
1011
779
  void input.taskSlug;
1012
780
  return this.recordProjectTranslatorHookEvent(repoRoot, {
@@ -1073,15 +841,6 @@ export function createSessionService(deps) {
1073
841
  lastHookEventAt: timestamp,
1074
842
  updatedAt: timestamp
1075
843
  };
1076
- if (role === GATE_REVIEWER_ROLE) {
1077
- const persisted = {
1078
- ...updated,
1079
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE
1080
- };
1081
- deps.registry.upsert(persisted);
1082
- await persistProjectGateReviewerSession(deps.fs, repoRoot, persisted);
1083
- return updated;
1084
- }
1085
844
  deps.registry.upsert(updated);
1086
845
  const config = await deps.projectService.loadConfig(repoRoot);
1087
846
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
@@ -1100,15 +859,6 @@ export function createSessionService(deps) {
1100
859
  lastTurnEndedAt: timestamp,
1101
860
  updatedAt: timestamp
1102
861
  };
1103
- if (role === GATE_REVIEWER_ROLE) {
1104
- const persisted = {
1105
- ...updated,
1106
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE
1107
- };
1108
- deps.registry.upsert(persisted);
1109
- await persistProjectGateReviewerSession(deps.fs, repoRoot, persisted);
1110
- return updated;
1111
- }
1112
862
  deps.registry.upsert(updated);
1113
863
  const config = await deps.projectService.loadConfig(repoRoot);
1114
864
  const task = await deps.taskService.loadTask(repoRoot, taskSlug);
@@ -1188,7 +938,7 @@ function getHandoffArtifactPath(paths, role) {
1188
938
  return undefined;
1189
939
  }
1190
940
  function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
1191
- if (role !== TRANSLATOR_ROLE && role !== GATE_REVIEWER_ROLE && role !== HARNESS_ENGINEER_ROLE) {
941
+ if (role !== TRANSLATOR_ROLE && role !== HARNESS_ENGINEER_ROLE) {
1192
942
  return registry.getByRole(taskSlug, role);
1193
943
  }
1194
944
  const candidates = registry.list().filter((session) => session.role === role);
@@ -1198,11 +948,6 @@ function getRegisteredRoleSession(registry, runtime, taskSlug, role) {
1198
948
  ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
1199
949
  return scopeProjectRoleSession(scoped, taskSlug);
1200
950
  }
1201
- function getRegisteredProjectGateReviewerSession(registry, runtime) {
1202
- const candidates = registry.list().filter((session) => session.role === GATE_REVIEWER_ROLE);
1203
- const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
1204
- return live ?? candidates.sort(compareSessionUpdatedAtDesc)[0];
1205
- }
1206
951
  function getRegisteredProjectTranslatorSession(registry, runtime) {
1207
952
  const candidates = registry.list().filter((session) => session.role === TRANSLATOR_ROLE);
1208
953
  const live = candidates.find((session) => runtime.getSession(session.id)?.status === "running");
@@ -1217,7 +962,7 @@ function compareSessionUpdatedAtDesc(left, right) {
1217
962
  return (right.updatedAt ?? "").localeCompare(left.updatedAt ?? "");
1218
963
  }
1219
964
  function scopeProjectRoleSession(record, taskSlug) {
1220
- if (!record || (record.role !== TRANSLATOR_ROLE && record.role !== GATE_REVIEWER_ROLE && record.role !== HARNESS_ENGINEER_ROLE)) {
965
+ if (!record || (record.role !== TRANSLATOR_ROLE && record.role !== HARNESS_ENGINEER_ROLE)) {
1221
966
  return record;
1222
967
  }
1223
968
  return {
@@ -1226,10 +971,6 @@ function scopeProjectRoleSession(record, taskSlug) {
1226
971
  };
1227
972
  }
1228
973
  async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, stateRoot, taskSlug, role) {
1229
- if (role === GATE_REVIEWER_ROLE) {
1230
- void taskSlug;
1231
- return loadPersistedProjectGateReviewerSession(fs, baseRepoRoot);
1232
- }
1233
974
  if (role === TRANSLATOR_ROLE) {
1234
975
  void taskSlug;
1235
976
  return loadPersistedTranslatorSession(fs, baseRepoRoot);
@@ -1240,21 +981,6 @@ async function loadPersistedRoleRecordForRole(fs, baseRepoRoot, taskRepoRoot, st
1240
981
  }
1241
982
  return loadPersistedRoleRecord(fs, taskRepoRoot, stateRoot, taskSlug, role);
1242
983
  }
1243
- async function loadPersistedProjectGateReviewerSession(fs, repoRoot) {
1244
- const sessionPath = resolveRepoPath(repoRoot, GATE_REVIEWER_SESSION_PATH);
1245
- if (!(await fs.pathExists(sessionPath))) {
1246
- return undefined;
1247
- }
1248
- const payload = await fs.readJson(sessionPath);
1249
- const record = normalizePersistedRoleRecord(isProjectRoleSessionFile(payload) ? payload.record : payload);
1250
- if (!record) {
1251
- return undefined;
1252
- }
1253
- return {
1254
- ...record,
1255
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE
1256
- };
1257
- }
1258
984
  async function loadPersistedTranslatorSession(fs, repoRoot) {
1259
985
  const sessionPath = resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH);
1260
986
  if (!(await fs.pathExists(sessionPath))) {
@@ -1314,12 +1040,6 @@ function normalizePersistedRoleRecord(record) {
1314
1040
  : undefined;
1315
1041
  }
1316
1042
  function normalizeProjectScopedRecordForPersistence(record) {
1317
- if (record.role === GATE_REVIEWER_ROLE) {
1318
- return {
1319
- ...record,
1320
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE
1321
- };
1322
- }
1323
1043
  if (record.role === TRANSLATOR_ROLE) {
1324
1044
  return {
1325
1045
  ...record,
@@ -1373,10 +1093,6 @@ async function persistTaskSession(fs, repoRoot, stateRoot, session) {
1373
1093
  });
1374
1094
  }
1375
1095
  async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoot, session) {
1376
- if (session.role === GATE_REVIEWER_ROLE) {
1377
- await persistProjectGateReviewerSession(fs, baseRepoRoot, session);
1378
- return;
1379
- }
1380
1096
  if (session.role === TRANSLATOR_ROLE) {
1381
1097
  await persistTranslatorSession(fs, baseRepoRoot, session);
1382
1098
  return;
@@ -1387,17 +1103,6 @@ async function persistRoleSessionRecord(fs, baseRepoRoot, taskRepoRoot, stateRoo
1387
1103
  }
1388
1104
  await persistTaskSession(fs, taskRepoRoot, stateRoot, session);
1389
1105
  }
1390
- async function persistProjectGateReviewerSession(fs, repoRoot, session) {
1391
- await fs.writeJsonAtomic(resolveRepoPath(repoRoot, GATE_REVIEWER_SESSION_PATH), {
1392
- version: 1,
1393
- role: session.role,
1394
- updatedAt: session.updatedAt,
1395
- record: {
1396
- ...session,
1397
- taskSlug: PROJECT_GATE_REVIEWER_SCOPE
1398
- }
1399
- });
1400
- }
1401
1106
  async function persistTranslatorSession(fs, repoRoot, session) {
1402
1107
  await fs.writeJsonAtomic(resolveRepoPath(repoRoot, TRANSLATOR_SESSION_PATH), {
1403
1108
  version: 1,
@@ -8,6 +8,7 @@ export function renderArchitectHarnessRules() {
8
8
  - Define every changed or created file's purpose, logic boundary, collaboration points, and non-private callable surface.
9
9
  - Own \`docs/known-issues.md\` promotion and durable issue updates.
10
10
  - Own architecture docs sync across \`docs/ARCHITECTURE.md\` and affected \`<module>/ARCHITECTURE.md\` files.
11
+ - Own post-task module architecture doc maintenance for every module touched by the final diff.
11
12
  - Do not implement production code.
12
13
  - Do not design complete test cases, coverage matrices, or final validation strategy; reviewer owns independent test design, test adequacy, and validation confidence.
13
14
  - Do not make product priority or approval decisions; route those questions back to project-manager.
@@ -31,7 +32,7 @@ export function renderArchitectHarnessRules() {
31
32
  - Give each Scaffold Manifest row a stable ID such as \`SCF-001\`; use that ID in any related \`VCM:CODE\` marker so coder can report completion by ID.
32
33
  - Put task context, phase notes, handoff instructions, temporary rationale, and coder guidance in the \`Scaffold Manifest\`, not in source-code comments.
33
34
  - Cover architecture docs impact, known risks, and Replan triggers.
34
- - 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.
35
+ - For docs impact, list every touched module and state whether its \`<module>/ARCHITECTURE.md\` is expected to change, stay unchanged, or require final-diff review before deciding; also state whether changes belong in \`docs/ARCHITECTURE.md\`, \`.ai/generated/public-surface.json\`, or no durable architecture doc.
35
36
 
36
37
  #### Code Scaffolding
37
38
 
@@ -49,6 +50,11 @@ export function renderArchitectHarnessRules() {
49
50
  ### Phase Planning
50
51
 
51
52
  - 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.
53
+ - For complex tasks, first provide an overall solution outline and recommended phases, but keep detailed implementation planning limited to the current phase.
54
+ - Treat \`.ai/vcm/handoffs/architecture-plan.md\` as the executable plan for the current phase, not an accumulating history of all phases.
55
+ - When moving to a new phase, rewrite \`architecture-plan.md\` for that phase: remove previous phase detailed scope, Scaffold Manifest rows, \`VCM:CODE\` guidance, and completed phase instructions.
56
+ - Keep only the minimum overall roadmap and prior-phase context needed to understand the current phase.
57
+ - Durable decisions discovered in previous phases must be promoted to durable docs when needed, not preserved as old task detail inside \`architecture-plan.md\`.
52
58
  - Split phased work into verifiable engineering slices with clear handoff and proof boundaries.
53
59
  - Prefer behavior slices, but use module, interface, migration, or risk-isolation slices when they are clearer.
54
60
  - Each phase must state goal, non-goals, affected scope, required behavior or contract proof points, completion criteria, dependencies, risks, and Replan triggers.
@@ -76,13 +82,41 @@ export function renderArchitectHarnessRules() {
76
82
  ### Docs Sync
77
83
 
78
84
  - Perform docs sync only when project-manager requests it after reviewer completes.
85
+
86
+ #### Architecture Docs Sync
87
+
88
+ - Architecture docs describe the current durable system architecture, not task history, implementation chronology, changelog, investigation notes, validation logs, or handoff content.
89
+ - Do not add phase/task/RP labels unless they are durable product, protocol, or spec identifiers that future maintainers must understand.
90
+ - Keep project-level docs focused on module map, dependency direction, cross-module relationships, major runtime flows, and project-wide constraints.
91
+ - Keep module-level docs focused on current responsibility boundaries, owned behavior, non-owned behavior, collaboration points, important public contracts, invariants, risks, and update triggers.
92
+ - Do not duplicate the generated public API index; explain design intent and contract meaning instead.
79
93
  - 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.
80
94
  - Update affected \`<module>/ARCHITECTURE.md\` when module-level detailed design changes: boundaries, behavior, important public surface explanations, internal risks, or module-specific architecture notes.
95
+ - During docs sync, inspect every module touched by the final diff.
96
+ - For each touched module, update its \`<module>/ARCHITECTURE.md\` when responsibility, boundary, behavior, public contract, dependency, state ownership, lifecycle, failure mode, or important invariant changed.
97
+ - If a touched module's architecture doc does not need changes, record why in \`.ai/vcm/handoffs/docs-sync-report.md\`.
98
+ - Do not move task logs, temporary rationale, or per-task validation history into durable architecture docs.
81
99
  - 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.
82
100
  - When module structure changes, require \`.ai/tools/generate-module-index --check\` or regeneration.
83
101
  - When public APIs, routes, or externally consumed surfaces change, require \`.ai/tools/generate-public-surface --check\` or regeneration.
84
- - Read \`.ai/vcm/handoffs/known-issues.md\` and promote confirmed unresolved issues to \`docs/known-issues.md\`.
85
- - 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.
102
+
103
+ #### Known Issues Sync
104
+
105
+ - \`docs/known-issues.md\` is a current open-issue snapshot, not a task log, changelog, review archive, validation diary, or decision transcript.
106
+ - Promote only unresolved durable issues or accepted limitations that can affect future architecture, implementation, validation, operation, or release decisions.
107
+ - Remove fully resolved issues from \`docs/known-issues.md\`; git history preserves resolved details.
108
+ - When a parent issue remains open but some sub-items are resolved, rewrite the entry around the remaining current gap instead of preserving resolved-history narrative.
109
+ - Keep one KI entry focused on one owning problem. Split unrelated residuals instead of grouping them under a phase, review, or implementation session.
110
+ - Do not include round names, role-session notes, commit hashes, reviewer verdict history, temporary investigation logs, or full validation history unless they are essential to identify the current unresolved issue.
111
+ - Each KI entry should state: status, category, affected modules/surfaces, current gap, impact, mitigation or workaround, resolution condition, and related issue IDs when useful.
112
+ - Distinguish product/protocol issues from dev-environment, test-infra, harness, or VCM-tooling issues. Do not mix them in one KI entry.
113
+ - Do not promote a task-local deferral unless it remains relevant after the task ends.
114
+ - Read \`.ai/vcm/handoffs/known-issues.md\`; promote only confirmed unresolved durable issues that satisfy Known Issues Sync.
115
+ - During docs sync, remove or rewrite resolved/stale KI entries touched by the task so \`docs/known-issues.md\` remains an open-issue snapshot.
116
+
117
+ #### Docs Sync Report
118
+
119
+ - Write \`.ai/vcm/handoffs/docs-sync-report.md\` with decision, evidence reviewed, architecture drift check, docs updated, docs left unchanged, promoted/updated/removed/not-promoted known issues, remaining documentation risks, and handoff notes.
86
120
 
87
121
  ### Background Jobs
88
122