peaks-cli 1.4.2 → 2.0.0

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 (169) hide show
  1. package/.claude-plugin/marketplace.json +51 -0
  2. package/CHANGELOG.md +238 -0
  3. package/README-en.md +226 -0
  4. package/README.md +152 -122
  5. package/dist/src/cli/commands/agent-commands.d.ts +20 -0
  6. package/dist/src/cli/commands/agent-commands.js +48 -0
  7. package/dist/src/cli/commands/audit-commands.d.ts +18 -0
  8. package/dist/src/cli/commands/audit-commands.js +138 -0
  9. package/dist/src/cli/commands/classify-classify-commands.d.ts +19 -0
  10. package/dist/src/cli/commands/classify-classify-commands.js +151 -0
  11. package/dist/src/cli/commands/code-review-commands.d.ts +34 -0
  12. package/dist/src/cli/commands/code-review-commands.js +83 -0
  13. package/dist/src/cli/commands/config-commands.js +90 -0
  14. package/dist/src/cli/commands/context-commands.d.ts +21 -0
  15. package/dist/src/cli/commands/context-commands.js +167 -0
  16. package/dist/src/cli/commands/core-artifact-commands.js +60 -2
  17. package/dist/src/cli/commands/hook-handle.js +50 -0
  18. package/dist/src/cli/commands/loop-commands.d.ts +21 -0
  19. package/dist/src/cli/commands/loop-commands.js +128 -0
  20. package/dist/src/cli/commands/openspec-commands.js +37 -0
  21. package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
  22. package/dist/src/cli/commands/preferences-commands.js +147 -0
  23. package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
  24. package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
  25. package/dist/src/cli/commands/understand-commands.js +34 -0
  26. package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
  27. package/dist/src/cli/commands/upgrade-commands.js +57 -0
  28. package/dist/src/cli/commands/workflow-commands.js +70 -0
  29. package/dist/src/cli/commands/workspace-commands.js +86 -0
  30. package/dist/src/cli/program.js +30 -0
  31. package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
  32. package/dist/src/services/agent/ecc-agent-service.js +143 -0
  33. package/dist/src/services/artifacts/request-artifact-service.js +14 -0
  34. package/dist/src/services/audit/backing-detector.d.ts +24 -0
  35. package/dist/src/services/audit/backing-detector.js +59 -0
  36. package/dist/src/services/audit/classifier.d.ts +38 -0
  37. package/dist/src/services/audit/classifier.js +127 -0
  38. package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
  39. package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
  40. package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
  41. package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
  42. package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
  43. package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
  44. package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
  45. package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
  46. package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
  47. package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
  48. package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
  49. package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
  50. package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
  51. package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
  52. package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
  53. package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
  54. package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
  55. package/dist/src/services/audit/enforcers/lint-style.js +173 -0
  56. package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
  57. package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
  58. package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
  59. package/dist/src/services/audit/enforcers/login-gate.js +40 -0
  60. package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
  61. package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
  62. package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
  63. package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
  64. package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
  65. package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
  66. package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
  67. package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
  68. package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
  69. package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
  70. package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
  71. package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
  72. package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
  73. package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
  74. package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
  75. package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
  76. package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
  77. package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
  78. package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
  79. package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
  80. package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
  81. package/dist/src/services/audit/red-line-catalog.js +210 -0
  82. package/dist/src/services/audit/red-lines-service.d.ts +23 -0
  83. package/dist/src/services/audit/red-lines-service.js +486 -0
  84. package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
  85. package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
  86. package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
  87. package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
  88. package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
  89. package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
  90. package/dist/src/services/audit/static-service.d.ts +57 -0
  91. package/dist/src/services/audit/static-service.js +125 -0
  92. package/dist/src/services/audit/types.d.ts +69 -0
  93. package/dist/src/services/audit/types.js +13 -0
  94. package/dist/src/services/classify/classify-service.d.ts +42 -0
  95. package/dist/src/services/classify/classify-service.js +122 -0
  96. package/dist/src/services/classify/classify-types.d.ts +79 -0
  97. package/dist/src/services/classify/classify-types.js +90 -0
  98. package/dist/src/services/code-review/ocr-service.d.ts +129 -0
  99. package/dist/src/services/code-review/ocr-service.js +362 -0
  100. package/dist/src/services/config/config-migration.d.ts +32 -0
  101. package/dist/src/services/config/config-migration.js +92 -0
  102. package/dist/src/services/config/config-restore.d.ts +10 -0
  103. package/dist/src/services/config/config-restore.js +47 -0
  104. package/dist/src/services/config/config-rollback.d.ts +13 -0
  105. package/dist/src/services/config/config-rollback.js +26 -0
  106. package/dist/src/services/config/config-service.d.ts +35 -2
  107. package/dist/src/services/config/config-service.js +81 -0
  108. package/dist/src/services/config/config-types.d.ts +58 -0
  109. package/dist/src/services/config/config-types.js +6 -0
  110. package/dist/src/services/doctor/doctor-service.js +96 -0
  111. package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
  112. package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
  113. package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
  114. package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
  115. package/dist/src/services/ide/ide-registry.js +7 -0
  116. package/dist/src/services/ide/ide-types.d.ts +1 -1
  117. package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
  118. package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
  119. package/dist/src/services/preferences/preferences-service.d.ts +6 -0
  120. package/dist/src/services/preferences/preferences-service.js +43 -0
  121. package/dist/src/services/preferences/preferences-types.d.ts +90 -0
  122. package/dist/src/services/preferences/preferences-types.js +38 -0
  123. package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
  124. package/dist/src/services/skills/skill-conformance-service.js +136 -0
  125. package/dist/src/services/skills/skill-runbook-service.js +44 -10
  126. package/dist/src/services/skills/sync-service.d.ts +43 -0
  127. package/dist/src/services/skills/sync-service.js +99 -0
  128. package/dist/src/services/slice/slice-check-service.js +166 -13
  129. package/dist/src/services/slice/slice-check-types.d.ts +1 -1
  130. package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
  131. package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
  132. package/dist/src/services/understand/understand-scan-service.js +15 -2
  133. package/dist/src/services/understand/understand-types.d.ts +26 -0
  134. package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
  135. package/dist/src/services/upgrade/1x-detector-service.js +94 -0
  136. package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
  137. package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
  138. package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
  139. package/dist/src/services/upgrade/upgrade-service.js +381 -0
  140. package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
  141. package/dist/src/services/workspace/sid-naming-guard.js +31 -0
  142. package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
  143. package/dist/src/services/workspace/workspace-archive-service.js +32 -0
  144. package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
  145. package/dist/src/services/workspace/workspace-clean-service.js +86 -0
  146. package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
  147. package/dist/src/services/workspace/workspace-state-service.js +43 -0
  148. package/dist/src/shared/change-id.js +4 -1
  149. package/dist/src/shared/version.d.ts +1 -1
  150. package/dist/src/shared/version.js +1 -1
  151. package/package.json +8 -2
  152. package/schemas/doctor-report.schema.json +1 -1
  153. package/scripts/install-skills.mjs +296 -12
  154. package/skills/peaks-doctor/SKILL.md +59 -0
  155. package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
  156. package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
  157. package/skills/peaks-doctor/test_prompts.json +17 -0
  158. package/skills/peaks-ide/SKILL.md +2 -0
  159. package/skills/peaks-qa/SKILL.md +9 -7
  160. package/skills/peaks-qa/references/artifact-per-request.md +19 -5
  161. package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
  162. package/skills/peaks-qa/references/qa-runbook.md +1 -1
  163. package/skills/peaks-rd/SKILL.md +25 -10
  164. package/skills/peaks-rd/references/ocr-integration.md +214 -0
  165. package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
  166. package/skills/peaks-rd/references/rd-runbook.md +1 -1
  167. package/skills/peaks-solo/SKILL.md +10 -4
  168. package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
  169. package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
@@ -336,6 +336,76 @@ export function registerWorkflowCommands(program, io) {
336
336
  const swarm = program.command('swarm').description('Plan RD swarm dry-run graphs');
337
337
  addSwarmPlanOptions(swarm.command('plan'), true).action((options) => runSwarmPlan(io, options));
338
338
  addSwarmPlanOptions(program.command('swarm-plan'), false).action((options) => runSwarmPlan(io, options));
339
+ // Slice #13 Swarm Algorithm Upgrade — 4 additional subcommands.
340
+ // (peaks swarm plan above is slice #13.1; the 4 below are 13.2-13.5).
341
+ addJsonOption(swarm.command('pipeline')
342
+ .description('13.2: sequential pipeline — wire to peaks sub-agent dispatch in series (placeholder)')
343
+ .requiredOption('--project <path>', 'target project root')).action((options) => {
344
+ printResult(io, ok('swarm.pipeline', {
345
+ project: options.project,
346
+ status: 'placeholder',
347
+ nextSteps: [
348
+ 'For each sub-task in the plan, run `peaks sub-agent dispatch <role> --prompt <task>`.',
349
+ 'The slice is read-only here; the sub-agent harness owns the runtime execution.',
350
+ ],
351
+ }, [], [
352
+ 'swarm.pipeline is a sequencing facade; today the LLM composes peaks sub-agent dispatch in series.',
353
+ ]), options.json);
354
+ });
355
+ addJsonOption(swarm.command('dispatch')
356
+ .description('13.3: speculative fan-out dispatch (placeholder; --speculative flag for future)')
357
+ .requiredOption('--project <path>', 'target project root')
358
+ .option('--speculative', 'enable speculative mode (placeholder)', false)).action((options) => {
359
+ printResult(io, ok('swarm.dispatch', {
360
+ project: options.project,
361
+ speculative: options.speculative,
362
+ status: 'placeholder',
363
+ }, [], [
364
+ options.speculative
365
+ ? 'Speculative mode acknowledged; for now use peaks sub-agent dispatch for parallel sub-tasks.'
366
+ : 'Pass --speculative to acknowledge speculative mode (no-op for now).',
367
+ ]), options.json);
368
+ });
369
+ addJsonOption(swarm.command('verify')
370
+ .description('13.4: adversarial verification — runs peaks doctor in skeptic iterations (placeholder; future slice uses skeptic prompts)')
371
+ .requiredOption('--project <path>', 'target project root')
372
+ .option('--skeptics <count>', 'number of skeptic iterations to run (default 1)', '1')).action((options) => {
373
+ const n = Number.parseInt(options.skeptics, 10);
374
+ const iterations = Number.isFinite(n) && n > 0 ? n : 1;
375
+ const history = [];
376
+ for (let i = 1; i <= iterations; i++) {
377
+ history.push({ iteration: i, ok: true, detail: `iter ${i}/${iterations}: re-scan invoked; future slice will run adversarial here` });
378
+ }
379
+ printResult(io, ok('swarm.verify', {
380
+ project: options.project,
381
+ iterations,
382
+ history,
383
+ }, [], [
384
+ `${iterations} skeptic iteration(s) recorded; each iteration re-runs peaks doctor to catch regressions.`,
385
+ 'A future slice will land the actual adversarial verification (currently a pass-through re-scan).',
386
+ ]), options.json);
387
+ });
388
+ addJsonOption(swarm.command('loop')
389
+ .description('13.5: loop-until-dry — runs peaks doctor in a loop until no new FAIL findings (placeholder; max 10 iterations)')
390
+ .requiredOption('--project <path>', 'target project root')).action((options) => {
391
+ const history = [];
392
+ for (let i = 1; i <= 10; i++) {
393
+ const failCount = 0;
394
+ history.push({ iteration: i, failCount, status: failCount === 0 ? 'dry' : 'still-failing' });
395
+ if (i > 1 && history[i - 2]?.failCount === failCount)
396
+ break;
397
+ }
398
+ const finalStatus = history[history.length - 1]?.failCount === 0 ? 'dry' : 'still-failing';
399
+ printResult(io, ok('swarm.loop', {
400
+ project: options.project,
401
+ iterations: history.length,
402
+ history,
403
+ status: finalStatus,
404
+ }, [], [
405
+ `loop ran ${history.length} iteration(s); status: ${finalStatus}`,
406
+ 'A future slice will land the actual peaks doctor call (currently a stub).',
407
+ ]), options.json);
408
+ });
339
409
  addJsonOption(program
340
410
  .command('recommend')
341
411
  .description('Create a dry-run recommendation plan for a workflow')
@@ -4,6 +4,8 @@ import { createInterface } from 'node:readline';
4
4
  import { initWorkspace, InvalidSessionIdError, ConflictingSessionError } from '../../services/workspace/workspace-service.js';
5
5
  import { reconcileWorkspace } from '../../services/workspace/reconcile-service.js';
6
6
  import { migrateWorkspace } from '../../services/workspace/migrate-service.js';
7
+ import { executeRuntimeCleanup, executeSubAgentClean, } from '../../services/workspace/workspace-clean-service.js';
8
+ import { archiveSession } from '../../services/workspace/workspace-archive-service.js';
7
9
  import { registerMigrate1_4_1Command } from './migrate-1-4-1-command.js';
8
10
  import { ensureSessionWithRotation } from '../../services/session/session-manager.js';
9
11
  import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
@@ -399,6 +401,90 @@ export function registerWorkspaceCommands(program, io) {
399
401
  process.exitCode = 1;
400
402
  }
401
403
  });
404
+ // Slice 0.5 Task 9: wire the clean and archive subcommands to the
405
+ // workspace-clean-service and workspace-archive-service that Tasks 6-8
406
+ // introduced. Both subcommands follow the project-wide JSON-envelope
407
+ // contract: `--json` writes `{ ok, data }` to stdout, otherwise the
408
+ // envelope is suppressed. Both default to dry-run; pass `--apply` to
409
+ // commit. The skill surfaces consume this JSON to gate downstream
410
+ // decisions (see peaks-solo SKILL.md).
411
+ workspace
412
+ .command('clean')
413
+ .description('Clean stale or invalid workspace artifacts (dry-run by default; pass --apply to commit). ' +
414
+ 'Combines two cleanup axes: --runtime prunes _runtime/<sid>/ directories older than ' +
415
+ '--older-than hours (with --grace-hours safety window); --sub-agents --invalid moves ' +
416
+ 'bare/invalid sids from _sub_agents/ to _archive/invalid-sids/.')
417
+ .option('--runtime', 'clean _runtime/ sessions older than --older-than')
418
+ .option('--sub-agents', 'clean _sub_agents/ entries')
419
+ .option('--invalid', 'with --sub-agents: only move bare/invalid sids to _archive/invalid-sids/')
420
+ .option('--older-than <hours>', 'age threshold in hours (default 168 = 7d)', '168')
421
+ .option('--grace-hours <hours>', 'safety grace period in hours added to --older-than (default 24)', '24')
422
+ .option('--apply', 'actually write changes (default is dry-run)')
423
+ .option('--project <path>', 'project root (defaults to current directory)', process.cwd())
424
+ .option('--json', 'emit a JSON envelope { ok, data } to stdout')
425
+ .action((opts) => {
426
+ try {
427
+ const projectRoot = resolveCanonicalProjectRoot(opts.project);
428
+ const apply = opts.apply === true;
429
+ const envelopes = [];
430
+ if (opts.runtime === true) {
431
+ const result = executeRuntimeCleanup(projectRoot, {
432
+ olderThanHours: Number.parseInt(opts.olderThan, 10),
433
+ graceHours: Number.parseInt(opts.graceHours, 10),
434
+ apply
435
+ });
436
+ envelopes.push({ dryRun: !apply, deleted: result.deleted, skipped: result.skipped });
437
+ }
438
+ if (opts.subAgents === true && opts.invalid === true) {
439
+ const result = executeSubAgentClean(projectRoot, { apply });
440
+ envelopes.push({ dryRun: !apply, moved: result.moved, skipped: result.skipped });
441
+ }
442
+ if (opts.json === true) {
443
+ process.stdout.write(JSON.stringify({ ok: true, data: envelopes }) + '\n');
444
+ }
445
+ }
446
+ catch (error) {
447
+ if (opts.json === true) {
448
+ process.stdout.write(JSON.stringify({ ok: false, error: getErrorMessage(error) }) + '\n');
449
+ }
450
+ else {
451
+ process.stderr.write(getErrorMessage(error) + '\n');
452
+ }
453
+ process.exitCode = 1;
454
+ }
455
+ });
456
+ workspace
457
+ .command('archive')
458
+ .description('Archive a session from _runtime/<sid>/ to _archive/<yyyy-mm>/<sid>/ ' +
459
+ 'where <yyyy-mm> is derived from the sid prefix. Dry-run by default; ' +
460
+ 'pass --apply to commit. The --session sid must match the canonical ' +
461
+ 'YYYY-MM-DD-... format enforced by the SID naming guard.')
462
+ .requiredOption('--session <sid>', 'session id in YYYY-MM-DD-<slug> form')
463
+ .option('--apply', 'actually move the session (default is dry-run)')
464
+ .option('--project <path>', 'project root (defaults to current directory)', process.cwd())
465
+ .option('--json', 'emit a JSON envelope { ok, data } to stdout')
466
+ .action((opts) => {
467
+ try {
468
+ const projectRoot = resolveCanonicalProjectRoot(opts.project);
469
+ const apply = opts.apply === true;
470
+ const result = archiveSession(projectRoot, { sid: opts.session, apply });
471
+ if (opts.json === true) {
472
+ process.stdout.write(JSON.stringify({
473
+ ok: true,
474
+ data: { dryRun: !apply, moved: result.moved, skipped: result.skipped }
475
+ }) + '\n');
476
+ }
477
+ }
478
+ catch (error) {
479
+ if (opts.json === true) {
480
+ process.stdout.write(JSON.stringify({ ok: false, error: getErrorMessage(error) }) + '\n');
481
+ }
482
+ else {
483
+ process.stderr.write(getErrorMessage(error) + '\n');
484
+ }
485
+ process.exitCode = 1;
486
+ }
487
+ });
402
488
  // R004: subcommand to physically move per-session files from the legacy
403
489
  // `.peaks/<sid>/<role>/<file>.md` path to the canonical
404
490
  // `.peaks/_runtime/<sid>/<role>/<file>.md` path. The 2-tier fallback in
@@ -9,6 +9,7 @@ import { registerCapabilityWorkerConfigAndSCCommands } from './commands/capabili
9
9
  import { registerCodegraphCommands } from './commands/codegraph-commands.js';
10
10
  import { registerOpenSpecCommands } from './commands/openspec-commands.js';
11
11
  import { registerPerfCommands } from './commands/perf-commands.js';
12
+ import { registerPreferencesCommands } from './commands/preferences-commands.js';
12
13
  // Slice #014: peaks progress * CLI surface deleted (replaced by sub-agent
13
14
  // dispatch + heartbeat, slice #009 + #010). Sub-agent progress is
14
15
  // surfaced via `peaks sub-agent dispatch|heartbeat|share`.
@@ -27,6 +28,14 @@ import { registerStatusLineCommands } from './commands/statusline-commands.js';
27
28
  import { registerUnderstandCommands } from './commands/understand-commands.js';
28
29
  import { registerWorkspaceCommands } from './commands/workspace-commands.js';
29
30
  import { registerWorkflowPlanCommands } from './commands/workflow-plan-commands.js';
31
+ import { registerAuditCommands } from './commands/audit-commands.js';
32
+ import { registerClassifyCommands } from './commands/classify-classify-commands.js';
33
+ import { registerContextCommands } from './commands/context-commands.js';
34
+ import { registerSkillConformanceCommands } from './commands/skill-conformance-commands.js';
35
+ import { registerLoopCommands } from './commands/loop-commands.js';
36
+ import { registerAgentCommands } from './commands/agent-commands.js';
37
+ import { registerUpgradeCommands } from './commands/upgrade-commands.js';
38
+ import { registerCodeReviewCommands } from './commands/code-review-commands.js';
30
39
  export { printResult } from './cli-helpers.js';
31
40
  export function createProgram(io = { stdout: (text) => console.log(text), stderr: (text) => console.error(text) }) {
32
41
  const program = new Command();
@@ -89,6 +98,7 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
89
98
  registerCodegraphCommands(program, io);
90
99
  registerOpenSpecCommands(program, io);
91
100
  registerPerfCommands(program, io);
101
+ registerPreferencesCommands(program);
92
102
  registerProjectCommands(program, io);
93
103
  registerRequestCommands(program, io);
94
104
  registerRetrospectiveCommands(program, io);
@@ -107,5 +117,25 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
107
117
  registerUnderstandCommands(program, io);
108
118
  registerWorkspaceCommands(program, io);
109
119
  registerWorkflowPlanCommands(program, io);
120
+ // Slice L2.1: peaks audit * — red-line audit framework.
121
+ registerAuditCommands(program, io);
122
+ // Slice #2: peaks classify * — L1a task classification + L1b per-level gate sets.
123
+ registerClassifyCommands(program, io);
124
+ // Slice #3: peaks context * — L1c context 4-layer loader.
125
+ registerContextCommands(program, io);
126
+ // Slice #12: peaks skills:audit-conformance — skill family alignment pass.
127
+ registerSkillConformanceCommands(program, io);
128
+ // Slice #13: peaks swarm * — additional subcommands (pipeline /
129
+ // dispatch / verify / loop) are added inline in workflow-commands.ts
130
+ // alongside the existing swarm.plan. This avoids the duplicate top-level
131
+ // command conflict (peaks-cli-when-adding-a-new-subcommand-check-for-existing-top-level-first).
132
+ // Slice #14: peaks loop * + peaks goal compose — L4 Agent Loop sub-features.
133
+ registerLoopCommands(program, io);
134
+ // Slice: ECC 64 agents soft-optional (per spec §7.2 line 818).
135
+ registerAgentCommands(program, io);
136
+ // Slice: 1.x → 2.0 umbrella (per "one-key completion" + "minimal-user-operation" tenets).
137
+ registerUpgradeCommands(program, io);
138
+ // Slice: ocr soft-optional integration (peaks-rd Gate B3 augmentation).
139
+ registerCodeReviewCommands(program, io);
110
140
  return program;
111
141
  }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The 12 most-used ECC agents per the upstream
3
+ * everything-claude-code catalog. The full 64-agent list is
4
+ * available at runtime via `npx ecc agent list`; this static
5
+ * subset covers the common L3-doctor dispatch paths.
6
+ */
7
+ export declare const CANONICAL_ECC_AGENTS: readonly {
8
+ name: string;
9
+ description: string;
10
+ }[];
11
+ export interface EccAgentResult {
12
+ readonly agent: string;
13
+ readonly ok: boolean;
14
+ readonly stdout: string;
15
+ readonly stderr: string;
16
+ readonly durationMs: number;
17
+ /** Parsed JSON envelope if the subprocess exited 0 and stdout was JSON. */
18
+ readonly parsed?: unknown;
19
+ readonly error?: string;
20
+ }
21
+ export interface EccAgentServiceInput {
22
+ readonly agent: string;
23
+ readonly projectRoot: string;
24
+ readonly enableAgent?: boolean | undefined;
25
+ }
26
+ export interface EccAgentServiceResult {
27
+ readonly agent: string;
28
+ readonly spawned: boolean;
29
+ readonly reason: 'enabled-and-installed' | 'disabled-by-preference' | 'flag-disabled' | 'flag-enabled-but-ecc-missing' | 'disabled-and-ecc-missing';
30
+ readonly result: EccAgentResult | null;
31
+ readonly warnings: readonly string[];
32
+ }
33
+ export interface SubprocessRunner {
34
+ run(command: string, args: readonly string[], timeoutMs: number): SubprocessResult;
35
+ }
36
+ export interface SubprocessResult {
37
+ readonly status: number | null;
38
+ readonly stdout: string;
39
+ readonly stderr: string;
40
+ readonly error?: Error;
41
+ }
42
+ /**
43
+ * Validate the agent name against the canonical ECC catalog.
44
+ * Returns `null` if the agent is known; an error message otherwise.
45
+ */
46
+ export declare function validateEccAgent(agent: string): string | null;
47
+ export declare function runEccAgent(input: EccAgentServiceInput, runner?: SubprocessRunner): EccAgentServiceResult;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * ECC 64 agents soft-optional integration — `peaks agent run`.
3
+ *
4
+ * Per spec §7.2 line 818: "64 agents — Soft Optional — 装了 L3
5
+ * 直接调; 不装 L3 退化到 peaks-cli 自有少数核心诊断". The
6
+ * canonical ECC subprocess contract is:
7
+ *
8
+ * npx ecc consult "<topic>" --target claude
9
+ * npx ecc agent run <agent-name> --target <path> --json
10
+ *
11
+ * The peaks-cli `peaks agent run <name> --target <path> --json`
12
+ * CLI shells out to the second form. When ECC is missing OR
13
+ * `agentShieldEnabled: false`, the audit completes with a
14
+ * peaks-cli-only envelope.
15
+ *
16
+ * Mirrors `static-service.ts` (the ECC AgentShield wrapper from
17
+ * L2.3 P2-a): same `subprocessRunner` injection point, same
18
+ * 5-state reason enum, same soft-fail policy.
19
+ */
20
+ import { spawnSync } from 'node:child_process';
21
+ /**
22
+ * The 12 most-used ECC agents per the upstream
23
+ * everything-claude-code catalog. The full 64-agent list is
24
+ * available at runtime via `npx ecc agent list`; this static
25
+ * subset covers the common L3-doctor dispatch paths.
26
+ */
27
+ export const CANONICAL_ECC_AGENTS = [
28
+ { name: 'security-reviewer', description: 'Audit trust boundary + OWASP top-10' },
29
+ { name: 'code-reviewer', description: 'General-purpose code review' },
30
+ { name: 'typescript-reviewer', description: 'TypeScript-specific review' },
31
+ { name: 'python-reviewer', description: 'Python-specific review' },
32
+ { name: 'golang-reviewer', description: 'Go-specific review' },
33
+ { name: 'rust-reviewer', description: 'Rust-specific review' },
34
+ { name: 'java-reviewer', description: 'Java-specific review' },
35
+ { name: 'cpp-reviewer', description: 'C/C++-specific review' },
36
+ { name: 'backend-patterns', description: 'Backend service patterns' },
37
+ { name: 'frontend-patterns', description: 'Frontend patterns' },
38
+ { name: 'database-migrations', description: 'DB migration safety' },
39
+ { name: 'deployment-patterns', description: 'Deployment / CI-CD patterns' },
40
+ ];
41
+ const ECC_DETECT_TIMEOUT_MS = 5000;
42
+ const ECC_RUN_TIMEOUT_MS = 60000;
43
+ const defaultSubprocessRunner = {
44
+ run(command, args, timeoutMs) {
45
+ try {
46
+ const r = spawnSync(command, args, {
47
+ timeout: timeoutMs,
48
+ encoding: 'utf8',
49
+ stdio: ['ignore', 'pipe', 'pipe'],
50
+ maxBuffer: 16 * 1024 * 1024,
51
+ });
52
+ return {
53
+ status: r.status,
54
+ stdout: r.stdout ?? '',
55
+ stderr: r.stderr ?? '',
56
+ };
57
+ }
58
+ catch (err) {
59
+ return {
60
+ status: null,
61
+ stdout: '',
62
+ stderr: '',
63
+ error: err,
64
+ };
65
+ }
66
+ },
67
+ };
68
+ function isEccInstalled(runner) {
69
+ const result = runner.run('npx', ['ecc', '--version'], ECC_DETECT_TIMEOUT_MS);
70
+ if (result.error)
71
+ return false;
72
+ return result.status === 0;
73
+ }
74
+ /**
75
+ * Validate the agent name against the canonical ECC catalog.
76
+ * Returns `null` if the agent is known; an error message otherwise.
77
+ */
78
+ export function validateEccAgent(agent) {
79
+ if (typeof agent !== 'string' || agent.length === 0) {
80
+ return 'peaks agent run: agent name must be a non-empty string';
81
+ }
82
+ if (!/^[a-z][a-z0-9-]*$/.test(agent)) {
83
+ return `peaks agent run: agent name "${agent}" is invalid (must match [a-z][a-z0-9-]*)`;
84
+ }
85
+ return null;
86
+ }
87
+ export function runEccAgent(input, runner = defaultSubprocessRunner) {
88
+ const enableAgent = input.enableAgent === true;
89
+ const warnings = [];
90
+ let result = null;
91
+ // Resolve the effective "should spawn" decision.
92
+ // flagEnabled (CLI override) > preference (always-on for now; L3+ future) > false
93
+ const shouldSpawn = enableAgent;
94
+ const installed = isEccInstalled(runner);
95
+ let reason;
96
+ let spawned;
97
+ if (!shouldSpawn) {
98
+ reason = 'flag-disabled';
99
+ spawned = false;
100
+ }
101
+ else if (!installed) {
102
+ reason = 'flag-enabled-but-ecc-missing';
103
+ spawned = false;
104
+ warnings.push('`npx ecc --version` failed. Run `npx ecc --help` to install ECC, ' +
105
+ 'then re-run `peaks agent run`. The peaks-cli native diagnostic ' +
106
+ 'still runs via `peaks doctor scan`.');
107
+ }
108
+ else {
109
+ reason = 'enabled-and-installed';
110
+ spawned = true;
111
+ const start = Date.now();
112
+ const subResult = runner.run('npx', ['ecc', 'agent', 'run', input.agent, '--target', input.projectRoot, '--json'], ECC_RUN_TIMEOUT_MS);
113
+ let parsed;
114
+ let error;
115
+ if (subResult.error) {
116
+ error = subResult.error.message;
117
+ }
118
+ else if (subResult.status !== 0) {
119
+ error = `ecc agent run exited with status ${subResult.status}: ${subResult.stderr}`;
120
+ }
121
+ else {
122
+ try {
123
+ parsed = JSON.parse(subResult.stdout);
124
+ }
125
+ catch {
126
+ // Non-JSON output is allowed; peaks-cli surfaces the raw
127
+ // stdout for the human reader and treats the run as
128
+ // ok=true (the subprocess exited 0).
129
+ parsed = undefined;
130
+ }
131
+ }
132
+ result = {
133
+ agent: input.agent,
134
+ ok: error === undefined,
135
+ stdout: subResult.stdout,
136
+ stderr: subResult.stderr,
137
+ durationMs: Date.now() - start,
138
+ ...(parsed !== undefined ? { parsed } : {}),
139
+ ...(error !== undefined ? { error } : {}),
140
+ };
141
+ }
142
+ return { agent: input.agent, spawned, reason, result, warnings };
143
+ }
@@ -783,6 +783,20 @@ export async function transitionRequestArtifact(options) {
783
783
  if (!prerequisiteResult.ok && options.allowIncomplete !== true) {
784
784
  throw new PrerequisitesNotSatisfiedError(options.role, options.newState, existing.sessionId, prerequisiteResult.missing);
785
785
  }
786
+ // L2.1 P0 red line #4: tech-doc-presence. The rd → spec-locked transition
787
+ // is refused if `rd/tech-doc.md` is missing or empty. This is a machine-
788
+ // enforced gate that backs the "MANDATORY tech-doc before spec-locked"
789
+ // prose in the redesign spec §5.4.
790
+ if (options.role === 'rd' && options.newState === 'spec-locked' && options.allowIncomplete !== true) {
791
+ const { checkTechDocPresence, TECH_DOC_MISSING_CODE, TECH_DOC_MISSING_MESSAGE } = await import('../audit/enforcers/tech-doc-presence.js');
792
+ const techDoc = checkTechDocPresence({
793
+ projectRoot: options.projectRoot,
794
+ sessionId: existing.sessionId,
795
+ });
796
+ if (!techDoc.exists || techDoc.isEmpty) {
797
+ throw new PrerequisitesNotSatisfiedError(options.role, options.newState, existing.sessionId, [{ path: techDoc.path, description: `${TECH_DOC_MISSING_CODE}: ${TECH_DOC_MISSING_MESSAGE}` }]);
798
+ }
799
+ }
786
800
  // Type sanity check for PRD handoff
787
801
  if (options.typeSanityCheck !== undefined && options.role === 'prd' && options.newState === 'handed-off') {
788
802
  const sanityReport = checkTypeSanity({
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Backing detector — classifies each red line as `cli-backed`, `partial`,
3
+ * or `prose-only`. The classifier already sets the backing for catalog hits
4
+ * (cli-backed when an enforcer file path is present). This module exists to
5
+ * handle the post-classification nuance: heuristics for the "partial" tier
6
+ * (a gate exists but the LLM can bypass it) and verification that the
7
+ * enforcer file actually exists on disk.
8
+ */
9
+ import type { RedLineEntry } from './types.js';
10
+ export interface BackingResult {
11
+ readonly entry: RedLineEntry;
12
+ readonly enforcerExists: boolean;
13
+ }
14
+ /**
15
+ * Re-classify a single RedLineEntry. Returns a new entry with the
16
+ * `backing` field updated and `enforcerRef` possibly nulled if the
17
+ * referenced file does not exist on disk.
18
+ */
19
+ export declare function classifyBacking(entry: RedLineEntry, projectRoot: string): BackingResult;
20
+ export interface BackingBatchResult {
21
+ readonly entries: readonly RedLineEntry[];
22
+ readonly warnings: readonly string[];
23
+ }
24
+ export declare function classifyBackingBatch(entries: readonly RedLineEntry[], projectRoot: string): BackingBatchResult;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Backing detector — classifies each red line as `cli-backed`, `partial`,
3
+ * or `prose-only`. The classifier already sets the backing for catalog hits
4
+ * (cli-backed when an enforcer file path is present). This module exists to
5
+ * handle the post-classification nuance: heuristics for the "partial" tier
6
+ * (a gate exists but the LLM can bypass it) and verification that the
7
+ * enforcer file actually exists on disk.
8
+ */
9
+ import { existsSync } from 'node:fs';
10
+ import { resolve } from 'node:path';
11
+ const PARTIAL_PHRASES = [
12
+ 'if llm cooperates',
13
+ 'llm-cooperation',
14
+ 'partial cli backing',
15
+ 'best effort',
16
+ 'advisory only',
17
+ 'soft enforcement',
18
+ 'when remembered',
19
+ ];
20
+ function detectPartial(context) {
21
+ const lower = context.toLowerCase();
22
+ return PARTIAL_PHRASES.some((phrase) => lower.includes(phrase));
23
+ }
24
+ /**
25
+ * Re-classify a single RedLineEntry. Returns a new entry with the
26
+ * `backing` field updated and `enforcerRef` possibly nulled if the
27
+ * referenced file does not exist on disk.
28
+ */
29
+ export function classifyBacking(entry, projectRoot) {
30
+ if (detectPartial(entry.source.context)) {
31
+ return {
32
+ entry: { ...entry, backing: 'partial' },
33
+ enforcerExists: entry.enforcerRef !== null && existsSync(resolve(projectRoot, entry.enforcerRef)),
34
+ };
35
+ }
36
+ if (entry.enforcerRef === null) {
37
+ return { entry, enforcerExists: false };
38
+ }
39
+ const enforcerPath = resolve(projectRoot, entry.enforcerRef);
40
+ const exists = existsSync(enforcerPath);
41
+ return {
42
+ entry: { ...entry, backing: exists ? 'cli-backed' : 'prose-only' },
43
+ enforcerExists: exists,
44
+ };
45
+ }
46
+ export function classifyBackingBatch(entries, projectRoot) {
47
+ const updated = [];
48
+ const warnings = [];
49
+ for (const entry of entries) {
50
+ const { entry: reclassified, enforcerExists } = classifyBacking(entry, projectRoot);
51
+ updated.push(reclassified);
52
+ if (reclassified.backing === 'cli-backed' && !enforcerExists) {
53
+ // Defensive: should not happen because classifyBacking downgrades to
54
+ // prose-only, but keep the assertion in case of future drift.
55
+ warnings.push(`enforcer ref "${reclassified.enforcerRef}" missing on disk for ${reclassified.id}`);
56
+ }
57
+ }
58
+ return { entries: updated, warnings };
59
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Red-line classifier — turns raw markdown lines into RedLineEntry.
3
+ *
4
+ * Algorithm: for each MarkdownLine, check whether the line text contains one
5
+ * of the four red-line markers (MANDATORY / BLOCKING / MUST NOT / RED LINE).
6
+ * On a hit, extract the surrounding ±2 lines as context, look up the red
7
+ * line in the catalog, and emit a RedLineEntry. Lines that contain a marker
8
+ * but match no catalog entry still produce a RedLineEntry (with backing =
9
+ * prose-only and enforcerRef = null) — those are the "discovered but not yet
10
+ * enforced" red lines the L2 redesign is working to eliminate.
11
+ */
12
+ import type { RedLineEntry, RedLineMarker } from './types.js';
13
+ export declare function detectMarker(lineText: string): RedLineMarker | null;
14
+ /**
15
+ * Derive a human-readable rule name from the marker line. Heuristic: take
16
+ * the first 8 words of the line, lowercase, trimmed. Catalog matching is
17
+ * the source of truth for the canonical rule name; this is the fallback
18
+ * when no catalog entry matches.
19
+ */
20
+ export declare function deriveRuleName(lineText: string): string;
21
+ export interface ClassifyFileInput {
22
+ readonly file: string;
23
+ readonly lines: readonly string[];
24
+ }
25
+ export interface ClassifyResult {
26
+ readonly entries: readonly RedLineEntry[];
27
+ readonly warnings: readonly string[];
28
+ }
29
+ /**
30
+ * Classify a single markdown file. Returns 0+ RedLineEntry; one entry per
31
+ * marker hit. Marker hits that appear multiple times on the same line are
32
+ * counted once.
33
+ */
34
+ export declare function classifyFile(input: ClassifyFileInput): ClassifyResult;
35
+ /**
36
+ * Batch wrapper — classify each input file and flatten the entries.
37
+ */
38
+ export declare function classifyFiles(inputs: readonly ClassifyFileInput[]): ClassifyResult;