peaks-cli 1.4.1 → 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 (219) 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 +142 -165
  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 +81 -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/memory-commands.d.ts +13 -0
  21. package/dist/src/cli/commands/memory-commands.js +60 -0
  22. package/dist/src/cli/commands/openspec-commands.js +37 -0
  23. package/dist/src/cli/commands/preferences-commands.d.ts +2 -0
  24. package/dist/src/cli/commands/preferences-commands.js +147 -0
  25. package/dist/src/cli/commands/retrospective-commands.d.ts +9 -0
  26. package/dist/src/cli/commands/retrospective-commands.js +58 -0
  27. package/dist/src/cli/commands/skill-conformance-commands.d.ts +9 -0
  28. package/dist/src/cli/commands/skill-conformance-commands.js +39 -0
  29. package/dist/src/cli/commands/understand-commands.js +34 -0
  30. package/dist/src/cli/commands/upgrade-commands.d.ts +23 -0
  31. package/dist/src/cli/commands/upgrade-commands.js +57 -0
  32. package/dist/src/cli/commands/workflow-commands.js +70 -0
  33. package/dist/src/cli/commands/workspace-commands.js +86 -0
  34. package/dist/src/cli/program.js +46 -22
  35. package/dist/src/services/agent/ecc-agent-service.d.ts +47 -0
  36. package/dist/src/services/agent/ecc-agent-service.js +143 -0
  37. package/dist/src/services/artifacts/request-artifact-service.js +14 -0
  38. package/dist/src/services/audit/backing-detector.d.ts +24 -0
  39. package/dist/src/services/audit/backing-detector.js +59 -0
  40. package/dist/src/services/audit/classifier.d.ts +38 -0
  41. package/dist/src/services/audit/classifier.js +127 -0
  42. package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +29 -0
  43. package/dist/src/services/audit/enforcers/active-skill-resolver.js +71 -0
  44. package/dist/src/services/audit/enforcers/design-draft-confirm.d.ts +25 -0
  45. package/dist/src/services/audit/enforcers/design-draft-confirm.js +54 -0
  46. package/dist/src/services/audit/enforcers/lint-audit-regression.d.ts +21 -0
  47. package/dist/src/services/audit/enforcers/lint-audit-regression.js +86 -0
  48. package/dist/src/services/audit/enforcers/lint-catalog-governance.d.ts +27 -0
  49. package/dist/src/services/audit/enforcers/lint-catalog-governance.js +38 -0
  50. package/dist/src/services/audit/enforcers/lint-cli-back.d.ts +16 -0
  51. package/dist/src/services/audit/enforcers/lint-cli-back.js +35 -0
  52. package/dist/src/services/audit/enforcers/lint-output-style.d.ts +11 -0
  53. package/dist/src/services/audit/enforcers/lint-output-style.js +94 -0
  54. package/dist/src/services/audit/enforcers/lint-reference-integrity.d.ts +6 -0
  55. package/dist/src/services/audit/enforcers/lint-reference-integrity.js +83 -0
  56. package/dist/src/services/audit/enforcers/lint-reference-shape.d.ts +30 -0
  57. package/dist/src/services/audit/enforcers/lint-reference-shape.js +272 -0
  58. package/dist/src/services/audit/enforcers/lint-style.d.ts +49 -0
  59. package/dist/src/services/audit/enforcers/lint-style.js +173 -0
  60. package/dist/src/services/audit/enforcers/lint-workflow-shape.d.ts +5 -0
  61. package/dist/src/services/audit/enforcers/lint-workflow-shape.js +141 -0
  62. package/dist/src/services/audit/enforcers/login-gate.d.ts +23 -0
  63. package/dist/src/services/audit/enforcers/login-gate.js +40 -0
  64. package/dist/src/services/audit/enforcers/mock-placement.d.ts +25 -0
  65. package/dist/src/services/audit/enforcers/mock-placement.js +48 -0
  66. package/dist/src/services/audit/enforcers/no-root-pollution.d.ts +21 -0
  67. package/dist/src/services/audit/enforcers/no-root-pollution.js +56 -0
  68. package/dist/src/services/audit/enforcers/pre-rd-scan.d.ts +22 -0
  69. package/dist/src/services/audit/enforcers/pre-rd-scan.js +23 -0
  70. package/dist/src/services/audit/enforcers/prototype-fidelity.d.ts +25 -0
  71. package/dist/src/services/audit/enforcers/prototype-fidelity.js +75 -0
  72. package/dist/src/services/audit/enforcers/resume-detection.d.ts +21 -0
  73. package/dist/src/services/audit/enforcers/resume-detection.js +52 -0
  74. package/dist/src/services/audit/enforcers/solo-code-ban.d.ts +23 -0
  75. package/dist/src/services/audit/enforcers/solo-code-ban.js +27 -0
  76. package/dist/src/services/audit/enforcers/sub-agent-sid.d.ts +25 -0
  77. package/dist/src/services/audit/enforcers/sub-agent-sid.js +63 -0
  78. package/dist/src/services/audit/enforcers/tech-doc-presence.d.ts +28 -0
  79. package/dist/src/services/audit/enforcers/tech-doc-presence.js +35 -0
  80. package/dist/src/services/audit/red-line-catalog-p2-a.d.ts +21 -0
  81. package/dist/src/services/audit/red-line-catalog-p2-a.js +233 -0
  82. package/dist/src/services/audit/red-line-catalog-p2-b.d.ts +19 -0
  83. package/dist/src/services/audit/red-line-catalog-p2-b.js +225 -0
  84. package/dist/src/services/audit/red-line-catalog.d.ts +51 -0
  85. package/dist/src/services/audit/red-line-catalog.js +210 -0
  86. package/dist/src/services/audit/red-lines-service.d.ts +23 -0
  87. package/dist/src/services/audit/red-lines-service.js +486 -0
  88. package/dist/src/services/audit/scanners/openspec-scanner.d.ts +15 -0
  89. package/dist/src/services/audit/scanners/openspec-scanner.js +55 -0
  90. package/dist/src/services/audit/scanners/rules-tree-scanner.d.ts +16 -0
  91. package/dist/src/services/audit/scanners/rules-tree-scanner.js +56 -0
  92. package/dist/src/services/audit/scanners/skills-tree-scanner.d.ts +17 -0
  93. package/dist/src/services/audit/scanners/skills-tree-scanner.js +46 -0
  94. package/dist/src/services/audit/static-service.d.ts +57 -0
  95. package/dist/src/services/audit/static-service.js +125 -0
  96. package/dist/src/services/audit/types.d.ts +69 -0
  97. package/dist/src/services/audit/types.js +13 -0
  98. package/dist/src/services/classify/classify-service.d.ts +42 -0
  99. package/dist/src/services/classify/classify-service.js +122 -0
  100. package/dist/src/services/classify/classify-types.d.ts +79 -0
  101. package/dist/src/services/classify/classify-types.js +90 -0
  102. package/dist/src/services/code-review/ocr-service.d.ts +129 -0
  103. package/dist/src/services/code-review/ocr-service.js +362 -0
  104. package/dist/src/services/config/config-migration.d.ts +32 -0
  105. package/dist/src/services/config/config-migration.js +92 -0
  106. package/dist/src/services/config/config-restore.d.ts +10 -0
  107. package/dist/src/services/config/config-restore.js +47 -0
  108. package/dist/src/services/config/config-rollback.d.ts +13 -0
  109. package/dist/src/services/config/config-rollback.js +26 -0
  110. package/dist/src/services/config/config-service.d.ts +35 -2
  111. package/dist/src/services/config/config-service.js +81 -0
  112. package/dist/src/services/config/config-types.d.ts +58 -0
  113. package/dist/src/services/config/config-types.js +6 -0
  114. package/dist/src/services/doctor/doctor-service.js +96 -0
  115. package/dist/src/services/fuzzy-matching/fuzzy-match-service.d.ts +15 -0
  116. package/dist/src/services/fuzzy-matching/fuzzy-match-service.js +56 -0
  117. package/dist/src/services/fuzzy-matching/types.d.ts +20 -0
  118. package/dist/src/services/fuzzy-matching/types.js +1 -0
  119. package/dist/src/services/ide/adapters/hermes-adapter.d.ts +21 -0
  120. package/dist/src/services/ide/adapters/hermes-adapter.js +51 -0
  121. package/dist/src/services/ide/adapters/openclaw-adapter.d.ts +14 -0
  122. package/dist/src/services/ide/adapters/openclaw-adapter.js +42 -0
  123. package/dist/src/services/ide/ide-registry.js +7 -0
  124. package/dist/src/services/ide/ide-types.d.ts +1 -1
  125. package/dist/src/services/memory/memory-search-service.d.ts +61 -0
  126. package/dist/src/services/memory/memory-search-service.js +80 -0
  127. package/dist/src/services/openspec/openspec-propose-from-doctor-service.d.ts +31 -0
  128. package/dist/src/services/openspec/openspec-propose-from-doctor-service.js +95 -0
  129. package/dist/src/services/preferences/preferences-service.d.ts +6 -0
  130. package/dist/src/services/preferences/preferences-service.js +43 -0
  131. package/dist/src/services/preferences/preferences-types.d.ts +90 -0
  132. package/dist/src/services/preferences/preferences-types.js +38 -0
  133. package/dist/src/services/recommendations/capability-seed-items.js +0 -1
  134. package/dist/src/services/recommendations/capability-seed-mappings.js +0 -1
  135. package/dist/src/services/recommendations/capability-seed-sources.js +0 -1
  136. package/dist/src/services/retrospective/retrospective-search-service.d.ts +37 -0
  137. package/dist/src/services/retrospective/retrospective-search-service.js +75 -0
  138. package/dist/src/services/skills/skill-conformance-service.d.ts +40 -0
  139. package/dist/src/services/skills/skill-conformance-service.js +136 -0
  140. package/dist/src/services/skills/skill-runbook-service.js +44 -10
  141. package/dist/src/services/skills/sync-service.d.ts +43 -0
  142. package/dist/src/services/skills/sync-service.js +99 -0
  143. package/dist/src/services/slice/slice-check-service.js +166 -13
  144. package/dist/src/services/slice/slice-check-types.d.ts +1 -1
  145. package/dist/src/services/standards/migrate-claude-rules-service.d.ts +19 -0
  146. package/dist/src/services/standards/migrate-claude-rules-service.js +193 -0
  147. package/dist/src/services/standards/project-context.d.ts +1 -1
  148. package/dist/src/services/standards/project-context.js +0 -4
  149. package/dist/src/services/standards/project-standards-service.js +1 -3
  150. package/dist/src/services/understand/understand-scan-service.js +15 -2
  151. package/dist/src/services/understand/understand-types.d.ts +26 -0
  152. package/dist/src/services/upgrade/1x-detector-service.d.ts +7 -0
  153. package/dist/src/services/upgrade/1x-detector-service.js +94 -0
  154. package/dist/src/services/upgrade/gitignore-migrate-service.d.ts +56 -0
  155. package/dist/src/services/upgrade/gitignore-migrate-service.js +170 -0
  156. package/dist/src/services/upgrade/upgrade-service.d.ts +47 -0
  157. package/dist/src/services/upgrade/upgrade-service.js +381 -0
  158. package/dist/src/services/workspace/migrate-1-4-1-service.js +1 -1
  159. package/dist/src/services/workspace/sid-naming-guard.d.ts +14 -0
  160. package/dist/src/services/workspace/sid-naming-guard.js +31 -0
  161. package/dist/src/services/workspace/workspace-archive-service.d.ts +19 -0
  162. package/dist/src/services/workspace/workspace-archive-service.js +32 -0
  163. package/dist/src/services/workspace/workspace-clean-service.d.ts +41 -0
  164. package/dist/src/services/workspace/workspace-clean-service.js +86 -0
  165. package/dist/src/services/workspace/workspace-state-service.d.ts +7 -0
  166. package/dist/src/services/workspace/workspace-state-service.js +43 -0
  167. package/dist/src/shared/change-id.js +4 -1
  168. package/dist/src/shared/version.d.ts +1 -1
  169. package/dist/src/shared/version.js +1 -1
  170. package/package.json +10 -8
  171. package/schemas/doctor-report.schema.json +1 -1
  172. package/scripts/install-skills.mjs +296 -12
  173. package/skills/peaks-doctor/SKILL.md +59 -0
  174. package/skills/peaks-doctor/references/doctor-check-catalog.md +31 -0
  175. package/skills/peaks-doctor/references/from-doctor-flow.md +64 -0
  176. package/skills/peaks-doctor/test_prompts.json +17 -0
  177. package/skills/peaks-ide/SKILL.md +2 -0
  178. package/skills/peaks-qa/SKILL.md +9 -7
  179. package/skills/peaks-qa/references/artifact-per-request.md +19 -5
  180. package/skills/peaks-qa/references/qa-perf-test-plan.md +6 -6
  181. package/skills/peaks-qa/references/qa-runbook.md +1 -1
  182. package/skills/peaks-rd/SKILL.md +25 -10
  183. package/skills/peaks-rd/references/ocr-integration.md +214 -0
  184. package/skills/peaks-rd/references/rd-fanout-contracts.md +70 -0
  185. package/skills/peaks-rd/references/rd-runbook.md +1 -1
  186. package/skills/peaks-solo/SKILL.md +11 -5
  187. package/skills/peaks-solo/references/completion-handoff.md +3 -1
  188. package/skills/peaks-solo/references/step-0-55-1x-detection.md +82 -0
  189. package/skills/peaks-solo/references/workflow-gates-and-types.md +9 -0
  190. package/dist/src/cli/commands/shadcn-commands.d.ts +0 -3
  191. package/dist/src/cli/commands/shadcn-commands.js +0 -35
  192. package/dist/src/cli/commands/skill-context-stats-command.d.ts +0 -40
  193. package/dist/src/cli/commands/skill-context-stats-command.js +0 -96
  194. package/dist/src/cli/commands/skill-scope-commands.d.ts +0 -51
  195. package/dist/src/cli/commands/skill-scope-commands.js +0 -310
  196. package/dist/src/services/shadcn/shadcn-service.d.ts +0 -27
  197. package/dist/src/services/shadcn/shadcn-service.js +0 -128
  198. package/dist/src/services/skill-scope/adapters/_stub-helper.d.ts +0 -39
  199. package/dist/src/services/skill-scope/adapters/_stub-helper.js +0 -98
  200. package/dist/src/services/skill-scope/adapters/claude-code.d.ts +0 -59
  201. package/dist/src/services/skill-scope/adapters/claude-code.js +0 -304
  202. package/dist/src/services/skill-scope/adapters/codex.d.ts +0 -2
  203. package/dist/src/services/skill-scope/adapters/codex.js +0 -12
  204. package/dist/src/services/skill-scope/adapters/cursor.d.ts +0 -2
  205. package/dist/src/services/skill-scope/adapters/cursor.js +0 -13
  206. package/dist/src/services/skill-scope/adapters/qoder.d.ts +0 -2
  207. package/dist/src/services/skill-scope/adapters/qoder.js +0 -13
  208. package/dist/src/services/skill-scope/adapters/tongyi.d.ts +0 -2
  209. package/dist/src/services/skill-scope/adapters/tongyi.js +0 -13
  210. package/dist/src/services/skill-scope/adapters/trae.d.ts +0 -2
  211. package/dist/src/services/skill-scope/adapters/trae.js +0 -12
  212. package/dist/src/services/skill-scope/detect.d.ts +0 -81
  213. package/dist/src/services/skill-scope/detect.js +0 -513
  214. package/dist/src/services/skill-scope/registry.d.ts +0 -41
  215. package/dist/src/services/skill-scope/registry.js +0 -83
  216. package/dist/src/services/skill-scope/source-of-truth.d.ts +0 -44
  217. package/dist/src/services/skill-scope/source-of-truth.js +0 -118
  218. package/dist/src/services/skill-scope/types.d.ts +0 -195
  219. package/dist/src/services/skill-scope/types.js +0 -97
@@ -0,0 +1,47 @@
1
+ export interface UpgradeInput {
2
+ readonly projectRoot: string;
3
+ /**
4
+ * When true, the umbrella is being invoked from the
5
+ * `npm i -g peaks-cli` postinstall. Suppresses the
6
+ * interactive prompts and accepts soft-fail on any
7
+ * sub-step.
8
+ */
9
+ readonly auto?: boolean;
10
+ /**
11
+ * When omitted, the sub-commands are inferred from PATH
12
+ * (the postinstall puts the binary there). The umbrella
13
+ * never blocks on a missing binary — it surfaces the
14
+ * install hint in `nextActions` instead.
15
+ */
16
+ readonly peaksBin?: string;
17
+ }
18
+ export interface SubStepResult {
19
+ readonly name: string;
20
+ readonly status: 'pass' | 'fail' | 'skipped';
21
+ readonly exitCode: number | null;
22
+ readonly stdout: string;
23
+ readonly stderr: string;
24
+ readonly durationMs: number;
25
+ }
26
+ export interface UpgradeResult {
27
+ readonly applied: boolean;
28
+ readonly fromVersion: string | null;
29
+ readonly toVersion: string;
30
+ readonly projectRoot: string;
31
+ readonly steps: readonly SubStepResult[];
32
+ readonly passedCount: number;
33
+ readonly failedCount: number;
34
+ readonly skippedCount: number;
35
+ readonly auditBefore: {
36
+ totalRedLines: number;
37
+ cliBacked: number;
38
+ } | null;
39
+ readonly auditAfter: {
40
+ totalRedLines: number;
41
+ cliBacked: number;
42
+ } | null;
43
+ readonly upgradeRecordPath: string | null;
44
+ readonly nextActions: readonly string[];
45
+ readonly warnings: readonly string[];
46
+ }
47
+ export declare function runUpgrade(input: UpgradeInput): UpgradeResult;
@@ -0,0 +1,381 @@
1
+ /**
2
+ * peaks upgrade --to 2.0 — umbrella service for the 1.x → 2.0
3
+ * migration.
4
+ *
5
+ * Per the "one-key completion" + "minimal-user-operation" tenets
6
+ * (2026-06-11), the typical upgrade path is:
7
+ *
8
+ * $ npm i -g peaks-cli@2.0 # postinstall does everything
9
+ *
10
+ * OR (postinstall skipped / manual fallback):
11
+ *
12
+ * $ peaks upgrade --to 2.0
13
+ *
14
+ * The umbrella orchestrates 7 sub-commands:
15
+ * 1. config migrate (already ships as `peaks config migrate`)
16
+ * 2. standards migrate (`peaks standards migrate --from-claude-rules`)
17
+ * 3. memory extract (already ships as `peaks memory extract`)
18
+ * 4. hooks install (already ships as `peaks hooks install`)
19
+ * 5. skill sync (this session, `peaks skill sync --all`)
20
+ * 6. audit verify (already ships as `peaks audit red-lines`)
21
+ * 7. write upgrade record (in-process, .peaks/memory/upgrade-2.0-*.md)
22
+ *
23
+ * Each sub-step is a thin shell-out to the existing CLI; the
24
+ * umbrella's only in-process work is the audit and the upgrade
25
+ * record write. Sub-step failures are SOFT (logged + nextActions
26
+ * populated) so the umbrella never blocks a successful partial
27
+ * upgrade.
28
+ */
29
+ import { spawnSync } from 'node:child_process';
30
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
31
+ import { join, dirname, relative, resolve } from 'node:path';
32
+ import { fileURLToPath } from 'node:url';
33
+ import { runRedLinesAudit } from '../audit/red-lines-service.js';
34
+ import { savePreferences } from '../preferences/preferences-service.js';
35
+ import { migrateGitignoreFile } from './gitignore-migrate-service.js';
36
+ const STEPS = [
37
+ { name: 'config-migrate', args: (p) => ['config', 'migrate', '--project', p, '--apply', '--json'] },
38
+ { name: 'standards-migrate', args: (p) => ['standards', 'migrate', '--from-claude-rules', '--project', p, '--apply', '--json'] },
39
+ // memory extract is special: its --artifact takes literal file
40
+ // paths (memory-service rejects glob patterns via realpathSync).
41
+ // The umbrella expands the three documented patterns
42
+ // (skills/**/SKILL.md, CLAUDE.md, .claude/rules/**/*.md) on disk
43
+ // and passes the resulting literal list. See runUpgrade's special
44
+ // case below for the args resolution; the args function here is
45
+ // a placeholder so the STEPS table stays uniform.
46
+ { name: 'memory-extract', args: (p) => ['memory', 'extract', '--project', p, '--json'] },
47
+ { name: 'hooks-install', args: (p) => ['hooks', 'install', '--project', p, '--json'] },
48
+ { name: 'skill-sync', args: (p) => ['skill', 'sync', '--all', '--project', p, '--json'] },
49
+ { name: 'audit-verify', args: (p) => ['audit', 'red-lines', '--project', p, '--json'] },
50
+ ];
51
+ /**
52
+ * Walk `<root>` recursively and collect every file whose basename
53
+ * matches `predicate`. Returns absolute paths.
54
+ *
55
+ * Mirrors `readMarkdownFilesRecursive` in
56
+ * src/services/standards/migrate-claude-rules-service.ts so the
57
+ * umbrella does not pull a new glob dependency (Node 20+ engine
58
+ * constraint — `fs.globSync` requires Node 22+).
59
+ */
60
+ function collectFilesRecursive(root, predicate) {
61
+ if (!existsSync(root))
62
+ return [];
63
+ const stat = statSync(root);
64
+ if (stat.isFile()) {
65
+ return predicate(root.split(/[\\/]/).pop() ?? '') ? [root] : [];
66
+ }
67
+ if (!stat.isDirectory())
68
+ return [];
69
+ const out = [];
70
+ for (const entry of readdirSync(root)) {
71
+ const child = join(root, entry);
72
+ let childStat;
73
+ try {
74
+ childStat = statSync(child);
75
+ }
76
+ catch {
77
+ continue;
78
+ }
79
+ if (childStat.isFile()) {
80
+ if (predicate(entry))
81
+ out.push(child);
82
+ }
83
+ else if (childStat.isDirectory()) {
84
+ out.push(...collectFilesRecursive(child, predicate));
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+ /**
90
+ * Resolve the three documented memory-extract artifact patterns
91
+ * against a real project tree. Returns project-relative paths
92
+ * (memory-service joins them with --project root) so the
93
+ * realpathSync inside memory-service's assertInsideProject
94
+ * succeeds.
95
+ *
96
+ * Patterns:
97
+ * - skills/[asterisk][asterisk]/SKILL.md (project-root convention)
98
+ * - .claude/skills/[asterisk][asterisk]/SKILL.md (Claude-Code consumer convention; ice-cola)
99
+ * - CLAUDE.md
100
+ * - .claude/rules/[asterisk][asterisk]/[asterisk].md
101
+ *
102
+ * Returns an empty list when none of the roots exist. The
103
+ * caller marks the step skipped in that case.
104
+ */
105
+ function expandMemoryArtifacts(projectRoot) {
106
+ const out = [];
107
+ // skills/**/SKILL.md (peaks-cli repo convention)
108
+ const skillFiles = collectFilesRecursive(join(projectRoot, 'skills'), (name) => name === 'SKILL.md');
109
+ for (const abs of skillFiles) {
110
+ out.push(relative(projectRoot, abs));
111
+ }
112
+ // .claude/skills/**/SKILL.md (Claude-Code consumer convention;
113
+ // surfaced by ice-cola dogfood 2026-06-12 — the 1.x install
114
+ // landed skills under .claude/skills/, not <root>/skills/)
115
+ const claudeSkillFiles = collectFilesRecursive(join(projectRoot, '.claude', 'skills'), (name) => name === 'SKILL.md');
116
+ for (const abs of claudeSkillFiles) {
117
+ out.push(relative(projectRoot, abs));
118
+ }
119
+ // CLAUDE.md (literal)
120
+ const claudeMd = join(projectRoot, 'CLAUDE.md');
121
+ if (existsSync(claudeMd) && statSync(claudeMd).isFile()) {
122
+ out.push('CLAUDE.md');
123
+ }
124
+ // .claude/rules/**/*.md
125
+ const claudeRules = collectFilesRecursive(join(projectRoot, '.claude', 'rules'), (name) => name.endsWith('.md'));
126
+ for (const abs of claudeRules) {
127
+ out.push(relative(projectRoot, abs));
128
+ }
129
+ return out;
130
+ }
131
+ function read1xVersion(cwd) {
132
+ const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';
133
+ if (home.length === 0)
134
+ return null;
135
+ const global = join(home, '.peaks', 'config.json');
136
+ if (!existsSync(global))
137
+ return null;
138
+ try {
139
+ const raw = JSON.parse(readFileSync(global, 'utf8'));
140
+ if (typeof raw.version === 'string')
141
+ return raw.version;
142
+ }
143
+ catch {
144
+ // ignore
145
+ }
146
+ return null;
147
+ }
148
+ function runStep(peaksBin, name, args, timeoutMs = 60_000) {
149
+ const start = Date.now();
150
+ // The global `peaks` shim is a `/bin/sh` symlink script (the
151
+ // npm install postinstall creates `peaks` → `peaks.sh` on
152
+ // Windows). cmd.exe (the default Windows shell) cannot run
153
+ // `.sh` scripts directly, so the shim fails with "unknown
154
+ // command 'migrate'" etc. The fix: prefer the local node
155
+ // binary + the peaks.js script path. The umbrella resolves
156
+ // the script path at startup; only falls back to `peaks` if
157
+ // no script path is available (Unix-only).
158
+ let command;
159
+ let spawnArgs;
160
+ if (peaksBin.includes('\\') || peaksBin.includes('/')) {
161
+ // peaksBin is a real path (e.g. /c/.../bin/peaks.js);
162
+ // invoke directly via node.
163
+ command = process.execPath;
164
+ spawnArgs = [peaksBin, ...args];
165
+ }
166
+ else {
167
+ // peaksBin is just "peaks" — best-effort shell exec.
168
+ command = peaksBin;
169
+ spawnArgs = args;
170
+ }
171
+ try {
172
+ const result = spawnSync(command, spawnArgs, {
173
+ encoding: 'utf8',
174
+ stdio: ['ignore', 'pipe', 'pipe'],
175
+ timeout: timeoutMs,
176
+ });
177
+ return {
178
+ name,
179
+ status: result.status === 0 ? 'pass' : 'fail',
180
+ exitCode: result.status,
181
+ stdout: result.stdout ?? '',
182
+ stderr: result.stderr ?? '',
183
+ durationMs: Date.now() - start,
184
+ };
185
+ }
186
+ catch (err) {
187
+ const message = err instanceof Error ? err.message : String(err);
188
+ return {
189
+ name,
190
+ status: 'fail',
191
+ exitCode: null,
192
+ stdout: '',
193
+ stderr: message,
194
+ durationMs: Date.now() - start,
195
+ };
196
+ }
197
+ }
198
+ function writeUpgradeRecord(projectRoot, result) {
199
+ try {
200
+ const memoryDir = join(projectRoot, '.peaks', 'memory');
201
+ mkdirSync(memoryDir, { recursive: true });
202
+ const date = new Date().toISOString().slice(0, 10);
203
+ const file = join(memoryDir, `upgrade-2.0-${date}.md`);
204
+ const lines = [];
205
+ lines.push(`# Upgrade to peaks-cli 2.0 — ${date}`);
206
+ lines.push('');
207
+ lines.push(`> Auto-generated by \`peaks upgrade --to 2.0${result.applied ? ' --auto' : ''}\`.`);
208
+ lines.push(`> Per the "one-key completion" + "minimal-user-operation" tenets.`);
209
+ lines.push('');
210
+ if (result.fromVersion !== null) {
211
+ lines.push(`**From version**: ${result.fromVersion}`);
212
+ }
213
+ lines.push(`**To version**: 2.0.0`);
214
+ lines.push(`**Project root**: \`${result.projectRoot}\``);
215
+ lines.push('');
216
+ lines.push('## Sub-step results');
217
+ lines.push('');
218
+ lines.push('| step | status | exitCode | durationMs |');
219
+ lines.push('|------|--------|----------|------------|');
220
+ for (const step of result.steps) {
221
+ lines.push(`| ${step.name} | ${step.status} | ${step.exitCode ?? 'n/a'} | ${step.durationMs} |`);
222
+ }
223
+ lines.push('');
224
+ if (result.auditBefore !== null || result.auditAfter !== null) {
225
+ lines.push('## Audit snapshot');
226
+ lines.push('');
227
+ if (result.auditBefore !== null) {
228
+ lines.push(`- Before: totalRedLines=${result.auditBefore.totalRedLines}, cliBacked=${result.auditBefore.cliBacked}`);
229
+ }
230
+ if (result.auditAfter !== null) {
231
+ lines.push(`- After: totalRedLines=${result.auditAfter.totalRedLines}, cliBacked=${result.auditAfter.cliBacked}`);
232
+ }
233
+ lines.push('');
234
+ }
235
+ lines.push('## Next actions');
236
+ lines.push('');
237
+ for (const a of result.nextActions) {
238
+ lines.push(`- ${a}`);
239
+ }
240
+ writeFileSync(file, lines.join('\n') + '\n', 'utf8');
241
+ return file;
242
+ }
243
+ catch (err) {
244
+ process.stderr.write(`peaks upgrade: failed to write upgrade record: ${err instanceof Error ? err.message : String(err)}\n`);
245
+ return null;
246
+ }
247
+ }
248
+ export function runUpgrade(input) {
249
+ // Resolve the peaks binary. Default: the peaks.js script
250
+ // co-located with this compiled module (the user just installed
251
+ // peaks-cli globally, but the global `peaks` shim is a .sh
252
+ // script that cmd.exe can't run on Windows). Falling back
253
+ // to just "peaks" lets the umbrella work when invoked from
254
+ // a Unix-style environment that can run the shim directly.
255
+ const here = dirname(fileURLToPath(import.meta.url));
256
+ // Walk up from the compiled location to find bin/peaks.js.
257
+ // The compiled service lives at dist/src/services/upgrade/upgrade-service.js;
258
+ // bin/peaks.js is at the peaks-cli root.
259
+ const peaksBin = input.peaksBin ??
260
+ resolve(here, '..', '..', '..', '..', 'bin', 'peaks.js');
261
+ const fallbackPeaks = 'peaks';
262
+ const resolvedPeaksBin = existsSync(peaksBin) ? peaksBin : fallbackPeaks;
263
+ const fromVersion = read1xVersion(input.projectRoot);
264
+ const steps = [];
265
+ const warnings = [];
266
+ const nextActions = [];
267
+ // Ensure .peaks/preferences.json exists. This is the file the
268
+ // 1.x detector keys off — without it, `peaks upgrade --detect-1x`
269
+ // keeps returning isOneX=true after a successful upgrade and the
270
+ // user gets stuck in a re-prompt loop. savePreferences with an
271
+ // empty override merges with DEFAULT_PREFERENCES and writes; if
272
+ // the file already exists the user's values are preserved.
273
+ // Real bug surfaced by ice-cola dogfood 2026-06-12.
274
+ try {
275
+ savePreferences(input.projectRoot, {});
276
+ }
277
+ catch (err) {
278
+ warnings.push(`ensure-preferences failed: ${err instanceof Error ? err.message : String(err)}`);
279
+ }
280
+ // Migrate .gitignore so 2.0 tracked artifacts
281
+ // (.peaks/standards/, .peaks/memory/*.md durable memories,
282
+ // .peaks/PROJECT.md) aren't silently hidden by a 1.x wholesale
283
+ // `/.peaks/` ignore rule. Real bug surfaced by ice-cola dogfood
284
+ // 2026-06-12: every consumer artifact was being dropped from git
285
+ // status. Service is idempotent + creates a timestamped backup
286
+ // before any write.
287
+ try {
288
+ const giResult = migrateGitignoreFile({ projectRoot: input.projectRoot, apply: true });
289
+ if (giResult.changed && giResult.appliedWrite && giResult.backupPath !== null) {
290
+ nextActions.push(`Updated .gitignore — removed stale wholesale .peaks rule(s): ${giResult.removedRules.join(', ')}. Backup at ${giResult.backupPath}.`);
291
+ }
292
+ else if (giResult.missing) {
293
+ warnings.push('gitignore-migrate skipped: project has no .gitignore');
294
+ }
295
+ }
296
+ catch (err) {
297
+ warnings.push(`gitignore-migrate failed: ${err instanceof Error ? err.message : String(err)}`);
298
+ }
299
+ // Audit BEFORE the upgrade (baseline)
300
+ let auditBefore = null;
301
+ try {
302
+ const r = runRedLinesAudit({ projectRoot: input.projectRoot });
303
+ auditBefore = { totalRedLines: r.audit.totalRedLines, cliBacked: r.audit.cliBacked };
304
+ }
305
+ catch (err) {
306
+ warnings.push(`audit-before failed: ${err instanceof Error ? err.message : String(err)}`);
307
+ }
308
+ // Run the 6 sub-steps
309
+ for (const step of STEPS) {
310
+ if (step.name === 'memory-extract') {
311
+ // Special case: expand the three glob patterns to literal
312
+ // paths before spawning. memory-service rejects literal
313
+ // '**' in artifact paths (assertInsideProject's realpathSync
314
+ // throws ENOENT) and refuses to run without --artifact.
315
+ const artifacts = expandMemoryArtifacts(input.projectRoot);
316
+ if (artifacts.length === 0) {
317
+ steps.push({
318
+ name: 'memory-extract',
319
+ status: 'skipped',
320
+ exitCode: null,
321
+ stdout: '',
322
+ stderr: 'no skills/, CLAUDE.md, or .claude/rules/ artifacts found in the project',
323
+ durationMs: 0,
324
+ });
325
+ continue;
326
+ }
327
+ const args = ['memory', 'extract', '--project', input.projectRoot, '--artifact', ...artifacts, '--apply', '--json'];
328
+ const r = runStep(resolvedPeaksBin, 'memory-extract', args);
329
+ steps.push(r);
330
+ if (r.status === 'fail') {
331
+ warnings.push(`memory-extract failed: ${r.stderr.slice(0, 200)}`);
332
+ }
333
+ continue;
334
+ }
335
+ const args = step.args(input.projectRoot);
336
+ const r = runStep(resolvedPeaksBin, step.name, args);
337
+ steps.push(r);
338
+ if (r.status === 'fail') {
339
+ warnings.push(`${step.name} failed: ${r.stderr.slice(0, 200)}`);
340
+ }
341
+ }
342
+ // Audit AFTER the upgrade (verify)
343
+ let auditAfter = null;
344
+ try {
345
+ const r = runRedLinesAudit({ projectRoot: input.projectRoot });
346
+ auditAfter = { totalRedLines: r.audit.totalRedLines, cliBacked: r.audit.cliBacked };
347
+ }
348
+ catch (err) {
349
+ warnings.push(`audit-after failed: ${err instanceof Error ? err.message : String(err)}`);
350
+ }
351
+ const passedCount = steps.filter((s) => s.status === 'pass').length;
352
+ const failedCount = steps.filter((s) => s.status === 'fail').length;
353
+ const skippedCount = steps.filter((s) => s.status === 'skipped').length;
354
+ const applied = failedCount === 0;
355
+ if (failedCount > 0) {
356
+ nextActions.push(`${failedCount} sub-step(s) failed. Run \`peaks upgrade --to 2.0\` again to retry the failed steps.`);
357
+ }
358
+ if (input.auto !== true) {
359
+ nextActions.push('Run `peaks audit red-lines --project .` to verify the L2 catalog is healthy.');
360
+ }
361
+ nextActions.push('See `docs/UPGRADING-2.0.md` for the manual fallback if this auto-upgrade fails.');
362
+ // Write the upgrade record (always, even on partial failure —
363
+ // the user gets a forensic artifact either way)
364
+ const partial = {
365
+ applied,
366
+ fromVersion,
367
+ toVersion: '2.0.0',
368
+ projectRoot: input.projectRoot,
369
+ steps,
370
+ passedCount,
371
+ failedCount,
372
+ skippedCount,
373
+ auditBefore,
374
+ auditAfter,
375
+ upgradeRecordPath: null,
376
+ nextActions,
377
+ warnings,
378
+ };
379
+ const upgradeRecordPath = writeUpgradeRecord(input.projectRoot, partial);
380
+ return { ...partial, upgradeRecordPath };
381
+ }
@@ -41,7 +41,7 @@ function sha256(content) {
41
41
  function enumerateLegacySessions(projectRoot) {
42
42
  // Legacy per-session dirs: `.peaks/<sid>/` (NOT `.peaks/_runtime/<sid>/`).
43
43
  // We skip well-known non-session entries.
44
- const SKIP = new Set(['memory', 'PROJECT.md', 'retrospective', 'scope', 'skill-scope', '.peaks-init-hooks-decision.json', 'session.json', '.session.json', '_runtime', '_sub_agents', 'change', 'caller', 'callers', 'sop-state', 'system', 'active-skill.json', '.active-skill.json']);
44
+ const SKIP = new Set(['memory', 'PROJECT.md', 'retrospective', 'scope', '.peaks-init-hooks-decision.json', 'session.json', '.session.json', '_runtime', '_sub_agents', 'change', 'caller', 'callers', 'sop-state', 'system', 'active-skill.json', '.active-skill.json']);
45
45
  const peaksRoot = join(projectRoot, '.peaks');
46
46
  if (!existsSync(peaksRoot))
47
47
  return [];
@@ -0,0 +1,14 @@
1
+ /**
2
+ * SID naming guard. Enforces the "two-axis" convention from spec §0:
3
+ * session id: YYYY-MM-DD-session-<3-6 chars lowercase alnum>
4
+ * change id: kebab-case
5
+ *
6
+ * Spec §8.7 — bare forms (sid-3 / sid-h / sid-r / unknown-sid) are
7
+ * migrated to `_archive/invalid-sids/`, NOT tolerated.
8
+ */
9
+ export declare const SID_FORMAT_DESCRIPTION = "<YYYY-MM-DD>-session-<3-6 chars lowercase alnum>, e.g. 2026-06-11-session-abc123";
10
+ export declare function isValidSessionId(sid: string): boolean;
11
+ export declare function isValidChangeId(cid: string): boolean;
12
+ export declare function isBareSid(name: string): boolean;
13
+ export declare function assertValidSessionId(sid: string): void;
14
+ export declare function assertValidChangeId(cid: string): void;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * SID naming guard. Enforces the "two-axis" convention from spec §0:
3
+ * session id: YYYY-MM-DD-session-<3-6 chars lowercase alnum>
4
+ * change id: kebab-case
5
+ *
6
+ * Spec §8.7 — bare forms (sid-3 / sid-h / sid-r / unknown-sid) are
7
+ * migrated to `_archive/invalid-sids/`, NOT tolerated.
8
+ */
9
+ export const SID_FORMAT_DESCRIPTION = '<YYYY-MM-DD>-session-<3-6 chars lowercase alnum>, e.g. 2026-06-11-session-abc123';
10
+ const VALID_SID_REGEX = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])-session-[0-9a-z]{3,6}$/;
11
+ const BARE_SID_REGEX = /^(sid-[a-z0-9]+|unknown-sid)$/;
12
+ const VALID_CHANGE_ID_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
13
+ export function isValidSessionId(sid) {
14
+ return VALID_SID_REGEX.test(sid);
15
+ }
16
+ export function isValidChangeId(cid) {
17
+ return VALID_CHANGE_ID_REGEX.test(cid);
18
+ }
19
+ export function isBareSid(name) {
20
+ return BARE_SID_REGEX.test(name);
21
+ }
22
+ export function assertValidSessionId(sid) {
23
+ if (!isValidSessionId(sid)) {
24
+ throw new Error(`NAMING_INVALID: session id "${sid}" does not match required format ${SID_FORMAT_DESCRIPTION}`);
25
+ }
26
+ }
27
+ export function assertValidChangeId(cid) {
28
+ if (!isValidChangeId(cid)) {
29
+ throw new Error(`NAMING_INVALID: change id "${cid}" must be kebab-case (lowercase alnum and dashes only)`);
30
+ }
31
+ }
@@ -0,0 +1,19 @@
1
+ export interface ArchivePlan {
2
+ sid: string;
3
+ sourcePath: string;
4
+ targetPath: string;
5
+ sourceExists: boolean;
6
+ }
7
+ export interface ArchiveOptions {
8
+ sid: string;
9
+ apply: boolean;
10
+ }
11
+ export interface ArchiveResult {
12
+ moved: string[];
13
+ skipped: {
14
+ sid: string;
15
+ reason: string;
16
+ }[];
17
+ }
18
+ export declare function planArchive(projectRoot: string, sid: string): ArchivePlan;
19
+ export declare function archiveSession(projectRoot: string, options: ArchiveOptions): ArchiveResult;
@@ -0,0 +1,32 @@
1
+ import { existsSync, mkdirSync, renameSync } from 'node:fs';
2
+ import { join, sep } from 'node:path';
3
+ import { assertValidSessionId } from './sid-naming-guard.js';
4
+ const ARCHIVE_ROOT = '_archive';
5
+ const RUNTIME_DIR = '_runtime';
6
+ function toPosix(p) {
7
+ return p.split(sep).join('/');
8
+ }
9
+ export function planArchive(projectRoot, sid) {
10
+ assertValidSessionId(sid);
11
+ const yyyyMm = sid.slice(0, 7);
12
+ const sourcePath = join(projectRoot, '.peaks', RUNTIME_DIR, sid);
13
+ const targetPath = join(projectRoot, '.peaks', ARCHIVE_ROOT, yyyyMm, sid);
14
+ return {
15
+ sid,
16
+ sourcePath: toPosix(sourcePath),
17
+ targetPath: toPosix(targetPath),
18
+ sourceExists: existsSync(sourcePath),
19
+ };
20
+ }
21
+ export function archiveSession(projectRoot, options) {
22
+ const plan = planArchive(projectRoot, options.sid);
23
+ if (!plan.sourceExists) {
24
+ return { moved: [], skipped: [{ sid: options.sid, reason: 'source does not exist' }] };
25
+ }
26
+ if (!options.apply) {
27
+ return { moved: [], skipped: [{ sid: options.sid, reason: 'dry-run' }] };
28
+ }
29
+ mkdirSync(join(plan.targetPath, '..'), { recursive: true });
30
+ renameSync(plan.sourcePath, plan.targetPath);
31
+ return { moved: [options.sid], skipped: [] };
32
+ }
@@ -0,0 +1,41 @@
1
+ export interface RuntimeSessionInfo {
2
+ sid: string;
3
+ mtimeMs: number;
4
+ ageHours: number;
5
+ }
6
+ export interface CleanupOptions {
7
+ olderThanHours: number;
8
+ graceHours: number;
9
+ }
10
+ export interface CleanupResult {
11
+ deleted: string[];
12
+ skipped: {
13
+ sid: string;
14
+ reason: string;
15
+ }[];
16
+ }
17
+ export declare function runtimeDirPath(projectRoot: string): string;
18
+ export declare function listRuntimeSessions(projectRoot: string): RuntimeSessionInfo[];
19
+ export declare function planRuntimeCleanup(sessions: RuntimeSessionInfo[], options: CleanupOptions): {
20
+ eligible: string[];
21
+ skipped: {
22
+ sid: string;
23
+ reason: string;
24
+ }[];
25
+ };
26
+ export declare function executeRuntimeCleanup(projectRoot: string, options: CleanupOptions & {
27
+ apply: boolean;
28
+ }): CleanupResult;
29
+ export interface SubAgentInvalidPlan {
30
+ invalid: string[];
31
+ invalidSidFormat: string[];
32
+ }
33
+ export declare function subAgentDirPath(projectRoot: string): string;
34
+ export declare function invalidSidsArchivePath(projectRoot: string): string;
35
+ export declare function listInvalidSubAgentSids(projectRoot: string): string[];
36
+ export declare function executeSubAgentClean(projectRoot: string, options: {
37
+ apply: boolean;
38
+ }): {
39
+ moved: string[];
40
+ skipped: string[];
41
+ };
@@ -0,0 +1,86 @@
1
+ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { isBareSid, isValidSessionId } from './sid-naming-guard.js';
4
+ const RUNTIME_DIR = '_runtime';
5
+ export function runtimeDirPath(projectRoot) {
6
+ return join(projectRoot, '.peaks', RUNTIME_DIR);
7
+ }
8
+ export function listRuntimeSessions(projectRoot) {
9
+ const dir = runtimeDirPath(projectRoot);
10
+ if (!existsSync(dir))
11
+ return [];
12
+ const now = Date.now();
13
+ return readdirSync(dir, { withFileTypes: true })
14
+ .filter((e) => e.isDirectory())
15
+ .map((e) => {
16
+ const sid = e.name;
17
+ const fullPath = join(dir, sid);
18
+ const stat = statSync(fullPath);
19
+ const ageHours = (now - stat.mtimeMs) / (1000 * 3600);
20
+ return { sid, mtimeMs: stat.mtimeMs, ageHours };
21
+ });
22
+ }
23
+ export function planRuntimeCleanup(sessions, options) {
24
+ const eligible = [];
25
+ const skipped = [];
26
+ const cutoffHours = options.olderThanHours + options.graceHours;
27
+ for (const s of sessions) {
28
+ if (s.ageHours >= cutoffHours) {
29
+ eligible.push(s.sid);
30
+ }
31
+ else {
32
+ skipped.push({ sid: s.sid, reason: `fresh: age=${s.ageHours.toFixed(1)}h < cutoff=${cutoffHours}h` });
33
+ }
34
+ }
35
+ return { eligible, skipped };
36
+ }
37
+ export function executeRuntimeCleanup(projectRoot, options) {
38
+ const sessions = listRuntimeSessions(projectRoot);
39
+ const plan = planRuntimeCleanup(sessions, options);
40
+ if (options.apply) {
41
+ const dir = runtimeDirPath(projectRoot);
42
+ for (const sid of plan.eligible) {
43
+ rmSync(join(dir, sid), { recursive: true, force: true });
44
+ }
45
+ }
46
+ return { deleted: plan.eligible, skipped: plan.skipped };
47
+ }
48
+ const SUBAGENT_DIR = '_sub_agents';
49
+ const INVALID_ARCHIVE = '_archive/invalid-sids';
50
+ export function subAgentDirPath(projectRoot) {
51
+ return join(projectRoot, '.peaks', SUBAGENT_DIR);
52
+ }
53
+ export function invalidSidsArchivePath(projectRoot) {
54
+ return join(projectRoot, '.peaks', INVALID_ARCHIVE);
55
+ }
56
+ export function listInvalidSubAgentSids(projectRoot) {
57
+ const dir = subAgentDirPath(projectRoot);
58
+ if (!existsSync(dir))
59
+ return [];
60
+ return readdirSync(dir, { withFileTypes: true })
61
+ .filter((e) => e.isDirectory())
62
+ .map((e) => e.name)
63
+ .filter((name) => isBareSid(name) || !isValidSessionId(name));
64
+ }
65
+ export function executeSubAgentClean(projectRoot, options) {
66
+ const invalid = listInvalidSubAgentSids(projectRoot);
67
+ const moved = [];
68
+ if (options.apply && invalid.length > 0) {
69
+ const archiveDir = invalidSidsArchivePath(projectRoot);
70
+ mkdirSync(archiveDir, { recursive: true });
71
+ for (const sid of invalid) {
72
+ const from = join(subAgentDirPath(projectRoot), sid);
73
+ const to = join(archiveDir, sid);
74
+ if (existsSync(to)) {
75
+ // collision — append timestamp suffix
76
+ const stamped = `${sid}-${Date.now()}`;
77
+ renameSync(from, join(archiveDir, stamped));
78
+ }
79
+ else {
80
+ renameSync(from, to);
81
+ }
82
+ moved.push(sid);
83
+ }
84
+ }
85
+ return { moved: options.apply ? moved : invalid, skipped: [] };
86
+ }
@@ -0,0 +1,7 @@
1
+ export declare function isLegacyDecisionDotfile(name: string): boolean;
2
+ export declare function stateDirPath(projectRoot: string): string;
3
+ export interface CollectResult {
4
+ moved: string[];
5
+ skipped: string[];
6
+ }
7
+ export declare function collectLegacyDecisionDotfiles(projectRoot: string): CollectResult;