peaks-loop 4.0.20 → 4.0.23

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.
@@ -6,49 +6,171 @@
6
6
  * Fails when the version drifts, when the binary is missing, or
7
7
  * when the package is not resolvable at all.
8
8
  *
9
+ * Slice rid-CG-003 (spike follow-up #3): also detects which managed
10
+ * codegraph directory is in use — `.peaks/.codegraph/` (preferred)
11
+ * or root `.codegraph/` (legacy) — via the injected `managedPathProbe`.
12
+ * The CG-007 yarn-pnp fallback is preserved as the package-resolution
13
+ * default; the managed-path probe defaults to the same fs-walk-style
14
+ * helper and is independently injectable for tests.
15
+ *
9
16
  * The probe is injected so tests do not depend on the real
10
17
  * `node_modules` resolution; the default probe uses
11
18
  * `createRequire(import.meta.url)` to find the package.
12
19
  */
13
- import { existsSync } from 'node:fs';
14
- import { dirname, resolve as resolvePath } from 'node:path';
20
+ import { existsSync, readFileSync, statSync } from 'node:fs';
21
+ import { dirname, join, resolve as resolvePath } from 'node:path';
15
22
  import { createRequire } from 'node:module';
16
23
  import { getErrorMessage } from 'peaks-loop-shared/result';
24
+ import { resolveCodegraphProjectRoot } from '../../../codegraph/codegraph-service.js';
17
25
  const CODEGRAPH_EXPECTED_VERSION = '0.7.10';
26
+ const CODEGRAPH_PACKAGE_NAME = '@colbymchenry/codegraph';
27
+ function findCodegraphPackageJsonFallback(startDir) {
28
+ // Slice rid-CG-007 (downstream safety): yarn-pnp / pnpm-strict /
29
+ // sub-package consumers may not expose `@colbymchenry/codegraph` to
30
+ // `createRequire(import.meta.url).resolve`. The fallback walks up
31
+ // the directory tree from `startDir` looking for
32
+ // `node_modules/@colbymchenry/codegraph/package.json`.
33
+ //
34
+ // Pinned at <=8 levels so the walk is bounded and the check never
35
+ // becomes O(repo-size) on a misconfigured consumer.
36
+ const MAX_DEPTH = 8;
37
+ let current = startDir;
38
+ for (let depth = 0; depth < MAX_DEPTH && current !== null; depth += 1) {
39
+ const candidate = join(current, 'node_modules', CODEGRAPH_PACKAGE_NAME, 'package.json');
40
+ if (existsSync(candidate)) {
41
+ return candidate;
42
+ }
43
+ const parent = dirname(current);
44
+ if (parent === current) {
45
+ break;
46
+ }
47
+ current = parent;
48
+ }
49
+ return null;
50
+ }
18
51
  function defaultCodegraphProbe() {
19
52
  const require = createRequire(import.meta.url);
20
- const packagePath = require.resolve('@colbymchenry/codegraph/package.json');
21
- const pkg = require(packagePath);
22
- const binaryPath = resolvePath(dirname(packagePath), 'dist', 'bin', 'codegraph.js');
23
- return {
53
+ let packagePath;
54
+ try {
55
+ packagePath = require.resolve(`${CODEGRAPH_PACKAGE_NAME}/package.json`);
56
+ }
57
+ catch (primaryError) {
58
+ // Fall back to an fs walk from the cwd. This covers
59
+ // yarn-pnp, pnpm-strict, and sub-package consumers whose
60
+ // require-resolve graph does not surface the package through
61
+ // `createRequire(import.meta.url)` even when the package is
62
+ // physically installed.
63
+ const fallback = findCodegraphPackageJsonFallback(process.cwd());
64
+ if (fallback === null) {
65
+ // Re-throw the original require.resolve error so the caller
66
+ // sees the canonical failure message — the fallback path is
67
+ // best-effort, not a replacement.
68
+ throw primaryError;
69
+ }
70
+ packagePath = fallback;
71
+ }
72
+ let version = 'unknown';
73
+ try {
74
+ const pkgRaw = readFileSync(packagePath, 'utf8');
75
+ const parsed = JSON.parse(pkgRaw);
76
+ version = parsed.version ?? 'unknown';
77
+ }
78
+ catch {
79
+ // Fall through with version='unknown'; the binary-existence
80
+ // check below is the load-bearing assertion.
81
+ }
82
+ let binaryPath;
83
+ try {
84
+ binaryPath = resolvePath(dirname(packagePath), 'dist', 'bin', 'codegraph.js');
85
+ }
86
+ catch {
87
+ binaryPath = '';
88
+ }
89
+ let binaryExists = false;
90
+ if (binaryPath.length > 0) {
91
+ try {
92
+ binaryExists = statSync(binaryPath).isFile();
93
+ }
94
+ catch {
95
+ binaryExists = false;
96
+ }
97
+ }
98
+ const result = {
24
99
  packagePath,
25
- version: pkg.version ?? 'unknown',
100
+ version,
26
101
  binaryPath,
27
- binaryExists: existsSync(binaryPath)
102
+ binaryExists,
103
+ // Slice rid-CG-003 — resolve the managed codegraph directory
104
+ // relative to the cwd the doctor itself was invoked from.
105
+ // Best-effort: when the operator ran `peaks doctor` outside a
106
+ // project (cwd has neither `.peaks/.codegraph/` nor
107
+ // `.codegraph/`) we still report `fresh-preferred` so the check
108
+ // message names the canonical future location.
109
+ managedPath: detectManagedCodegraphPath(process.cwd())
28
110
  };
111
+ return result;
112
+ }
113
+ /**
114
+ * Slice rid-CG-003 — pure wrapper over `resolveCodegraphProjectRoot`
115
+ * that returns a probe-shaped managed-path payload (or null when
116
+ * the cwd does not look like a peaks project root).
117
+ */
118
+ function detectManagedCodegraphPath(cwd) {
119
+ try {
120
+ const location = resolveCodegraphProjectRoot(cwd);
121
+ return {
122
+ source: location.source,
123
+ codegraphDir: location.codegraphDir,
124
+ cwd: location.cwd
125
+ };
126
+ }
127
+ catch {
128
+ return null;
129
+ }
29
130
  }
30
- function runCheck(probe) {
131
+ function renderManagedPathSuffix(managedPath) {
132
+ if (!managedPath) {
133
+ return '';
134
+ }
135
+ if (managedPath.source === 'preferred') {
136
+ return `; managed path: ${managedPath.codegraphDir} (preferred .peaks/.codegraph/)`;
137
+ }
138
+ if (managedPath.source === 'legacy') {
139
+ return `; managed path: ${managedPath.codegraphDir} (legacy root .codegraph/ — consider moving to .peaks/.codegraph/)`;
140
+ }
141
+ return `; managed path: ${managedPath.codegraphDir} (fresh-preferred — no codegraph dir yet; next init will create it)`;
142
+ }
143
+ function runCheck(probe, managedPathProbe) {
31
144
  try {
32
145
  const result = probe();
146
+ const managedPath = managedPathProbe();
33
147
  const versionOk = result.version === CODEGRAPH_EXPECTED_VERSION;
148
+ const managedPathSuffix = renderManagedPathSuffix(managedPath);
34
149
  if (!versionOk) {
150
+ // rid-CG-007: downstream consumers may pull a different
151
+ // version via yarn-pnp / pnpm-strict. Surface as a warning
152
+ // (ok: false, severity: warning) so the check does NOT flip
153
+ // the doctor exit code. Upstream 0.7.x binaries are wire-
154
+ // compatible with 0.7.10 for the subset peaks-loop exercises
155
+ // (status / init / index / query / files / context / affected).
35
156
  return [{
36
157
  id: 'capability:codegraph',
37
158
  ok: false,
38
- message: `@colbymchenry/codegraph version mismatch: expected ${CODEGRAPH_EXPECTED_VERSION}, resolved ${result.version} at ${result.packagePath}`
159
+ severity: 'warning',
160
+ message: `@colbymchenry/codegraph version drift: expected ${CODEGRAPH_EXPECTED_VERSION}, resolved ${result.version} at ${result.packagePath} — peaks-loop uses an allow-list of subcommands and tolerates 0.7.x wire-compat. Run \`pnpm install @colbymchenry/codegraph@${CODEGRAPH_EXPECTED_VERSION}\` to pin.${managedPathSuffix}`
39
161
  }];
40
162
  }
41
163
  if (!result.binaryExists) {
42
164
  return [{
43
165
  id: 'capability:codegraph',
44
166
  ok: false,
45
- message: `@colbymchenry/codegraph@${result.version} resolved at ${result.packagePath} but binary is missing at ${result.binaryPath}`
167
+ message: `@colbymchenry/codegraph@${result.version} resolved at ${result.packagePath} but binary is missing at ${result.binaryPath}${managedPathSuffix}`
46
168
  }];
47
169
  }
48
170
  return [{
49
171
  id: 'capability:codegraph',
50
172
  ok: true,
51
- message: `@colbymchenry/codegraph@${result.version} resolves with binary at ${result.binaryPath}`
173
+ message: `@colbymchenry/codegraph@${result.version} resolves with binary at ${result.binaryPath}${managedPathSuffix}`
52
174
  }];
53
175
  }
54
176
  catch (error) {
@@ -61,7 +183,11 @@ function runCheck(probe) {
61
183
  }
62
184
  function run({ options }) {
63
185
  const probe = options.codegraphProbe ?? defaultCodegraphProbe;
64
- return runCheck(probe);
186
+ const managedPathProbe = options.codegraphManagedPathProbe ?? defaultCodegraphManagedPathProbe;
187
+ return runCheck(probe, managedPathProbe);
188
+ }
189
+ function defaultCodegraphManagedPathProbe() {
190
+ return detectManagedCodegraphPath(process.cwd());
65
191
  }
66
192
  export const check = {
67
193
  name: 'codegraph-capability',
@@ -56,11 +56,27 @@ export type DoctorReport = {
56
56
  warnings: number;
57
57
  };
58
58
  };
59
+ export type CodegraphManagedPathInfo = {
60
+ source: 'preferred' | 'legacy' | 'fresh-preferred';
61
+ codegraphDir: string;
62
+ cwd: string;
63
+ };
59
64
  export type CodegraphCapabilityProbe = {
60
65
  packagePath: string;
61
66
  version: string;
62
67
  binaryPath: string;
63
68
  binaryExists: boolean;
69
+ /**
70
+ * Slice rid-CG-003 — preferred-path resolution result. When the
71
+ * probe can detect `.peaks/.codegraph/` or `.codegraph/` inside
72
+ * `process.cwd()` it sets `managedPath`; otherwise null (e.g.
73
+ * when the operator invoked `peaks doctor` outside a project).
74
+ *
75
+ * `source: 'preferred'` — `.peaks/.codegraph/` exists
76
+ * `source: 'legacy'` — only `.codegraph/` exists
77
+ * `source: 'fresh-preferred'` — neither exists; defaults to preferred
78
+ */
79
+ managedPath: CodegraphManagedPathInfo | null;
64
80
  };
65
81
  export type DistVersionComparison = {
66
82
  dist: string | null;
@@ -190,6 +206,15 @@ export type DoctorOptions = {
190
206
  schemasBaseDir?: string;
191
207
  skillsBaseDir?: string;
192
208
  codegraphProbe?: () => CodegraphCapabilityProbe;
209
+ /**
210
+ * Slice rid-CG-003 — optional override for the managed-codegraph
211
+ * path detection inside the `capability:codegraph` check. When
212
+ * omitted, the check uses the default fs helper
213
+ * (`resolveCodegraphProjectRoot(process.cwd())`). Tests inject a
214
+ * custom probe to drive preferred / legacy / fresh-preferred
215
+ * outcomes without monkey-patching `process.cwd()`.
216
+ */
217
+ codegraphManagedPathProbe?: () => CodegraphManagedPathInfo | null;
193
218
  skillPresenceProbe?: () => DoctorSkillPresence | null;
194
219
  skillPresenceFreshnessThresholdMs?: number;
195
220
  statusLineInstalledProbe?: () => boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "4.0.20",
3
+ "version": "4.0.23",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "keywords": [
@@ -101,10 +101,10 @@
101
101
  "headroom-ai": "0.22.4",
102
102
  "yaml": "^2.9.0",
103
103
  "zod": "^3.25.76",
104
- "peaks-loop-internal-runtime": "0.0.2",
105
- "peaks-loop-mut": "0.1.23",
106
- "peaks-loop-shared": "0.0.51",
107
- "peaks-loop-shared-channel": "0.0.27"
104
+ "peaks-loop-internal-runtime": "0.0.8",
105
+ "peaks-loop-mut": "0.1.22",
106
+ "peaks-loop-shared": "0.0.57",
107
+ "peaks-loop-shared-channel": "0.0.26"
108
108
  },
109
109
  "devDependencies": {
110
110
  "@changesets/cli": "2.31.1",