peaks-cli 1.0.13 → 1.0.15

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 (35) hide show
  1. package/dist/src/cli/commands/core-artifact-commands.js +25 -0
  2. package/dist/src/cli/commands/project-commands.js +5 -0
  3. package/dist/src/cli/commands/request-commands.js +1 -1
  4. package/dist/src/cli/commands/workflow-commands.js +38 -0
  5. package/dist/src/services/artifacts/request-artifact-service.d.ts +2 -2
  6. package/dist/src/services/artifacts/request-artifact-service.js +60 -5
  7. package/dist/src/services/codegraph/codegraph-process-runner.d.ts +2 -0
  8. package/dist/src/services/codegraph/codegraph-process-runner.js +93 -0
  9. package/dist/src/services/codegraph/codegraph-service.js +2 -98
  10. package/dist/src/services/config/config-safety.d.ts +14 -0
  11. package/dist/src/services/config/config-safety.js +275 -0
  12. package/dist/src/services/config/config-service.d.ts +1 -1
  13. package/dist/src/services/config/config-service.js +5 -274
  14. package/dist/src/services/dashboard/project-dashboard-service.d.ts +11 -0
  15. package/dist/src/services/dashboard/project-dashboard-service.js +21 -2
  16. package/dist/src/services/doctor/doctor-service.d.ts +3 -0
  17. package/dist/src/services/doctor/doctor-service.js +58 -0
  18. package/dist/src/services/mcp/mcp-scan-service.js +1 -1
  19. package/dist/src/services/skills/skill-presence-service.d.ts +10 -0
  20. package/dist/src/services/skills/skill-presence-service.js +54 -0
  21. package/dist/src/services/skills/skill-runbook-service.js +1 -1
  22. package/dist/src/services/workflow/autonomous-resume-writer.d.ts +16 -0
  23. package/dist/src/services/workflow/autonomous-resume-writer.js +156 -0
  24. package/dist/src/services/workflow/workflow-autonomous-service.js +7 -13
  25. package/dist/src/shared/version.d.ts +1 -1
  26. package/dist/src/shared/version.js +1 -1
  27. package/package.json +1 -1
  28. package/schemas/doctor-report.schema.json +2 -2
  29. package/skills/peaks-prd/SKILL.md +12 -0
  30. package/skills/peaks-qa/SKILL.md +12 -0
  31. package/skills/peaks-rd/SKILL.md +12 -0
  32. package/skills/peaks-sc/SKILL.md +12 -0
  33. package/skills/peaks-solo/SKILL.md +14 -0
  34. package/skills/peaks-txt/SKILL.md +22 -0
  35. package/skills/peaks-ui/SKILL.md +12 -0
@@ -7,6 +7,7 @@ import { planProxyTest } from '../../services/proxy/proxy-service.js';
7
7
  import { runDoctor } from '../../services/doctor/doctor-service.js';
8
8
  import { listSkills } from '../../services/skills/skill-registry.js';
9
9
  import { inspectSkillRunbook } from '../../services/skills/skill-runbook-service.js';
10
+ import { setSkillPresence, clearSkillPresence, getSkillPresence } from '../../services/skills/skill-presence-service.js';
10
11
  import { fail, ok } from '../../shared/result.js';
11
12
  import { addJsonOption, failUnsupportedNonDryRun, getErrorMessage, isArtifactProvider, isArtifactSetupStep, printResult } from '../cli-helpers.js';
12
13
  export function registerCoreAndArtifactCommands(program, io) {
@@ -56,6 +57,30 @@ export function registerCoreAndArtifactCommands(program, io) {
56
57
  process.exitCode = 1;
57
58
  }
58
59
  });
60
+ addJsonOption(skill
61
+ .command('presence')
62
+ .description('Show the currently active Peaks skill')).action((options) => {
63
+ const presence = getSkillPresence();
64
+ if (presence === null) {
65
+ printResult(io, ok('skill.presence', { active: false }), options.json);
66
+ return;
67
+ }
68
+ printResult(io, ok('skill.presence', { active: true, ...presence }), options.json);
69
+ });
70
+ addJsonOption(skill
71
+ .command('presence:set <name>')
72
+ .description('Set the currently active Peaks skill for session-wide visibility')
73
+ .option('--mode <mode>', 'execution mode')
74
+ .option('--gate <gate>', 'current gate')).action((name, options) => {
75
+ const presence = setSkillPresence(name, options.mode, options.gate);
76
+ printResult(io, ok('skill.presence:set', { active: true, ...presence }), options.json);
77
+ });
78
+ addJsonOption(skill
79
+ .command('presence:clear')
80
+ .description('Clear the active Peaks skill presence indicator')).action((options) => {
81
+ const removed = clearSkillPresence();
82
+ printResult(io, ok('skill.presence:clear', { active: false, removed }), options.json);
83
+ });
59
84
  const profile = program.command('profile').description('Manage runtime profiles');
60
85
  addJsonOption(profile.command('list').description('List available profiles')).action((options) => {
61
86
  printResult(io, ok('profile.list', { profiles: listProfiles() }), options.json);
@@ -22,6 +22,11 @@ export function registerProjectCommands(program, io) {
22
22
  process.exitCode = 1;
23
23
  return;
24
24
  }
25
+ if (dashboard.skillPresence.active && !dashboard.skillPresence.fresh) {
26
+ printResult(io, fail('project.dashboard', 'PROJECT_DASHBOARD_STALE_SKILL_PRESENCE', `Active Peaks skill presence ${dashboard.skillPresence.skill ?? '<unknown>'} is stale (set ${dashboard.skillPresence.setAt ?? '<unknown>'})`, dashboard, ['Run `peaks skill presence:clear` if the role has ended, or `peaks skill presence:set <skill>` to refresh it']), options.json);
27
+ process.exitCode = 1;
28
+ return;
29
+ }
25
30
  if (!dashboard.doctor.ok) {
26
31
  printResult(io, fail('project.dashboard', 'PROJECT_DASHBOARD_DOCTOR_FAILED', `Doctor reports ${dashboard.doctor.failed} failed check(s) (${dashboard.doctor.passed} passed)`, dashboard, ['Run `peaks doctor --json` and resolve the failing checks before re-running the dashboard']), options.json);
27
32
  process.exitCode = 1;
@@ -2,7 +2,7 @@ import { InvalidArgumentError } from 'commander';
2
2
  import { allowedStatesForRole, createRequestArtifact, listRequestArtifacts, showRequestArtifact, transitionRequestArtifact } from '../../services/artifacts/request-artifact-service.js';
3
3
  import { fail, ok } from '../../shared/result.js';
4
4
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
5
- const VALID_ROLES = ['prd', 'ui', 'rd', 'qa'];
5
+ const VALID_ROLES = ['prd', 'ui', 'rd', 'qa', 'sc'];
6
6
  function parseRole(value) {
7
7
  if (!VALID_ROLES.includes(value)) {
8
8
  throw new InvalidArgumentError(`must be one of ${VALID_ROLES.join(', ')}`);
@@ -2,6 +2,7 @@ import { createRdSwarmPlan } from '../../services/rd/rd-service.js';
2
2
  import { createTechPlan, getTechStatus } from '../../services/tech/tech-service.js';
3
3
  import { createWorkflowRouterPlan, isSoloMode, isWorkflowMode } from '../../services/workflow/workflow-router-service.js';
4
4
  import { createAutonomousWorkflowPlan } from '../../services/workflow/workflow-autonomous-service.js';
5
+ import { writeAutonomousResumeArtifacts } from '../../services/workflow/autonomous-resume-writer.js';
5
6
  import { createRecommendationPlan } from '../../services/recommendations/recommendation-service.js';
6
7
  import { createRefactorDryRun } from '../../services/refactor/refactor-service.js';
7
8
  import { ensureWorkspaceConfigForCurrentPath, getCurrentWorkspaceConfig, readConfig } from '../../services/config/config-service.js';
@@ -196,6 +197,31 @@ function runSwarmPlan(io, options) {
196
197
  process.exitCode = 1;
197
198
  }
198
199
  }
200
+ async function runAutonomousResumeInit(io, options) {
201
+ try {
202
+ if (!options.project || !options.project.trim()) {
203
+ throw new Error('Project path must be non-empty');
204
+ }
205
+ const result = await writeAutonomousResumeArtifacts({
206
+ changeId: options.changeId,
207
+ goal: options.goal,
208
+ artifactWorkspacePath: options.project,
209
+ apply: options.apply === true
210
+ });
211
+ const data = {
212
+ applied: result.applied,
213
+ files: result.files.map((file) => file.path)
214
+ };
215
+ const nextActions = result.applied
216
+ ? ['Run peaks workflow autonomous --change-id ' + options.changeId + ' --goal "<goal>" --json to verify resumePlan.status']
217
+ : ['Re-run with --apply to write the resume scaffold to disk'];
218
+ printResult(io, ok('autonomous-resume.init', data, [], nextActions), options.json);
219
+ }
220
+ catch (error) {
221
+ printResult(io, fail('autonomous-resume.init', 'AUTONOMOUS_RESUME_INIT_FAILED', getErrorMessage(error), {}, ['Use a safe change id, a non-empty goal, and a writable project path']), options.json);
222
+ process.exitCode = 1;
223
+ }
224
+ }
199
225
  function addTechPlanOptions(command) {
200
226
  return addJsonOption(command
201
227
  .description('Generate a technical dry-run graph')
@@ -232,6 +258,14 @@ function addSwarmPlanOptions(command, includeSkill) {
232
258
  }
233
259
  return addJsonOption(configured);
234
260
  }
261
+ function addAutonomousResumeInitOptions(command) {
262
+ return addJsonOption(command
263
+ .description('Write the autonomous resume artifact scaffold for a change-id')
264
+ .requiredOption('--change-id <id>', 'change identifier')
265
+ .requiredOption('--goal <goal>', 'planning goal')
266
+ .requiredOption('--project <path>', 'artifact workspace path to write under')
267
+ .option('--apply', 'write the artifacts to disk (default is dry-run preview)'));
268
+ }
235
269
  export function registerWorkflowCommands(program, io) {
236
270
  const refactor = program.command('refactor').description('Plan a Peaks refactor run without modifying code');
237
271
  addJsonOption(refactor
@@ -261,6 +295,10 @@ export function registerWorkflowCommands(program, io) {
261
295
  addWorkflowRouteOptions(program.command('route'), 'Plan a workflow routing dry-run summary').action((options) => runWorkflowRoute(io, options));
262
296
  addWorkflowRouteOptions(workflow.command('autonomous'), 'Plan an autonomous workflow handoff summary').action((options) => runAutonomousWorkflow(io, options));
263
297
  addWorkflowRouteOptions(program.command('autonomous'), 'Plan an autonomous workflow handoff summary').action((options) => runAutonomousWorkflow(io, options));
298
+ const autonomousResume = workflow.command('autonomous-resume').description('Manage autonomous workflow resume artifacts');
299
+ addAutonomousResumeInitOptions(autonomousResume.command('init')).action((options) => runAutonomousResumeInit(io, options));
300
+ const autonomousResumeAlias = program.command('autonomous-resume').description('Manage autonomous workflow resume artifacts');
301
+ addAutonomousResumeInitOptions(autonomousResumeAlias.command('init')).action((options) => runAutonomousResumeInit(io, options));
264
302
  const swarm = program.command('swarm').description('Plan RD swarm dry-run graphs');
265
303
  addSwarmPlanOptions(swarm.command('plan'), true).action((options) => runSwarmPlan(io, options));
266
304
  addSwarmPlanOptions(program.command('swarm-plan'), false).action((options) => runSwarmPlan(io, options));
@@ -1,4 +1,4 @@
1
- export type RequestArtifactRole = 'prd' | 'ui' | 'rd' | 'qa';
1
+ export type RequestArtifactRole = 'prd' | 'ui' | 'rd' | 'qa' | 'sc';
2
2
  export type CreateRequestArtifactOptions = {
3
3
  role: RequestArtifactRole;
4
4
  requestId: string;
@@ -40,7 +40,7 @@ export type ShowRequestArtifactResult = RequestArtifactSummary & {
40
40
  };
41
41
  export declare function listRequestArtifacts(options: ListRequestArtifactsOptions): Promise<RequestArtifactSummary[]>;
42
42
  export declare function showRequestArtifact(options: ShowRequestArtifactOptions): Promise<ShowRequestArtifactResult | null>;
43
- export type RequestArtifactState = 'draft' | 'confirmed-by-user' | 'direction-locked' | 'spec-locked' | 'implemented' | 'qa-handoff' | 'running' | 'verdict-issued' | 'handed-off' | 'blocked';
43
+ export type RequestArtifactState = 'draft' | 'confirmed-by-user' | 'direction-locked' | 'spec-locked' | 'implemented' | 'qa-handoff' | 'running' | 'verdict-issued' | 'impact-recorded' | 'boundary-recorded' | 'handed-off' | 'blocked';
44
44
  export declare function allowedStatesForRole(role: RequestArtifactRole): ReadonlyArray<RequestArtifactState>;
45
45
  export type TransitionRequestArtifactOptions = {
46
46
  role: RequestArtifactRole;
@@ -2,7 +2,7 @@ import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
3
  import { isDirectory, listDirectories, pathExists } from '../../shared/fs.js';
4
4
  const REQUEST_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
5
- const VALID_ROLES = new Set(['prd', 'ui', 'rd', 'qa']);
5
+ const VALID_ROLES = new Set(['prd', 'ui', 'rd', 'qa', 'sc']);
6
6
  function defaultClock() {
7
7
  return new Date().toISOString();
8
8
  }
@@ -215,6 +215,58 @@ function renderQaTemplate(requestId, sessionId, timestamp) {
215
215
 
216
216
  ## Status
217
217
 
218
+ - created: ${timestamp}
219
+ - last update: ${timestamp}
220
+ - state: draft
221
+ `;
222
+ }
223
+ function renderScTemplate(requestId, sessionId, timestamp) {
224
+ return `# SC Request ${requestId}
225
+
226
+ - session: ${sessionId}
227
+ - linked-prd: .peaks/${sessionId}/prd/requests/${requestId}.md
228
+ - linked-rd: .peaks/${sessionId}/rd/requests/${requestId}.md
229
+ - linked-qa: .peaks/${sessionId}/qa/requests/${requestId}.md
230
+ - linked-ui: .peaks/${sessionId}/ui/requests/${requestId}.md (when UI involved)
231
+ - type: feature | bug | refactor | clarification
232
+
233
+ ## Change impact
234
+
235
+ - modules / files / routes / data models touched
236
+ - blast radius: local | cross-cutting | release-critical
237
+ - rollback strategy
238
+
239
+ ## Commit boundaries
240
+
241
+ - one commit per OpenSpec heading (when openspec/ exists)
242
+ - otherwise: list of slice ids → commit message + scope
243
+
244
+ ## Artifact retention
245
+
246
+ - prd artifact: ...
247
+ - rd artifact: ...
248
+ - qa artifact: ...
249
+ - ui artifact: ... (when UI involved)
250
+ - coverage evidence: ...
251
+ - code review evidence: ...
252
+
253
+ ## Sync / authorization
254
+
255
+ - artifact workspace path: .peaks/${sessionId}/
256
+ - memory sync authorized: yes | no
257
+ - artifact sync authorized: yes | no
258
+ - rationale if not authorized: keep local
259
+
260
+ ## Rollback points
261
+
262
+ - commits / tags / branches that can revert each boundary
263
+
264
+ ## Handoff
265
+
266
+ - to peaks-txt: .peaks/${sessionId}/txt/skill-usage-lessons.md (when reusable lesson exists)
267
+
268
+ ## Status
269
+
218
270
  - created: ${timestamp}
219
271
  - last update: ${timestamp}
220
272
  - state: draft
@@ -230,11 +282,13 @@ function renderTemplate(role, requestId, sessionId, timestamp) {
230
282
  return renderRdTemplate(requestId, sessionId, timestamp);
231
283
  case 'qa':
232
284
  return renderQaTemplate(requestId, sessionId, timestamp);
285
+ case 'sc':
286
+ return renderScTemplate(requestId, sessionId, timestamp);
233
287
  }
234
288
  }
235
289
  export async function createRequestArtifact(options) {
236
290
  if (!VALID_ROLES.has(options.role)) {
237
- throw new Error(`Invalid role: ${String(options.role)} (expected prd, ui, rd, or qa)`);
291
+ throw new Error(`Invalid role: ${String(options.role)} (expected prd, ui, rd, qa, or sc)`);
238
292
  }
239
293
  if (!REQUEST_ID_PATTERN.test(options.requestId)) {
240
294
  throw new Error(`Invalid request id: ${options.requestId} (expected letters, digits, dots, underscores, or dashes)`);
@@ -313,7 +367,7 @@ export async function listRequestArtifacts(options) {
313
367
  }
314
368
  export async function showRequestArtifact(options) {
315
369
  if (!VALID_ROLES.has(options.role)) {
316
- throw new Error(`Invalid role: ${String(options.role)} (expected prd, ui, rd, or qa)`);
370
+ throw new Error(`Invalid role: ${String(options.role)} (expected prd, ui, rd, qa, or sc)`);
317
371
  }
318
372
  if (!REQUEST_ID_PATTERN.test(options.requestId)) {
319
373
  throw new Error(`Invalid request id: ${options.requestId} (expected letters, digits, dots, underscores, or dashes)`);
@@ -347,7 +401,8 @@ const ALLOWED_STATES_PER_ROLE = {
347
401
  prd: ['draft', 'confirmed-by-user', 'handed-off', 'blocked'],
348
402
  ui: ['draft', 'direction-locked', 'handed-off', 'blocked'],
349
403
  rd: ['draft', 'spec-locked', 'implemented', 'qa-handoff', 'handed-off', 'blocked'],
350
- qa: ['draft', 'running', 'verdict-issued', 'blocked']
404
+ qa: ['draft', 'running', 'verdict-issued', 'blocked'],
405
+ sc: ['draft', 'impact-recorded', 'boundary-recorded', 'handed-off', 'blocked']
351
406
  };
352
407
  export function allowedStatesForRole(role) {
353
408
  return ALLOWED_STATES_PER_ROLE[role];
@@ -391,7 +446,7 @@ function updateStatusBlock(markdown, newState, timestamp, reason) {
391
446
  }
392
447
  export async function transitionRequestArtifact(options) {
393
448
  if (!VALID_ROLES.has(options.role)) {
394
- throw new Error(`Invalid role: ${String(options.role)} (expected prd, ui, rd, or qa)`);
449
+ throw new Error(`Invalid role: ${String(options.role)} (expected prd, ui, rd, qa, or sc)`);
395
450
  }
396
451
  if (!REQUEST_ID_PATTERN.test(options.requestId)) {
397
452
  throw new Error(`Invalid request id: ${options.requestId} (expected letters, digits, dots, underscores, or dashes)`);
@@ -0,0 +1,2 @@
1
+ import type { CodegraphExecutionResult, CodegraphInvocation } from './codegraph-service.js';
2
+ export declare function defaultCodegraphProcessRunner(invocation: CodegraphInvocation): Promise<CodegraphExecutionResult>;
@@ -0,0 +1,93 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { join } from 'node:path';
3
+ const CODEGRAPH_PROCESS_TIMEOUT_MS = 600_000;
4
+ const CODEGRAPH_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
5
+ function createCodegraphEnvironment(sourceEnv = process.env) {
6
+ const preservedKeys = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
7
+ const environment = {};
8
+ for (const key of preservedKeys) {
9
+ const value = sourceEnv[key];
10
+ if (value !== undefined) {
11
+ environment[key] = value;
12
+ }
13
+ }
14
+ return environment;
15
+ }
16
+ function assertOutputLimit(currentSize, chunkSize) {
17
+ const nextSize = currentSize + chunkSize;
18
+ if (nextSize > CODEGRAPH_OUTPUT_LIMIT_BYTES) {
19
+ throw new Error(`codegraph output exceeded ${CODEGRAPH_OUTPUT_LIMIT_BYTES} bytes`);
20
+ }
21
+ return nextSize;
22
+ }
23
+ function terminateCodegraphProcess(childProcess) {
24
+ if (childProcess.pid === undefined) {
25
+ childProcess.kill();
26
+ return;
27
+ }
28
+ if (process.platform === 'win32') {
29
+ if (process.env.SystemRoot) {
30
+ spawn(join(process.env.SystemRoot, 'System32', 'taskkill.exe'), ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
31
+ }
32
+ else {
33
+ childProcess.kill();
34
+ }
35
+ return;
36
+ }
37
+ try {
38
+ process.kill(-childProcess.pid, 'SIGTERM');
39
+ }
40
+ catch {
41
+ childProcess.kill('SIGTERM');
42
+ }
43
+ }
44
+ export function defaultCodegraphProcessRunner(invocation) {
45
+ return new Promise((resolveResult, reject) => {
46
+ const childProcess = spawn(invocation.executable, invocation.args, {
47
+ cwd: invocation.cwd,
48
+ detached: process.platform !== 'win32',
49
+ env: createCodegraphEnvironment(),
50
+ shell: false
51
+ });
52
+ const timeout = setTimeout(() => {
53
+ terminateCodegraphProcess(childProcess);
54
+ reject(new Error(`codegraph process timed out after ${CODEGRAPH_PROCESS_TIMEOUT_MS}ms`));
55
+ }, CODEGRAPH_PROCESS_TIMEOUT_MS);
56
+ const stdoutChunks = [];
57
+ const stderrChunks = [];
58
+ let stdoutSize = 0;
59
+ let stderrSize = 0;
60
+ childProcess.stdout.on('data', (chunk) => {
61
+ try {
62
+ stdoutSize = assertOutputLimit(stdoutSize, chunk.length);
63
+ stdoutChunks.push(chunk);
64
+ }
65
+ catch (error) {
66
+ terminateCodegraphProcess(childProcess);
67
+ reject(error);
68
+ }
69
+ });
70
+ childProcess.stderr.on('data', (chunk) => {
71
+ try {
72
+ stderrSize = assertOutputLimit(stderrSize, chunk.length);
73
+ stderrChunks.push(chunk);
74
+ }
75
+ catch (error) {
76
+ terminateCodegraphProcess(childProcess);
77
+ reject(error);
78
+ }
79
+ });
80
+ childProcess.on('error', (error) => {
81
+ clearTimeout(timeout);
82
+ reject(error);
83
+ });
84
+ childProcess.on('close', (exitCode) => {
85
+ clearTimeout(timeout);
86
+ resolveResult({
87
+ exitCode,
88
+ stdout: Buffer.concat(stdoutChunks).toString('utf8'),
89
+ stderr: Buffer.concat(stderrChunks).toString('utf8')
90
+ });
91
+ });
92
+ });
93
+ }
@@ -1,16 +1,11 @@
1
1
  import { existsSync, realpathSync, statSync } from 'node:fs';
2
- import { spawn } from 'node:child_process';
3
2
  import { createRequire } from 'node:module';
4
- import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
4
+ import { defaultCodegraphProcessRunner } from './codegraph-process-runner.js';
5
5
  const CODEGRAPH_PACKAGE_NAME = '@colbymchenry/codegraph';
6
6
  const CODEGRAPH_PACKAGE_VERSION = '0.7.10';
7
7
  const CODEGRAPH_EXECUTABLE = process.execPath;
8
8
  const CODEGRAPH_BINARY_PATH = resolveCodegraphBinaryPath();
9
- const CODEGRAPH_PROCESS_TIMEOUT_MS = 600_000;
10
- const CODEGRAPH_OUTPUT_LIMIT_BYTES = 10 * 1024 * 1024;
11
- const NODE_OPTIONS_ENV_KEY = 'NODE_OPTIONS';
12
- const NPM_CONFIG_PREFIX = 'npm_config_';
13
- const NPM_CONFIG_UPPER_PREFIX = 'NPM_CONFIG_';
14
9
  const POSITIONAL_ARGUMENT_PREFIX = '-';
15
10
  const ALLOWED_SUBCOMMANDS = ['status', 'init', 'index', 'query', 'files', 'context', 'affected'];
16
11
  const NUMERIC_FLAG_NAMES = ['limit', 'maxDepth'];
@@ -28,9 +23,6 @@ function resolveCodegraphBinaryPath() {
28
23
  const require = createRequire(import.meta.url);
29
24
  const packageJsonPath = require.resolve('@colbymchenry/codegraph/package.json');
30
25
  const binaryPath = resolve(dirname(packageJsonPath), 'dist', 'bin', 'codegraph.js');
31
- if (!existsSync(binaryPath)) {
32
- throw new Error('Unable to resolve local codegraph binary from @colbymchenry/codegraph');
33
- }
34
26
  return binaryPath;
35
27
  }
36
28
  function assertSupportedSubcommand(subcommand) {
@@ -95,9 +87,6 @@ function resolveExistingBoundary(absoluteFilePath) {
95
87
  let currentPath = dirname(absoluteFilePath);
96
88
  while (!existsSync(currentPath)) {
97
89
  const parentPath = dirname(currentPath);
98
- if (parentPath === currentPath) {
99
- return currentPath;
100
- }
101
90
  currentPath = parentPath;
102
91
  }
103
92
  return currentPath;
@@ -150,91 +139,6 @@ function buildCommandArgs(options, projectRoot) {
150
139
  }
151
140
  return args;
152
141
  }
153
- function createCodegraphEnvironment(sourceEnv = process.env) {
154
- const preservedKeys = ['PATH', 'Path', 'HOME', 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', 'TEMP', 'TMP', 'SystemRoot', 'WINDIR'];
155
- const environment = {};
156
- for (const key of preservedKeys) {
157
- const value = sourceEnv[key];
158
- if (value !== undefined) {
159
- environment[key] = value;
160
- }
161
- }
162
- return environment;
163
- }
164
- function assertOutputLimit(currentSize, chunkSize) {
165
- const nextSize = currentSize + chunkSize;
166
- if (nextSize > CODEGRAPH_OUTPUT_LIMIT_BYTES) {
167
- throw new Error(`codegraph output exceeded ${CODEGRAPH_OUTPUT_LIMIT_BYTES} bytes`);
168
- }
169
- return nextSize;
170
- }
171
- function terminateCodegraphProcess(childProcess) {
172
- if (childProcess.pid === undefined) {
173
- childProcess.kill();
174
- return;
175
- }
176
- if (process.platform === 'win32') {
177
- const taskkillPath = process.env.SystemRoot ? join(process.env.SystemRoot, 'System32', 'taskkill.exe') : 'taskkill.exe';
178
- spawn(taskkillPath, ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
179
- return;
180
- }
181
- try {
182
- process.kill(-childProcess.pid, 'SIGTERM');
183
- }
184
- catch {
185
- childProcess.kill('SIGTERM');
186
- }
187
- }
188
- function defaultCodegraphProcessRunner(invocation) {
189
- return new Promise((resolveResult, reject) => {
190
- const childProcess = spawn(invocation.executable, invocation.args, {
191
- cwd: invocation.cwd,
192
- detached: process.platform !== 'win32',
193
- env: createCodegraphEnvironment(),
194
- shell: false
195
- });
196
- const timeout = setTimeout(() => {
197
- terminateCodegraphProcess(childProcess);
198
- reject(new Error(`codegraph process timed out after ${CODEGRAPH_PROCESS_TIMEOUT_MS}ms`));
199
- }, CODEGRAPH_PROCESS_TIMEOUT_MS);
200
- const stdoutChunks = [];
201
- const stderrChunks = [];
202
- let stdoutSize = 0;
203
- let stderrSize = 0;
204
- childProcess.stdout.on('data', (chunk) => {
205
- try {
206
- stdoutSize = assertOutputLimit(stdoutSize, chunk.length);
207
- stdoutChunks.push(chunk);
208
- }
209
- catch (error) {
210
- terminateCodegraphProcess(childProcess);
211
- reject(error);
212
- }
213
- });
214
- childProcess.stderr.on('data', (chunk) => {
215
- try {
216
- stderrSize = assertOutputLimit(stderrSize, chunk.length);
217
- stderrChunks.push(chunk);
218
- }
219
- catch (error) {
220
- terminateCodegraphProcess(childProcess);
221
- reject(error);
222
- }
223
- });
224
- childProcess.on('error', (error) => {
225
- clearTimeout(timeout);
226
- reject(error);
227
- });
228
- childProcess.on('close', (exitCode) => {
229
- clearTimeout(timeout);
230
- resolveResult({
231
- exitCode,
232
- stdout: Buffer.concat(stdoutChunks).toString('utf8'),
233
- stderr: Buffer.concat(stderrChunks).toString('utf8')
234
- });
235
- });
236
- });
237
- }
238
142
  export function createCodegraphInvocation(options) {
239
143
  assertSupportedSubcommand(options.subcommand);
240
144
  const projectRoot = resolveProjectRoot(options.project);
@@ -0,0 +1,14 @@
1
+ export declare function getUserConfigPath(): string;
2
+ export declare function isInsidePath(childPath: string, parentPath: string): boolean;
3
+ export declare function findProjectRoot(startPath: string): string | null;
4
+ export declare function resolveProjectRootForConfig(startPath: string): string;
5
+ export declare function getProjectConfigPath(projectRoot: string | null): string | null;
6
+ export declare function getProjectBootstrapConfigPath(projectRoot: string): string;
7
+ export declare function validateProjectBootstrapConfigPathForWrite(projectRoot: string, configPath: string): void;
8
+ export declare function validateUserConfigPathForWrite(configPath: string): void;
9
+ export declare function validateArtifactWorkspaceRoot(artifactRoot: string, workspaceRoot: string): void;
10
+ export declare function validateArtifactWorkspaceMarkerPath(artifactRoot: string, peaksPath: string, markerPath: string): void;
11
+ export declare function readConfigFileSafely(configPath: string, errorMessage: string): string;
12
+ export declare function writeConfigFileSafely(configPath: string, content: string, validateBeforeWrite: () => void, errorMessage: string): void;
13
+ export declare function writeProjectConfigFile(projectRoot: string, configPath: string, content: string): void;
14
+ export declare function writeUserConfigFile(configPath: string, content: string): void;