peaks-loop 4.0.50 → 4.0.52

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 (127) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README-en.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/cli/commands/baseline-commands.js +11 -1
  5. package/dist/cli/commands/codegraph-command-runtime.d.ts +28 -0
  6. package/dist/cli/commands/codegraph-command-runtime.js +72 -0
  7. package/dist/cli/commands/codegraph-commands.d.ts +2 -11
  8. package/dist/cli/commands/codegraph-commands.js +173 -228
  9. package/dist/cli/commands/codegraph-status-command.d.ts +22 -0
  10. package/dist/cli/commands/codegraph-status-command.js +299 -0
  11. package/dist/cli/commands/core/memory-command.js +6 -2
  12. package/dist/cli/commands/job-commands.js +121 -30
  13. package/dist/cli/commands/project-commands.js +13 -3
  14. package/dist/cli/commands/request-commands.js +19 -8
  15. package/dist/cli/commands/share-commands.js +85 -18
  16. package/dist/cli/commands/slice-commands.js +2 -2
  17. package/dist/services/artifacts/artifact-prerequisites.js +23 -1
  18. package/dist/services/codegraph/codegraph-autorefresh.d.ts +16 -0
  19. package/dist/services/codegraph/codegraph-autorefresh.js +51 -5
  20. package/dist/services/codegraph/codegraph-config-repair-writer.d.ts +88 -0
  21. package/dist/services/codegraph/codegraph-config-repair-writer.js +322 -0
  22. package/dist/services/codegraph/codegraph-exclude-integrity.d.ts +20 -2
  23. package/dist/services/codegraph/codegraph-exclude-integrity.js +24 -3
  24. package/dist/services/codegraph/codegraph-exclude-reconciler.d.ts +23 -2
  25. package/dist/services/codegraph/codegraph-exclude-reconciler.js +123 -12
  26. package/dist/services/codegraph/codegraph-exclude-repair.d.ts +109 -55
  27. package/dist/services/codegraph/codegraph-exclude-repair.js +249 -195
  28. package/dist/services/codegraph/codegraph-include-reconciler.d.ts +10 -0
  29. package/dist/services/codegraph/codegraph-include-reconciler.js +160 -0
  30. package/dist/services/codegraph/codegraph-index-integrity.d.ts +268 -0
  31. package/dist/services/codegraph/codegraph-index-integrity.js +471 -0
  32. package/dist/services/codegraph/codegraph-service.d.ts +54 -0
  33. package/dist/services/codegraph/codegraph-service.js +84 -1
  34. package/dist/services/dispatch/sub-agent-dispatcher.d.ts +11 -30
  35. package/dist/services/dispatch/sub-agent-dispatcher.js +5 -48
  36. package/dist/services/doctor/doctor-service/checks/codegraph-exclude-integrity.js +19 -4
  37. package/dist/services/doctor/doctor-service/checks/codegraph-index-integrity.d.ts +54 -0
  38. package/dist/services/doctor/doctor-service/checks/codegraph-index-integrity.js +151 -0
  39. package/dist/services/doctor/doctor-service/checks/l3-orphan-sessions.js +10 -10
  40. package/dist/services/doctor/doctor-service/plugin-registry.js +2 -0
  41. package/dist/services/doctor/doctor-service/types.d.ts +25 -0
  42. package/dist/services/ide/adapters/claude-code-adapter.js +0 -1
  43. package/dist/services/ide/adapters/codex-adapter.js +1 -2
  44. package/dist/services/ide/adapters/cursor-adapter.js +1 -2
  45. package/dist/services/ide/adapters/hermes-adapter.js +1 -2
  46. package/dist/services/ide/adapters/openclaw-adapter.js +1 -2
  47. package/dist/services/ide/adapters/qoder-adapter.js +1 -2
  48. package/dist/services/ide/adapters/tongyi-lingma-adapter.js +1 -2
  49. package/dist/services/ide/adapters/trae-adapter.js +1 -2
  50. package/dist/services/ide/adapters/zcode-adapter.js +0 -1
  51. package/dist/services/ide/ide-types.d.ts +0 -2
  52. package/dist/services/memory/project-memory-service/index/kind-dispatch.js +48 -13
  53. package/dist/services/memory/project-memory-service/index.d.ts +5 -3
  54. package/dist/services/memory/project-memory-service/index.js +2 -2
  55. package/dist/services/memory/project-memory-service/parsers/frontmatter.d.ts +15 -1
  56. package/dist/services/memory/project-memory-service/parsers/frontmatter.js +34 -6
  57. package/dist/services/memory/project-memory-service/parsers/markdown-pure.d.ts +27 -1
  58. package/dist/services/memory/project-memory-service/parsers/markdown-pure.js +92 -7
  59. package/dist/services/memory/project-memory-service/types.d.ts +86 -0
  60. package/dist/services/slice/slice-check-types.d.ts +1 -1
  61. package/dist/services/workspace/runtime-layout.d.ts +91 -0
  62. package/dist/services/workspace/runtime-layout.js +148 -0
  63. package/dist/services/workspace/workspace-claude-settings-materializer.js +14 -0
  64. package/package.json +6 -6
  65. package/scripts/clean-dist.mjs +15 -3
  66. package/scripts/sync-version.mjs +26 -4
  67. package/skills/bee/peaks-perf-audit/SKILL.md +2 -2
  68. package/skills/bee/peaks-perf-audit/references/audit-protocol.md +1 -1
  69. package/skills/bee/peaks-prd/SKILL.md +4 -4
  70. package/skills/bee/peaks-prd/references/prd-for-multi-pass.md +1 -1
  71. package/skills/bee/peaks-prd/references/workflow.md +1 -1
  72. package/skills/bee/peaks-qa/SKILL.md +6 -6
  73. package/skills/bee/peaks-qa/references/external-capability-guidance.md +1 -1
  74. package/skills/bee/peaks-qa/references/qa-fanout-contract.md +1 -1
  75. package/skills/bee/peaks-qa/references/qa-skill-presence.md +1 -1
  76. package/skills/bee/peaks-qa/references/reading-handoff-frontmatter.md +2 -2
  77. package/skills/bee/peaks-rd/SKILL.md +2 -2
  78. package/skills/bee/peaks-rd/references/code-reviewer-4dim-hint.md +1 -1
  79. package/skills/bee/peaks-rd/references/external-references.md +1 -1
  80. package/skills/bee/peaks-rd/references/mandatory-perf-baseline.md +1 -1
  81. package/skills/bee/peaks-rd/references/ocr-multilang-1.8.md +2 -2
  82. package/skills/bee/peaks-rd/references/parallel-review-fanout.md +2 -2
  83. package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +11 -8
  84. package/skills/bee/peaks-rd/references/rd-runbook.md +1 -1
  85. package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +7 -7
  86. package/skills/bee/peaks-rd/references/rd-transition-gates.md +1 -1
  87. package/skills/bee/peaks-rd/references/reading-v2-slice-results.md +1 -1
  88. package/skills/bee/peaks-rd/references/skill-presence-and-title.md +1 -1
  89. package/skills/bee/peaks-rd/references/v2-12-fanout-collapse.md +7 -5
  90. package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +3 -3
  91. package/skills/bee/peaks-reviewer/SKILL.md +1 -1
  92. package/skills/bee/peaks-sc/SKILL.md +1 -1
  93. package/skills/bee/peaks-security-audit/SKILL.md +3 -3
  94. package/skills/bee/peaks-security-audit/references/audit-protocol.md +1 -1
  95. package/skills/bee/peaks-txt/SKILL.md +3 -3
  96. package/skills/bee/peaks-txt/references/context-capsule.md +1 -1
  97. package/skills/bee/peaks-ui/SKILL.md +1 -1
  98. package/skills/peaks-audit/SKILL.md +1 -1
  99. package/skills/peaks-code/SKILL.md +9 -9
  100. package/skills/peaks-code/references/context-governance.md +1 -1
  101. package/skills/peaks-code/references/dag-orchestrator.md +3 -4
  102. package/skills/peaks-code/references/external-references.md +1 -1
  103. package/skills/peaks-code/references/external-skill-invocation.md +2 -2
  104. package/skills/peaks-code/references/fanout-mandatory.md +3 -3
  105. package/skills/peaks-code/references/frontend-only-mode.md +2 -2
  106. package/skills/peaks-code/references/gstack-integration.md +1 -1
  107. package/skills/peaks-code/references/micro-cycle.md +1 -1
  108. package/skills/peaks-code/references/periodic-checkpoint.md +2 -2
  109. package/skills/peaks-code/references/project-memory-loading.md +19 -1
  110. package/skills/peaks-code/references/project-scan-checklist.md +1 -1
  111. package/skills/peaks-code/references/resume-detection.md +1 -1
  112. package/skills/peaks-code/references/runbook.md +3 -3
  113. package/skills/peaks-code/references/session-overload-signal-index.md +2 -2
  114. package/skills/peaks-code/references/startup-sequence.md +16 -16
  115. package/skills/peaks-code/references/step-11-memory-sediment.md +3 -3
  116. package/skills/peaks-code/references/sub-agent-dispatch.md +7 -6
  117. package/skills/peaks-code/references/swarm-dispatch-contract.md +1 -1
  118. package/skills/peaks-code/references/workflow-gates-and-types.md +3 -3
  119. package/skills/peaks-code/references/worktree-governance.md +1 -1
  120. package/skills/peaks-final-review/SKILL.md +3 -3
  121. package/skills/peaks-ide/references/audit-log-helper.md +5 -4
  122. package/skills/peaks-resume/SKILL.md +1 -1
  123. package/skills/peaks-slice-decompose/SKILL.md +4 -4
  124. package/skills/peaks-slice-decompose/references/cross-pass-edge-interpretation.md +1 -1
  125. package/skills/peaks-slice-decompose/references/granularity-decision.md +1 -1
  126. package/skills/peaks-slice-decompose/references/v2-schema.md +2 -2
  127. package/skills/peaks-solo/SKILL.md +1 -2
@@ -139,22 +139,13 @@ export declare const claudeCodeSubAgentDispatcher: SubAgentDispatcher;
139
139
  * stays the same; only the per-IDE wiring breaks (intentionally).
140
140
  */
141
141
  export declare const traeSubAgentDispatcher: SubAgentDispatcher;
142
- /**
143
- * Trae-CN dispatcher. Mirrors Trae's shape with a separate label so
144
- * the CLI's IDE detection can distinguish a Trae install region
145
- * (Trae-CN differs in skill install path / log location only;
146
- * dispatch surface is identical per slice #011 framework rule).
147
- *
148
- * Slice 1.3: real `awaitBatch` — same polling core as Trae.
149
- */
150
- export declare const traeCnSubAgentDispatcher: SubAgentDispatcher;
151
142
  /**
152
143
  * Codex (OpenAI CLI IDE) dispatcher.
153
144
  *
154
145
  * Slice #13 noted Codex's sub-agent tool name is TBD; per slice #009
155
146
  * rationale, the dispatcher mirrors Claude Code's shape so the
156
- * adapter contract stays uniform. Slice 1.3 promotes Codex from
157
- * `awaitByLlmFallback` to a real `awaitBatch` (file-based polling,
147
+ * adapter contract stays uniform. Slice 1.3 promotes Codex from the
148
+ * 1.2 marker to a real `awaitBatch` (file-based polling,
158
149
  * Codex default 45s — Codex's documented heartbeat is slightly
159
150
  * slower per slice #13 R-3).
160
151
  */
@@ -201,36 +192,26 @@ export declare class SubAgentNotSupportedError extends Error {
201
192
  */
202
193
  export declare function awaitClaudeCodeBatch(input: SubAgentAwaitBatchInput): Promise<readonly SubAgentBatchResult[]>;
203
194
  /**
204
- * Slice 1.3 — shared per-IDE polling core for trae / trae-cn / codex /
205
- * cursor. Same polling loop shape as `awaitClaudeCodeBatch`, with
206
- * per-IDE default timeout + note prefix. The 4 IDEs differ only in
207
- * (a) `defaultTimeoutMs` (Trae / Trae-CN / Cursor = 30s, Codex = 45s
208
- * per slice #13 R-3) and (b) the `note` label surfaced when an IDE
209
- * times out (so 1.4 dogfood can attribute a timeout to the right
210
- * IDE).
195
+ * Slice 1.3 — shared per-IDE polling core for trae / codex / cursor.
196
+ * Same polling loop shape as `awaitClaudeCodeBatch`, with per-IDE
197
+ * default timeout + note prefix. The 3 IDEs differ only in
198
+ * (a) `defaultTimeoutMs` (Trae / Cursor = 30s, Codex = 45s per slice
199
+ * #13 R-3) and (b) the `note` label surfaced when an IDE times out
200
+ * (so 1.4 dogfood can attribute a timeout to the right IDE).
211
201
  *
212
- * MVP rationale (per Karpathy §2 Simplicity First): the 4 IDEs
202
+ * MVP rationale (per Karpathy §2 Simplicity First): the 3 IDEs
213
203
  * currently share the same file-based polling transport. The only
214
204
  * per-IDE distinction is the timeout + label. Future per-IDE
215
205
  * divergence (real IPC / shell hooks) is a 1.4 dogfood concern —
216
206
  * here we keep the dispatcher interface uniform while each IDE's
217
- * `awaitBatch` is now a real implementation rather than the 1.2
218
- * `awaitByLlmFallback` marker.
207
+ * `awaitBatch` is a real implementation.
219
208
  */
220
209
  export interface PollDispatchRecordsOptions {
221
- readonly ide: 'trae' | 'trae-cn' | 'codex' | 'cursor';
210
+ readonly ide: 'trae' | 'codex' | 'cursor';
222
211
  readonly defaultTimeoutMs: number;
223
212
  readonly notePrefix: string;
224
213
  }
225
214
  export declare function pollDispatchRecords(input: SubAgentAwaitBatchInput, opts: PollDispatchRecordsOptions): Promise<readonly SubAgentBatchResult[]>;
226
- /**
227
- * 1.2 fallback for trae / trae-cn / codex / cursor. Deprecated by
228
- * slice 1.3 — kept exported for legacy callers + back-compat tests
229
- * (the 1.2 marker is still a valid envelope shape; the 1.4 dogfood
230
- * tests can compare the marker note vs the 1.3 real note to verify
231
- * per-IDE attribution).
232
- */
233
- export declare function awaitByLlmFallback(input: SubAgentAwaitBatchInput, ide: string): Promise<readonly SubAgentBatchResult[]>;
234
215
  /**
235
216
  * In-process awaiter registration hook (used by `peaks sub-agent dispatch`
236
217
  * to attach a resolver; not part of the dispatcher public surface).
@@ -106,38 +106,13 @@ export const traeSubAgentDispatcher = {
106
106
  notePrefix: 'trae 1.3 real awaitBatch'
107
107
  })
108
108
  };
109
- /**
110
- * Trae-CN dispatcher. Mirrors Trae's shape with a separate label so
111
- * the CLI's IDE detection can distinguish a Trae install region
112
- * (Trae-CN differs in skill install path / log location only;
113
- * dispatch surface is identical per slice #011 framework rule).
114
- *
115
- * Slice 1.3: real `awaitBatch` — same polling core as Trae.
116
- */
117
- export const traeCnSubAgentDispatcher = {
118
- label: 'trae-cn',
119
- supportsRole: (role) => role.length > 0,
120
- buildToolCall: ({ role, prompt, requestId }) => ({
121
- name: 'Task',
122
- args: {
123
- subagent_type: 'general-purpose',
124
- description: `${role} for rid=${requestId}`,
125
- prompt,
126
- },
127
- }),
128
- awaitBatch: async (input) => pollDispatchRecords(input, {
129
- ide: 'trae-cn',
130
- defaultTimeoutMs: 30_000,
131
- notePrefix: 'trae-cn 1.3 real awaitBatch'
132
- })
133
- };
134
109
  /**
135
110
  * Codex (OpenAI CLI IDE) dispatcher.
136
111
  *
137
112
  * Slice #13 noted Codex's sub-agent tool name is TBD; per slice #009
138
113
  * rationale, the dispatcher mirrors Claude Code's shape so the
139
- * adapter contract stays uniform. Slice 1.3 promotes Codex from
140
- * `awaitByLlmFallback` to a real `awaitBatch` (file-based polling,
114
+ * adapter contract stays uniform. Slice 1.3 promotes Codex from the
115
+ * 1.2 marker to a real `awaitBatch` (file-based polling,
141
116
  * Codex default 45s — Codex's documented heartbeat is slightly
142
117
  * slower per slice #13 R-3).
143
118
  */
@@ -245,14 +220,14 @@ export async function awaitClaudeCodeBatch(input) {
245
220
  // service. The back-compat envelope shape is preserved (one
246
221
  // `SubAgentBatchResult` per record path) so the S3 characterization
247
222
  // test stays green; the underlying loop is identical to the trae /
248
- // trae-cn / codex / cursor wrappers below. The new typed outcome
223
+ // codex / cursor wrappers below. The new typed outcome
249
224
  // lives on the unified service; the S4 fail-fast test pins it.
250
225
  //
251
226
  // Slice 2026-07-30-nightshift: claude-code does NOT use a
252
227
  // per-IDE note prefix. The 1.4 dogfood contract says the done
253
228
  // note is `null` (raw outcome) and the failed note is the raw
254
- // `outcome` string with no prefix. The 4 non-Claude IDEs
255
- // (trae / trae-cn / codex / cursor) prefix the note with their
229
+ // `outcome` string with no prefix. The 3 non-Claude IDEs
230
+ // (trae / codex / cursor) prefix the note with their
256
231
  // per-IDE label so cross-IDE attribution is visible to the LLM.
257
232
  // Passing no `notePrefix` here keeps the legacy contract.
258
233
  const unified = await awaitBatchUnified(input.dispatchCount, input.recordPaths, input.timeoutMs, { defaultTimeoutMs: 60_000 });
@@ -299,24 +274,6 @@ export async function pollDispatchRecords(input, opts) {
299
274
  });
300
275
  return unified.results;
301
276
  }
302
- /**
303
- * 1.2 fallback for trae / trae-cn / codex / cursor. Deprecated by
304
- * slice 1.3 — kept exported for legacy callers + back-compat tests
305
- * (the 1.2 marker is still a valid envelope shape; the 1.4 dogfood
306
- * tests can compare the marker note vs the 1.3 real note to verify
307
- * per-IDE attribution).
308
- */
309
- export async function awaitByLlmFallback(input, ide) {
310
- const startedAt = Date.now();
311
- return input.recordPaths.map((p, i) => ({
312
- dispatchIndex: i,
313
- recordPath: p,
314
- status: 'timeout',
315
- durationMs: 0,
316
- note: `awaitByLlm: ${ide} 1.2 fallback (real impl in 1.3)`
317
- }));
318
- void startedAt;
319
- }
320
277
  /**
321
278
  * In-process awaiter registration hook (used by `peaks sub-agent dispatch`
322
279
  * to attach a resolver; not part of the dispatcher public surface).
@@ -30,8 +30,23 @@ import { inspectCodegraphExcludeIntegrity, isCodegraphExcludeConfigPresent } fro
30
30
  const CHECK_ID = 'capability:codegraph-exclude-integrity';
31
31
  /** How many rules / offending files the message names before eliding. */
32
32
  const MAX_NAMED = 5;
33
- function defaultProbe() {
34
- const projectRoot = process.cwd();
33
+ /**
34
+ * 2026-09-17 — `projectRoot` is the doctor's resolved L3 root, NOT
35
+ * `process.cwd()`. Same defect as the sibling `codegraph-index-integrity`
36
+ * probe (see its note): with `projectRootResolver` injected, every other
37
+ * check inspected the caller's root while this one inspected the operator's
38
+ * checkout, so its verdict depended on which repository you ran the doctor
39
+ * in. This one is read-only — it reads `.codegraph/config.json` and
40
+ * `git ls-files`, never the sqlite index — so unlike its sibling it did not
41
+ * also materialise `-shm`/`-wal` sidecars; the wrong-root defect was the
42
+ * same either way.
43
+ */
44
+ function defaultProbe(projectRoot) {
45
+ // An unresolved root is not a project — see the sibling probe's note: a
46
+ // relative lookup here would resolve against `cwd` and inspect the
47
+ // operator's checkout.
48
+ if (projectRoot.length === 0)
49
+ return null;
35
50
  // No config → codegraph was never initialized here, so no exclude
36
51
  // list is in play and there is nothing to report.
37
52
  return isCodegraphExcludeConfigPresent(projectRoot)
@@ -48,8 +63,8 @@ function renderGapMessage(excludedTrackedCount, trackedSourceCount, rulesToRemov
48
63
  const elidedFiles = violations.length > MAX_NAMED ? `; … (+${violations.length - MAX_NAMED})` : '';
49
64
  return `codegraph index is incomplete: ${excludedTrackedCount} of ${trackedSourceCount} tracked source files are blocked by ${rulesToRemove.length} exclude rule(s) [${namedRules}${elidedRules}]. Blocked: ${namedFiles}${elidedFiles}. Run \`peaks codegraph repair-exclude --project <root>\` to drop them and rebuild the index.`;
50
65
  }
51
- function run({ options }) {
52
- const probe = options.codegraphIntegrityProbe ?? defaultProbe;
66
+ function run({ options, resolvedL3Root }) {
67
+ const probe = options.codegraphIntegrityProbe ?? (() => defaultProbe(resolvedL3Root));
53
68
  let report;
54
69
  try {
55
70
  report = probe();
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Check: codegraph index integrity (`capability:codegraph-index-integrity`).
3
+ *
4
+ * The third codegraph check, and the one that stops `[OK]` from being a
5
+ * self-consistency assertion:
6
+ * - `capability:codegraph` answers "is upstream resolvable";
7
+ * - `capability:codegraph-exclude-integrity` answers "do the `exclude`
8
+ * rules drop tracked files";
9
+ * - this one answers "does the INDEX ITSELF cover the repository" —
10
+ * files the extractor supports that `include` never admitted, and rows
11
+ * the index still holds for paths that are gone.
12
+ *
13
+ * Why the exclude gate structurally cannot answer it: that gate runs the
14
+ * `include` filter FIRST and reconciles `exclude` against the survivors, so
15
+ * a file dropped by `include` is not "a tracked source file" as far as it is
16
+ * concerned; and it never reads the index at all, so dead rows are invisible
17
+ * to it. Both defects were present while `peaks codegraph status` printed
18
+ * `[OK] Index is up to date`.
19
+ *
20
+ * Read-only by construction — it consumes the same read-only inspector
21
+ * `peaks codegraph status` gates on. It never writes `.codegraph/config.json`
22
+ * and never invokes the upstream binary.
23
+ *
24
+ * Failure posture:
25
+ * - codegraph not initialized in the inspected root (no
26
+ * `.codegraph/codegraph.db`) → `ok: true`; there is no index to be
27
+ * incomplete or stale, and a fresh clone must not fail the doctor.
28
+ * - a confirmed gap on either axis → `ok: false`. ADVISORY by default
29
+ * (`severity: 'warning'`, the doctor exit code is left alone), and
30
+ * blocking (no severity tag) when the project opts in with
31
+ * `PEAKS_CODEGRAPH_INDEX_STRICT=1` — the user's option C decision.
32
+ * The finding is `ok: false` either way; only its severity moves.
33
+ * - could not evaluate (not a git work tree, missing/malformed config,
34
+ * unreadable index) → `ok: false, severity: 'warning'` so the doctor
35
+ * reports the blind spot without flipping the exit code on an
36
+ * unrelated failure. Kept non-blocking in BOTH modes: "could not
37
+ * evaluate" is not a finding about the project, and it is
38
+ * distinguishable from the `ok: true` "covers the repository"
39
+ * verdict by its `ok` value.
40
+ *
41
+ * R12-2 — the partial collapse that remains, stated rather than implied:
42
+ * this branch and the advisory `gap` branch below BOTH emit `ok: false`
43
+ * with `severity: 'warning'`, and `DoctorCheck` has no third field, so in
44
+ * the doctor envelope they are separated by message text alone. Not fixed,
45
+ * deliberately: the shape is legacy (`runDoctor` compatibility is a
46
+ * stated constraint), neither state is `ok: true` — so the invariant that
47
+ * a non-measurement may never read as "verified clean" holds — and in the
48
+ * mode where they are collapsed neither state moves the exit code.
49
+ * Under `PEAKS_CODEGRAPH_INDEX_STRICT=1` they ARE separated in the
50
+ * machine fields: the advisory `gap` branch drops its tag, this one keeps
51
+ * it. Pinned by the "(severity policy)" and "(unevaluable)" tests.
52
+ */
53
+ import type { DoctorCheckPlugin } from '../types.js';
54
+ export declare const check: DoctorCheckPlugin;
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Check: codegraph index integrity (`capability:codegraph-index-integrity`).
3
+ *
4
+ * The third codegraph check, and the one that stops `[OK]` from being a
5
+ * self-consistency assertion:
6
+ * - `capability:codegraph` answers "is upstream resolvable";
7
+ * - `capability:codegraph-exclude-integrity` answers "do the `exclude`
8
+ * rules drop tracked files";
9
+ * - this one answers "does the INDEX ITSELF cover the repository" —
10
+ * files the extractor supports that `include` never admitted, and rows
11
+ * the index still holds for paths that are gone.
12
+ *
13
+ * Why the exclude gate structurally cannot answer it: that gate runs the
14
+ * `include` filter FIRST and reconciles `exclude` against the survivors, so
15
+ * a file dropped by `include` is not "a tracked source file" as far as it is
16
+ * concerned; and it never reads the index at all, so dead rows are invisible
17
+ * to it. Both defects were present while `peaks codegraph status` printed
18
+ * `[OK] Index is up to date`.
19
+ *
20
+ * Read-only by construction — it consumes the same read-only inspector
21
+ * `peaks codegraph status` gates on. It never writes `.codegraph/config.json`
22
+ * and never invokes the upstream binary.
23
+ *
24
+ * Failure posture:
25
+ * - codegraph not initialized in the inspected root (no
26
+ * `.codegraph/codegraph.db`) → `ok: true`; there is no index to be
27
+ * incomplete or stale, and a fresh clone must not fail the doctor.
28
+ * - a confirmed gap on either axis → `ok: false`. ADVISORY by default
29
+ * (`severity: 'warning'`, the doctor exit code is left alone), and
30
+ * blocking (no severity tag) when the project opts in with
31
+ * `PEAKS_CODEGRAPH_INDEX_STRICT=1` — the user's option C decision.
32
+ * The finding is `ok: false` either way; only its severity moves.
33
+ * - could not evaluate (not a git work tree, missing/malformed config,
34
+ * unreadable index) → `ok: false, severity: 'warning'` so the doctor
35
+ * reports the blind spot without flipping the exit code on an
36
+ * unrelated failure. Kept non-blocking in BOTH modes: "could not
37
+ * evaluate" is not a finding about the project, and it is
38
+ * distinguishable from the `ok: true` "covers the repository"
39
+ * verdict by its `ok` value.
40
+ *
41
+ * R12-2 — the partial collapse that remains, stated rather than implied:
42
+ * this branch and the advisory `gap` branch below BOTH emit `ok: false`
43
+ * with `severity: 'warning'`, and `DoctorCheck` has no third field, so in
44
+ * the doctor envelope they are separated by message text alone. Not fixed,
45
+ * deliberately: the shape is legacy (`runDoctor` compatibility is a
46
+ * stated constraint), neither state is `ok: true` — so the invariant that
47
+ * a non-measurement may never read as "verified clean" holds — and in the
48
+ * mode where they are collapsed neither state moves the exit code.
49
+ * Under `PEAKS_CODEGRAPH_INDEX_STRICT=1` they ARE separated in the
50
+ * machine fields: the advisory `gap` branch drops its tag, this one keeps
51
+ * it. Pinned by the "(severity policy)" and "(unevaluable)" tests.
52
+ */
53
+ import { getErrorMessage } from 'peaks-loop-shared/result';
54
+ import { isCodegraphInitialized } from '../../../codegraph/codegraph-service.js';
55
+ import { CODEGRAPH_INDEX_STRICT_ENV_VAR, CODEGRAPH_REPAIR_INDEX_COMMAND, inspectCodegraphIndexIntegrity, isCodegraphIndexStrictMode } from '../../../codegraph/codegraph-index-integrity.js';
56
+ const CHECK_ID = 'capability:codegraph-index-integrity';
57
+ /** How many offending paths the message names per axis before eliding. */
58
+ const MAX_NAMED = 5;
59
+ function elide(paths) {
60
+ const named = paths.slice(0, MAX_NAMED).join(', ');
61
+ return paths.length > MAX_NAMED ? `${named}, … (+${paths.length - MAX_NAMED})` : named;
62
+ }
63
+ /**
64
+ * 2026-09-17 — `projectRoot` is the doctor's resolved L3 root, NOT
65
+ * `process.cwd()`. It used to read `process.cwd()`, which is a different
66
+ * thing from the root the rest of the doctor was pointed at: a caller that
67
+ * injects `projectRootResolver` (e.g.
68
+ * `tests/unit/doctor/doctor-exit-code-warn-only.test.ts`) got every other
69
+ * check aimed at its temp root while THIS one opened the operator's real
70
+ * `.codegraph/codegraph.db`. Opening that database materialises its
71
+ * `-shm`/`-wal` sidecars, so a unit run mutated the operator's checkout and
72
+ * the check's result depended on which repository you happened to run it
73
+ * in. Every other L3 check reads `resolvedL3Root` from `DoctorContext`;
74
+ * this one now does too.
75
+ */
76
+ function defaultProbe(projectRoot) {
77
+ // An unresolved root is not a project. It must NOT fall through to a
78
+ // relative lookup (`join('', '.codegraph', …)` resolves against `cwd`),
79
+ // because that fallback IS the defect this signature was changed to close:
80
+ // a caller that never resolved a root would silently inspect the operator's
81
+ // checkout. `runDoctor` always resolves one; only a hand-built test context
82
+ // can pass ''.
83
+ if (projectRoot.length === 0)
84
+ return null;
85
+ // No index → nothing to be incomplete or stale.
86
+ return isCodegraphInitialized(projectRoot) ? inspectCodegraphIndexIntegrity(projectRoot) : null;
87
+ }
88
+ function renderGapMessage(includeGap, admittedTrackedCount, trackedSourceCount, deadRows, indexedFileCount) {
89
+ const parts = [];
90
+ if (includeGap.length > 0) {
91
+ parts.push(`${includeGap.length} of ${trackedSourceCount} extractor-supported tracked file(s) are not admitted by the config's include globs (admitted: ${admittedTrackedCount}) [${elide(includeGap)}]`);
92
+ }
93
+ if (deadRows.length > 0) {
94
+ parts.push(`${deadRows.length} of ${indexedFileCount} indexed file(s) are gone from disk [${elide(deadRows)}]`);
95
+ }
96
+ return `codegraph index does not cover the repository: ${parts.join('; ')}.`;
97
+ }
98
+ // The remediation clause, in both modes. Naming the command is the
99
+ // slice-002 half of slice-001's design decision 4 (which deliberately named
100
+ // none while none existed); the command name is a shared constant, so this
101
+ // message cannot drift from the CLI surface it points at.
102
+ function remediation() {
103
+ return `Run \`${CODEGRAPH_REPAIR_INDEX_COMMAND}\` to add the missing include pattern(s) and rebuild the index without the stale rows.`;
104
+ }
105
+ function run({ options, resolvedL3Root }) {
106
+ const probe = options.codegraphIndexIntegrityProbe ?? (() => defaultProbe(resolvedL3Root));
107
+ const strict = isCodegraphIndexStrictMode();
108
+ let report;
109
+ try {
110
+ report = probe();
111
+ }
112
+ catch (error) {
113
+ return [{
114
+ id: CHECK_ID,
115
+ ok: false,
116
+ severity: 'warning',
117
+ message: `codegraph index integrity could not be evaluated: ${getErrorMessage(error)}`
118
+ }];
119
+ }
120
+ if (report === null) {
121
+ return [{
122
+ id: CHECK_ID,
123
+ ok: true,
124
+ message: 'codegraph is not initialized in this project (no .codegraph/codegraph.db); there is no index to be incomplete or stale'
125
+ }];
126
+ }
127
+ if (!report.gap) {
128
+ return [{
129
+ id: CHECK_ID,
130
+ ok: true,
131
+ message: `codegraph index covers the repository (${report.admittedTrackedCount} extractor-supported tracked file(s) admitted, ${report.indexedFileCount} indexed row(s), none stale)`
132
+ }];
133
+ }
134
+ const gapMessage = renderGapMessage(report.includeGap, report.admittedTrackedCount, report.trackedSourceCount, report.deadRows, report.indexedFileCount);
135
+ // Option C: advisory by default, blocking on opt-in. `ok: false` in both
136
+ // modes — the finding is reported either way; only `severity` (and with
137
+ // it the doctor exit code) moves. The advisory suffix is the discovery
138
+ // path for the switch, so an operator who wants blocking is told how.
139
+ return strict
140
+ ? [{ id: CHECK_ID, ok: false, message: `${gapMessage} ${remediation()}` }]
141
+ : [{
142
+ id: CHECK_ID,
143
+ ok: false,
144
+ severity: 'warning',
145
+ message: `${gapMessage} ${remediation()} Advisory: set ${CODEGRAPH_INDEX_STRICT_ENV_VAR}=1 to make this blocking.`
146
+ }];
147
+ }
148
+ export const check = {
149
+ name: 'codegraph-index-integrity',
150
+ run
151
+ };
@@ -18,16 +18,16 @@
18
18
  import { existsSync, readdirSync } from 'node:fs';
19
19
  import { join } from 'node:path';
20
20
  import { getErrorMessage } from 'peaks-loop-shared/result';
21
- /**
22
- * Canonical system subdirs that intentionally live under
23
- * `.peaks/_runtime/` and must NOT be flagged as orphan sessions.
24
- *
25
- * `change/` is the routing target for change-id reviewable
26
- * artifacts per F3 audit-p1. Adding a new entry here requires
27
- * also updating `RUNTIME_SYSTEM_SUBDIRS_DOC` in the comments
28
- * below so the next maintainer knows why each entry is listed.
29
- */
30
- const RUNTIME_SYSTEM_SUBDIRS = new Set(['change']);
21
+ import { RUNTIME_SYSTEM_SUBDIRS } from '../../../workspace/runtime-layout.js';
22
+ // The exclude-list moved to `src/services/workspace/runtime-layout.ts`.
23
+ // It is imported above, not re-declared here: the local literal
24
+ // (`new Set(['change'])`) had already drifted — `callers/` is a designed
25
+ // location written by `caller-binding-service.ts` and was never added, so
26
+ // this check reported `4 orphan session(s) …: callers, cli, unknown-sid, x`
27
+ // and `peaks doctor` exited 1 on a clean workspace, permanently.
28
+ // `tests/unit/workspace/runtime-layout-drift-guard.test.ts` now fails when
29
+ // the code writes a `.peaks/_runtime/` child the registry does not know, so
30
+ // the set can no longer drift silently.
31
31
  function run({ resolvedL3Root, isValidSessionId }) {
32
32
  try {
33
33
  const runtimeDir = join(resolvedL3Root, '.peaks/_runtime');
@@ -43,6 +43,7 @@ import { check as statuslineInstall } from './checks/statusline-install.js';
43
43
  import { check as statuslineRuntime } from './checks/statusline-runtime.js';
44
44
  import { check as codegraphCapability } from './checks/codegraph-capability.js';
45
45
  import { check as codegraphExcludeIntegrity } from './checks/codegraph-exclude-integrity.js';
46
+ import { check as codegraphIndexIntegrity } from './checks/codegraph-index-integrity.js';
46
47
  import { check as distSourceVersion } from './checks/dist-source-version.js';
47
48
  import { check as multiBinaryDrift } from './checks/multi-binary-drift.js';
48
49
  import { check as workspaceLayout } from './checks/workspace-layout.js';
@@ -72,6 +73,7 @@ export const PLUGINS = [
72
73
  statuslineRuntime, // id "statusline:runtime"
73
74
  codegraphCapability, // id "capability:codegraph"
74
75
  codegraphExcludeIntegrity, // id "capability:codegraph-exclude-integrity"
76
+ codegraphIndexIntegrity, // id "capability:codegraph-index-integrity"
75
77
  distSourceVersion, // id "build:dist-version-matches-source"
76
78
  multiBinaryDrift, // id "build:multi-binary-drift"
77
79
  workspaceLayout, // id "build:workspace-layout-canonical"
@@ -94,6 +94,23 @@ export type CodegraphExcludeIntegrityProbe = {
94
94
  readonly matchedRule: string;
95
95
  }[];
96
96
  };
97
+ /**
98
+ * Structural shape of the codegraph index-integrity report the
99
+ * `capability:codegraph-index-integrity` check gates on. Declared
100
+ * structurally (rather than imported from the codegraph service) to
101
+ * keep this type module dependency-free — the default probe returns a
102
+ * `CodegraphIndexIntegrityReport`, which is assignable here.
103
+ */
104
+ export type CodegraphIndexIntegrityProbe = {
105
+ readonly gap: boolean;
106
+ readonly trackedSourceCount: number;
107
+ readonly admittedTrackedCount: number;
108
+ /** Class ① — extractor-supported tracked files `include` does not admit. */
109
+ readonly includeGap: readonly string[];
110
+ readonly indexedFileCount: number;
111
+ /** Class ② — index rows whose path is gone from disk. */
112
+ readonly deadRows: readonly string[];
113
+ };
97
114
  export type DistVersionComparison = {
98
115
  dist: string | null;
99
116
  source: string;
@@ -256,6 +273,14 @@ export type DoctorOptions = {
256
273
  * and reported as a non-blocking warning.
257
274
  */
258
275
  codegraphIntegrityProbe?: () => CodegraphExcludeIntegrityProbe | null;
276
+ /**
277
+ * Optional override for the `capability:codegraph-index-integrity`
278
+ * check. Returns the report, or `null` when codegraph is not
279
+ * initialized in the inspected root (no index to inspect). When
280
+ * omitted, the check inspects `process.cwd()`. Throwing is allowed
281
+ * and reported as a non-blocking warning.
282
+ */
283
+ codegraphIndexIntegrityProbe?: () => CodegraphIndexIntegrityProbe | null;
259
284
  skillPresenceProbe?: () => DoctorSkillPresence | null;
260
285
  skillPresenceFreshnessThresholdMs?: number;
261
286
  statusLineInstalledProbe?: () => boolean;
@@ -591,7 +591,6 @@ export const CLAUDE_CODE_ADAPTER = {
591
591
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
592
592
  return join(root, '.claude', 'settings.json');
593
593
  },
594
- supportsScope: () => true,
595
594
  },
596
595
  envVar: 'CLAUDE_PROJECT_DIR',
597
596
  hookEvent: 'PreToolUse',
@@ -54,8 +54,7 @@ export const CODEX_ADAPTER = {
54
54
  resolveSettingsFile: (scope, projectRoot) => {
55
55
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
56
56
  return join(root, '.codex', 'settings.json');
57
- },
58
- supportsScope: (scope) => scope === 'project' || scope === 'global'
57
+ }
59
58
  },
60
59
  envVar: 'CODEX_PROJECT_DIR', // UNVERIFIED — see slice #13 PRD R-3; fallback path still works
61
60
  hookEvent: 'pre_tool_use', // UNVERIFIED — Codex lowercase snake_case pattern; re-verify against Codex docs
@@ -44,8 +44,7 @@ export const CURSOR_ADAPTER = {
44
44
  resolveSettingsFile: (scope, projectRoot) => {
45
45
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
46
46
  return join(root, '.cursor', 'settings.json');
47
- },
48
- supportsScope: (scope) => scope === 'project' || scope === 'global'
47
+ }
49
48
  },
50
49
  envVar: 'CURSOR_PROJECT_DIR', // UNVERIFIED — see slice #12 PRD R-2; fallback path still works
51
50
  hookEvent: 'beforeShellExecution', // UNVERIFIED — see slice #12 PRD R-1; re-verify against Cursor's published hook schema
@@ -29,8 +29,7 @@ export const HERMES_ADAPTER = {
29
29
  resolveSettingsFile: (scope, projectRoot) => {
30
30
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
31
31
  return join(root, '.hermes', 'settings.json');
32
- },
33
- supportsScope: (scope) => scope === 'project' || scope === 'global'
32
+ }
34
33
  },
35
34
  envVar: 'HERMES_PROJECT_DIR', // UNVERIFIED
36
35
  hookEvent: 'PreToolUse', // UNVERIFIED
@@ -22,8 +22,7 @@ export const OPENCLAW_ADAPTER = {
22
22
  resolveSettingsFile: (scope, projectRoot) => {
23
23
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
24
24
  return join(root, '.openclaw', 'settings.json');
25
- },
26
- supportsScope: (scope) => scope === 'project' || scope === 'global'
25
+ }
27
26
  },
28
27
  envVar: 'OPENCLAW_PROJECT_DIR', // UNVERIFIED
29
28
  hookEvent: 'PreToolUse', // UNVERIFIED
@@ -33,8 +33,7 @@ export const QODER_ADAPTER = {
33
33
  resolveSettingsFile: (scope, projectRoot) => {
34
34
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
35
35
  return join(root, '.qoder', 'settings.json');
36
- },
37
- supportsScope: (scope) => scope === 'project' || scope === 'global'
36
+ }
38
37
  },
39
38
  envVar: 'QODER_PROJECT_DIR', // UNVERIFIED
40
39
  hookEvent: 'PreToolUse', // UNVERIFIED
@@ -24,8 +24,7 @@ export const TONGYI_LINGMA_ADAPTER = {
24
24
  resolveSettingsFile: (scope, projectRoot) => {
25
25
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
26
26
  return join(root, '.lingma', 'settings.json');
27
- },
28
- supportsScope: (scope) => scope === 'project' || scope === 'global'
27
+ }
29
28
  },
30
29
  envVar: 'TONGYI_LINGMA_PROJECT_DIR', // UNVERIFIED
31
30
  hookEvent: 'PreToolUse', // UNVERIFIED
@@ -50,8 +50,7 @@ export const TRAE_ADAPTER = {
50
50
  resolveSettingsFile: (scope, projectRoot) => {
51
51
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
52
52
  return join(root, '.trae', 'settings.json');
53
- },
54
- supportsScope: (scope) => scope === 'project' || scope === 'global'
53
+ }
55
54
  },
56
55
  envVar: 'TRAE_PROJECT_DIR',
57
56
  hookEvent: 'beforeToolCall', // VERIFIED against Trae 1.x fixture — slice 009-009-2026-06-07-trae-dogfood (2026-06-07); fixture at tests/fixtures/trae/trae-1x-payload.json
@@ -159,7 +159,6 @@ export const ZCODE_ADAPTER = {
159
159
  const root = scope === 'global' ? homedir() : resolve(projectRoot ?? homedir());
160
160
  return join(root, '.zcode', 'settings.json');
161
161
  },
162
- supportsScope: (scope) => scope === 'project' || scope === 'global',
163
162
  },
164
163
  // UNVERIFIED — z-code 桌面应用未公开 `ZCODE_PROJECT_DIR` env var,这里
165
164
  // 用占位符,RD 阶段假设 z-code 借用了 Anthropic-compatible 协议并可能
@@ -41,8 +41,6 @@ export interface IdeSettingsLocation {
41
41
  readonly localSettingsFileName?: string;
42
42
  /** 解析出 settings.json 绝对路径 */
43
43
  resolveSettingsFile(scope: 'project' | 'global', projectRoot: string | undefined): string;
44
- /** 该 IDE 是否支持此 scope(用于清晰报错) */
45
- supportsScope(scope: 'project' | 'global'): boolean;
46
44
  }
47
45
  /**
48
46
  * Slim IDE adapter 描述。每 IDE 一个静态常量(无需 DI)。