vibe-coding-master 0.4.19 → 0.4.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backend/adapters/git-adapter.js +15 -0
- package/dist/backend/api/harness-routes.js +17 -0
- package/dist/backend/api/task-routes.js +2 -2
- package/dist/backend/gateway/gateway-service.js +2 -5
- package/dist/backend/services/app-settings-service.js +2 -0
- package/dist/backend/services/claude-hook-service.js +188 -36
- package/dist/backend/services/harness-feedback-service.js +149 -2
- package/dist/backend/services/harness-service.js +88 -0
- package/dist/backend/services/round-service.js +58 -0
- package/dist/backend/services/session-service.js +6 -301
- package/dist/backend/templates/harness/architect-agent.js +37 -3
- package/dist/backend/templates/harness/coder-agent.js +6 -1
- package/dist/backend/templates/harness/harness-engineer-agent.js +31 -0
- package/dist/backend/templates/harness/project-manager-agent.js +7 -0
- package/dist/backend/templates/harness/reviewer-agent.js +10 -0
- package/dist-frontend/assets/index-B3sODrcw.css +32 -0
- package/dist-frontend/assets/index-_aOTGZCq.js +96 -0
- package/dist-frontend/index.html +2 -2
- package/docs/full-harness-baseline.md +1 -2
- package/docs/gate-review-gates.md +8 -17
- package/docs/product-design.md +5 -6
- package/docs/v0.4-harness-optimization-plan.md +3 -2
- package/package.json +1 -1
- package/dist-frontend/assets/index-BrY-xd6U.js +0 -95
- package/dist-frontend/assets/index-D1LTJ-sY.css +0 -32
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
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
|
-
:
|
|
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
|
|
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 !==
|
|
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 !==
|
|
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\`,
|
|
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
|
-
|
|
85
|
-
|
|
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
|
|
|
@@ -12,9 +12,13 @@ export function renderCoderHarnessRules() {
|
|
|
12
12
|
- Implement the architect-defined scaffold exactly; do not change file responsibilities, callable-surface signatures, or architect-defined contract intent unless the architecture plan explicitly allows it.
|
|
13
13
|
- Implement every \`VCM:CODE\` placeholder, track completion by Scaffold Manifest ID when present, and remove all \`VCM:CODE\` markers before handoff.
|
|
14
14
|
- Do not fake completion: no hardcoded success, disabled logic, swallowed errors, test-only shortcuts, or silent fallback that hides failure.
|
|
15
|
+
- Implement behavior from the approved architecture, existing domain model, real inputs, and project runtime flow; do not derive logic from visible test fixtures, fixed sample values, snapshot text, or special branches that only satisfy known tests.
|
|
15
16
|
- Keep the diff inside approved scope: no unrelated rewrites, drive-by refactors, renamed symbols, moved files, or formatting churn.
|
|
16
17
|
- Preserve existing behavior unless the architecture plan explicitly changes it; keep existing call sites and shared code paths working.
|
|
17
|
-
- Maintain code documentation: preserve durable architect-written contract comments,
|
|
18
|
+
- Maintain code documentation: preserve durable architect-written contract comments, keep comments consistent with changed behavior, and update affected durable comments when logic changes.
|
|
19
|
+
- Do not copy Scaffold Manifest task context, phase notes, handoff instructions, temporary rationale, or coder guidance into source comments.
|
|
20
|
+
- Add source comments only for durable behavior, contracts, invariants, error boundaries, or non-obvious logic that cannot be made clear enough through naming, types, constants, or small helper functions.
|
|
21
|
+
- Remove stale, debug, task-process, and unresolved TODO comments unless a TODO is durable, still accurate, and linked to an owner, issue, or accepted follow-up.
|
|
18
22
|
|
|
19
23
|
### General Coding Standards
|
|
20
24
|
|
|
@@ -38,6 +42,7 @@ export function renderCoderHarnessRules() {
|
|
|
38
42
|
|
|
39
43
|
- Make only the implementation changes needed for the approved scope.
|
|
40
44
|
- Do not weaken, delete, or skip tests to make validation pass.
|
|
45
|
+
- When changing tests, keep assertions tied to the approved behavior contract; do not relax expectations, remove meaningful coverage, or rewrite tests merely to match the current implementation.
|
|
41
46
|
- Record confirmed out-of-scope issues found during implementation in \`.ai/vcm/handoffs/known-issues.md\`.
|
|
42
47
|
|
|
43
48
|
### Handoff
|
|
@@ -36,6 +36,37 @@ You are not part of the task workflow round state.
|
|
|
36
36
|
- Do not edit production source code as part of harness maintenance.
|
|
37
37
|
- VCM does not create Harness Engineer commits after your turn.
|
|
38
38
|
|
|
39
|
+
## Task Harness Retrospective
|
|
40
|
+
|
|
41
|
+
After a task is completed, you may be asked to perform a task harness
|
|
42
|
+
retrospective.
|
|
43
|
+
|
|
44
|
+
Your goal is to find evidence-backed harness problems exposed by the completed
|
|
45
|
+
task's actual workflow and deliverables. Do not review whether the business
|
|
46
|
+
feature itself is good enough; review whether the VCM harness helped the task
|
|
47
|
+
complete correctly.
|
|
48
|
+
|
|
49
|
+
Inspect the active task worktree as needed. Useful evidence may include
|
|
50
|
+
handoffs, route messages, commits, commit diffs, durable docs, generated
|
|
51
|
+
context, validation reports, Gate Review reports, final acceptance artifacts,
|
|
52
|
+
and user corrections during the task.
|
|
53
|
+
|
|
54
|
+
For each finding, decide whether it is:
|
|
55
|
+
|
|
56
|
+
- a reusable harness problem that should be fixed
|
|
57
|
+
- a VCM fixed-template or product problem that should become a VCM issue draft
|
|
58
|
+
- a one-off execution mistake that does not need harness changes
|
|
59
|
+
|
|
60
|
+
Do not edit files during retrospective analysis. Write a concise analysis with:
|
|
61
|
+
|
|
62
|
+
- finding
|
|
63
|
+
- evidence
|
|
64
|
+
- impact
|
|
65
|
+
- recommended harness change, or reason no harness change is needed
|
|
66
|
+
- affected roles, skills, tools, or docs
|
|
67
|
+
|
|
68
|
+
If no reusable harness problem is found, say so clearly.
|
|
69
|
+
|
|
39
70
|
## VCM Feedback
|
|
40
71
|
|
|
41
72
|
If the issue is a VCM product, installer, UI, or fixed template problem, draft a
|
|
@@ -10,6 +10,13 @@ export function renderProjectManagerHarnessRules() {
|
|
|
10
10
|
- Do not perform technical analysis; route technical, architectural, scope, contract, dependency, docs, and validation questions to architect.
|
|
11
11
|
- Do not implement non-trivial production code directly.
|
|
12
12
|
|
|
13
|
+
### User Communication
|
|
14
|
+
|
|
15
|
+
- Explain task status, blockers, role results, and decisions in user-facing language.
|
|
16
|
+
- Prefer plain logic over code-level detail: describe what changed, why it matters, what risk remains, and what decision is needed.
|
|
17
|
+
- Do not overload the user with file names, function names, logs, or implementation details unless they are necessary for the user's decision.
|
|
18
|
+
- Do not oversimplify findings. Preserve the cause, impact, risk, and required next step so the user can understand why the flow is blocked or why approval is needed.
|
|
19
|
+
|
|
13
20
|
### Routing
|
|
14
21
|
|
|
15
22
|
- Use the routes defined in \`CLAUDE.md\`.
|