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,40 @@
1
+ /**
2
+ * login-gate enforcer (L2.2 P1) — verifies destructive / auth-required
3
+ * paths require explicit user confirmation.
4
+ *
5
+ * Two red lines:
6
+ * - rl-login-gate-001: destructive paths (uninstall, drop, force-push) must
7
+ * require user confirmation
8
+ * - rl-login-gate-002: protected paths (auth-required) must check session
9
+ *
10
+ * This is a static-check enforcer: the actual runtime gate is in
11
+ * `peaks mode-enforcement` (the requireUserConfirmation function). The
12
+ * catalog entry points to this file; the audit flags it as cli-backed
13
+ * when the integration is wired (L2.2 ships the wiring).
14
+ */
15
+ const DESTRUCTIVE_PATH_PATTERNS = [
16
+ /uninstall/i,
17
+ /\bdrop\b/i,
18
+ /force-push/i,
19
+ /--force\b/,
20
+ /--hard\b/,
21
+ /rm\s+-rf?\b/,
22
+ ];
23
+ const PROTECTED_PATH_PATTERNS = [
24
+ /auth/i,
25
+ /login/i,
26
+ /session/i,
27
+ ];
28
+ export function checkLoginGate(input) {
29
+ for (const pattern of DESTRUCTIVE_PATH_PATTERNS) {
30
+ if (pattern.test(input.command)) {
31
+ return { destructive: true, protected: false, matchedPattern: pattern.source };
32
+ }
33
+ }
34
+ for (const pattern of PROTECTED_PATH_PATTERNS) {
35
+ if (pattern.test(input.command)) {
36
+ return { destructive: false, protected: true, matchedPattern: pattern.source };
37
+ }
38
+ }
39
+ return { destructive: false, protected: false, matchedPattern: null };
40
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * mock-placement enforcer — scans changed files for inline mock-data
3
+ * patterns and fails the slice check if any changed file under `src/` or
4
+ * `skills/` contains `mockData: { ... }`, `fixtures = { ... }`, or a
5
+ * multi-line `const fooMock = { ... }` literal.
6
+ *
7
+ * Per L2 redesign §5.4. Mocks belong in `tests/fixtures/`, not inline.
8
+ * Per `references/mock-data-placement.md` from peaks-rd: framework-aware
9
+ * mock placement. peaks-cli has no UI framework, but the rule still
10
+ * applies for non-fixture files.
11
+ */
12
+ export interface MockPlacementCheckInput {
13
+ readonly filePath: string;
14
+ readonly content: string;
15
+ }
16
+ export interface MockPlacementViolation {
17
+ readonly filePath: string;
18
+ readonly pattern: string;
19
+ readonly snippet: string;
20
+ }
21
+ export declare function hasInlineMock(content: string): MockPlacementViolation | null;
22
+ export declare function findMockViolations(changedFiles: readonly {
23
+ filePath: string;
24
+ content: string;
25
+ }[]): readonly MockPlacementViolation[];
@@ -0,0 +1,48 @@
1
+ /**
2
+ * mock-placement enforcer — scans changed files for inline mock-data
3
+ * patterns and fails the slice check if any changed file under `src/` or
4
+ * `skills/` contains `mockData: { ... }`, `fixtures = { ... }`, or a
5
+ * multi-line `const fooMock = { ... }` literal.
6
+ *
7
+ * Per L2 redesign §5.4. Mocks belong in `tests/fixtures/`, not inline.
8
+ * Per `references/mock-data-placement.md` from peaks-rd: framework-aware
9
+ * mock placement. peaks-cli has no UI framework, but the rule still
10
+ * applies for non-fixture files.
11
+ */
12
+ const MOCK_PATTERNS = [
13
+ /\bmockData\s*[:=]\s*\{/,
14
+ /\bfixtures?\s*=\s*\{/,
15
+ /const\s+\w*[Mm]ock\w*\s*=\s*\{[\s\S]{20,}/,
16
+ ];
17
+ export function hasInlineMock(content) {
18
+ for (const pattern of MOCK_PATTERNS) {
19
+ const match = pattern.exec(content);
20
+ if (match) {
21
+ return {
22
+ filePath: '',
23
+ pattern: pattern.source,
24
+ snippet: match[0].slice(0, 80),
25
+ };
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ export function findMockViolations(changedFiles) {
31
+ const violations = [];
32
+ for (const { filePath, content } of changedFiles) {
33
+ // Mocks are allowed in tests/fixtures/. The slice check is invoked
34
+ // with the diff-vs-scope output, which already filters out
35
+ // test/fixture paths; this guard is a safety net.
36
+ if (filePath.includes('tests/fixtures/'))
37
+ continue;
38
+ if (filePath.includes('__fixtures__'))
39
+ continue;
40
+ if (!filePath.startsWith('src/') && !filePath.startsWith('skills/'))
41
+ continue;
42
+ const violation = hasInlineMock(content);
43
+ if (violation) {
44
+ violations.push({ ...violation, filePath });
45
+ }
46
+ }
47
+ return violations;
48
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * no-root-pollution enforcer — PreToolUse Write/Edit guard.
3
+ *
4
+ * Per L2 redesign §5.4. Deny writes to <project>/root for files NOT in
5
+ * the documented allowlist. The allowlist is hand-maintained for v1; a
6
+ * follow-up slice can expose it via `peaks standards`.
7
+ *
8
+ * Trust red line: this hook MUST fail-open on registry / FS errors. The
9
+ * LLM is never bricked by a peaks bug.
10
+ */
11
+ export interface RootWriteCheckInput {
12
+ readonly projectRoot: string;
13
+ readonly filePath: string;
14
+ }
15
+ export interface RootWriteCheckResult {
16
+ readonly isRoot: boolean;
17
+ readonly allowed: boolean;
18
+ readonly topSegment: string;
19
+ readonly denyReason: string;
20
+ }
21
+ export declare function isRootWrite(input: RootWriteCheckInput): RootWriteCheckResult;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * no-root-pollution enforcer — PreToolUse Write/Edit guard.
3
+ *
4
+ * Per L2 redesign §5.4. Deny writes to <project>/root for files NOT in
5
+ * the documented allowlist. The allowlist is hand-maintained for v1; a
6
+ * follow-up slice can expose it via `peaks standards`.
7
+ *
8
+ * Trust red line: this hook MUST fail-open on registry / FS errors. The
9
+ * LLM is never bricked by a peaks bug.
10
+ */
11
+ import { resolve } from 'node:path';
12
+ const ROOT_FILE_ALLOWLIST = new Set([
13
+ // Top-level docs
14
+ 'README.md', 'README-en.md', 'LICENSE', 'LICENSE.md', 'NOTICE', 'CONTRIBUTING.md',
15
+ 'CHANGELOG.md', 'AUTHORS', 'CONTRIBUTORS',
16
+ // Build / package manifests
17
+ 'package.json', 'pnpm-lock.yaml', 'pnpm-workspace.yaml', 'tsconfig.json',
18
+ 'tsconfig.*.json', 'vitest.config.ts', 'vite.config.ts', '.npmrc', '.nvmrc',
19
+ // VCS / editor
20
+ '.gitignore', '.gitattributes', '.editorconfig', '.gitkeep',
21
+ // Project-local config dirs (peaks-cli convention)
22
+ 'openspec', '.peaks', '.claude', '.peaksrc',
23
+ // Source dirs (writes into them are normal)
24
+ 'src', 'tests', 'bin', 'scripts', 'schemas', 'output-styles', 'docs',
25
+ // Skills are allowed at root
26
+ 'skills',
27
+ // Generated / ignored
28
+ 'dist', 'node_modules', 'coverage', '.nyc_output',
29
+ ]);
30
+ export function isRootWrite(input) {
31
+ const projectRoot = resolve(input.projectRoot);
32
+ const filePath = resolve(input.filePath);
33
+ const rel = filePath.startsWith(projectRoot)
34
+ ? filePath.slice(projectRoot.length).replace(/^[\\/]+/, '')
35
+ : filePath;
36
+ const topSegment = rel.split(/[\\/]/)[0] ?? '';
37
+ // If file is NOT at root (e.g. src/foo/bar.ts), the top segment is "src"
38
+ // which is in the allowlist. This handler only flags FILES AT THE ROOT
39
+ // (top-level), so check whether the file is exactly at root depth.
40
+ const segments = rel.split(/[\\/]/).filter(Boolean);
41
+ const isAtRoot = segments.length === 1;
42
+ if (!isAtRoot) {
43
+ return { isRoot: false, allowed: true, topSegment, denyReason: '' };
44
+ }
45
+ if (ROOT_FILE_ALLOWLIST.has(topSegment)) {
46
+ return { isRoot: true, allowed: true, topSegment, denyReason: '' };
47
+ }
48
+ return {
49
+ isRoot: true,
50
+ allowed: false,
51
+ topSegment,
52
+ denyReason: `no-root-pollution: file "${rel}" is not in the root allowlist. ` +
53
+ `Move it under docs/, tests/, skills/, or another documented directory, ` +
54
+ `or add it to ROOT_FILE_ALLOWLIST in src/services/audit/enforcers/no-root-pollution.ts.`,
55
+ };
56
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * pre-rd-scan enforcer (L2.2 P1) — verifies the project has been scanned
3
+ * before the rd implementation phase begins.
4
+ *
5
+ * Two red lines:
6
+ * - rl-pre-rd-scan-001: peaks scan archetype must have been run
7
+ * - rl-pre-rd-scan-002: peaks standards preflight must have been run
8
+ *
9
+ * Detected by checking for the project-scan.md and standards reports in
10
+ * the session dir. Both are produced by the pre-RD workflow.
11
+ */
12
+ export interface PreRdScanInput {
13
+ readonly projectRoot: string;
14
+ readonly sessionId: string;
15
+ }
16
+ export interface PreRdScanResult {
17
+ readonly archetypeScanned: boolean;
18
+ readonly archetypeReportPath: string;
19
+ readonly standardsPreflightDone: boolean;
20
+ readonly standardsReportPath: string;
21
+ }
22
+ export declare function checkPreRdScan(input: PreRdScanInput): PreRdScanResult;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * pre-rd-scan enforcer (L2.2 P1) — verifies the project has been scanned
3
+ * before the rd implementation phase begins.
4
+ *
5
+ * Two red lines:
6
+ * - rl-pre-rd-scan-001: peaks scan archetype must have been run
7
+ * - rl-pre-rd-scan-002: peaks standards preflight must have been run
8
+ *
9
+ * Detected by checking for the project-scan.md and standards reports in
10
+ * the session dir. Both are produced by the pre-RD workflow.
11
+ */
12
+ import { existsSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ export function checkPreRdScan(input) {
15
+ const archetypeReportPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'rd/project-scan.md');
16
+ const standardsReportPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'standards-preflight.json');
17
+ return {
18
+ archetypeScanned: existsSync(archetypeReportPath),
19
+ archetypeReportPath,
20
+ standardsPreflightDone: existsSync(standardsReportPath),
21
+ standardsReportPath,
22
+ };
23
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * prototype-fidelity enforcer (L2.2 P1) — verifies a prototype is
3
+ * functional (not a stub).
4
+ *
5
+ * Two red lines:
6
+ * - rl-prototype-fidelity-001: prototype files must not contain TODO/FIXME/XXX
7
+ * - rl-prototype-fidelity-002: prototype must have at least 1 passing test
8
+ *
9
+ * (L2.2 ships the source-level check; deeper test-running integration is
10
+ * deferred to a follow-up slice.)
11
+ */
12
+ export interface PrototypeFidelityInput {
13
+ readonly projectRoot: string;
14
+ readonly filePaths: readonly string[];
15
+ }
16
+ export interface PrototypeFidelityResult {
17
+ readonly stubMarkers: readonly {
18
+ filePath: string;
19
+ pattern: string;
20
+ snippet: string;
21
+ }[];
22
+ readonly testFiles: readonly string[];
23
+ }
24
+ export declare function findStubMarkers(input: PrototypeFidelityInput): PrototypeFidelityResult;
25
+ export declare function findTestFiles(projectRoot: string, sourceDir: string): readonly string[];
@@ -0,0 +1,75 @@
1
+ /**
2
+ * prototype-fidelity enforcer (L2.2 P1) — verifies a prototype is
3
+ * functional (not a stub).
4
+ *
5
+ * Two red lines:
6
+ * - rl-prototype-fidelity-001: prototype files must not contain TODO/FIXME/XXX
7
+ * - rl-prototype-fidelity-002: prototype must have at least 1 passing test
8
+ *
9
+ * (L2.2 ships the source-level check; deeper test-running integration is
10
+ * deferred to a follow-up slice.)
11
+ */
12
+ import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+ const STUB_MARKER_PATTERNS = [
15
+ /\bTODO\b/,
16
+ /\bFIXME\b/,
17
+ /\bXXX\b/,
18
+ /\bHACK\b/,
19
+ /\bstub\b\s*[:=]/i,
20
+ /\bnot implemented\b/i,
21
+ ];
22
+ export function findStubMarkers(input) {
23
+ const markers = [];
24
+ for (const filePath of input.filePaths) {
25
+ const abs = join(input.projectRoot, filePath);
26
+ let content;
27
+ try {
28
+ content = readFileSync(abs, 'utf8');
29
+ }
30
+ catch {
31
+ continue;
32
+ }
33
+ for (const pattern of STUB_MARKER_PATTERNS) {
34
+ const match = pattern.exec(content);
35
+ if (match !== null) {
36
+ markers.push({ filePath, pattern: pattern.source, snippet: match[0] });
37
+ }
38
+ }
39
+ }
40
+ return { stubMarkers: markers, testFiles: [] };
41
+ }
42
+ export function findTestFiles(projectRoot, sourceDir) {
43
+ const testsDir = join(projectRoot, sourceDir.replace(/^src\//, 'tests/'));
44
+ if (!existsSync(testsDir))
45
+ return [];
46
+ try {
47
+ const stat = statSync(testsDir);
48
+ if (!stat.isDirectory())
49
+ return [];
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ const result = [];
55
+ const walk = (dir) => {
56
+ let entries;
57
+ try {
58
+ entries = readdirSync(dir, { withFileTypes: true });
59
+ }
60
+ catch {
61
+ return;
62
+ }
63
+ for (const entry of entries) {
64
+ const full = join(dir, entry.name);
65
+ if (entry.isDirectory()) {
66
+ walk(full);
67
+ }
68
+ else if (entry.isFile() && /\.(test|spec)\.ts$/.test(entry.name)) {
69
+ result.push(full);
70
+ }
71
+ }
72
+ };
73
+ walk(testsDir);
74
+ return result;
75
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * resume-detection enforcer (L2.2 P1) — verifies a slice can be resumed
3
+ * before the LLM continues work on it.
4
+ *
5
+ * Two red lines:
6
+ * - rl-resume-detection-001: session-binding file must exist
7
+ * - rl-resume-detection-002: existing rd request state must be in
8
+ * {spec-locked, implemented, qa-handoff} (resumable states)
9
+ */
10
+ export interface ResumeDetectionInput {
11
+ readonly projectRoot: string;
12
+ readonly sessionId: string;
13
+ }
14
+ export interface ResumeDetectionResult {
15
+ readonly sessionBindingExists: boolean;
16
+ readonly sessionBindingPath: string;
17
+ readonly requestState: string | null;
18
+ readonly requestStatePath: string;
19
+ readonly canResume: boolean;
20
+ }
21
+ export declare function checkResume(input: ResumeDetectionInput): ResumeDetectionResult;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * resume-detection enforcer (L2.2 P1) — verifies a slice can be resumed
3
+ * before the LLM continues work on it.
4
+ *
5
+ * Two red lines:
6
+ * - rl-resume-detection-001: session-binding file must exist
7
+ * - rl-resume-detection-002: existing rd request state must be in
8
+ * {spec-locked, implemented, qa-handoff} (resumable states)
9
+ */
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ const RESUMABLE_STATES = new Set(['spec-locked', 'implemented', 'qa-handoff']);
13
+ export function checkResume(input) {
14
+ const sessionBindingPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'session.json');
15
+ const sessionBindingExists = existsSync(sessionBindingPath);
16
+ // The rd request artifact lives under .peaks/_runtime/<sid>/rd/requests/<rid>.md
17
+ // (canonical session layout per `src-services-session-canonical-workspace-resolver.md`).
18
+ // We look at the most-recent rd request to detect state. The full request-artifact
19
+ // service is the authoritative source; this is a lightweight check for the audit
20
+ // scanner.
21
+ const rdDir = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'rd/requests');
22
+ let requestState = null;
23
+ let requestStatePath = '';
24
+ if (existsSync(rdDir)) {
25
+ try {
26
+ const files = require('node:fs').readdirSync(rdDir);
27
+ for (const file of files) {
28
+ if (!file.endsWith('.md'))
29
+ continue;
30
+ const path = join(rdDir, file);
31
+ const content = readFileSync(path, 'utf8');
32
+ const match = /state:\s*([a-z0-9-]+)/i.exec(content);
33
+ if (match !== null) {
34
+ requestState = match[1] ?? null;
35
+ requestStatePath = path;
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ catch {
41
+ // ignore read errors
42
+ }
43
+ }
44
+ const canResume = sessionBindingExists && requestState !== null && RESUMABLE_STATES.has(requestState);
45
+ return {
46
+ sessionBindingExists,
47
+ sessionBindingPath,
48
+ requestState,
49
+ requestStatePath,
50
+ canResume,
51
+ };
52
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Solo-code-ban enforcer — PreToolUse Bash guard.
3
+ *
4
+ * Per L2 redesign §5.4. Deny `git commit` or `git apply` invocations from
5
+ * a peaks-* skill. The Solo Code-Change Red Line says peaks-solo / peaks-rd
6
+ * are orchestrators, not implementers; the actual `git commit` step must
7
+ * go through `peaks request transition`, which itself enforces spec-locked
8
+ * + tech-doc-presence.
9
+ *
10
+ * Trust red line (per `gate-enforcement-hook.md`): if the registry or
11
+ * manifest read fails, the hook must fail-OPEN (warn + allow). The LLM is
12
+ * never bricked by a peaks bug.
13
+ */
14
+ export interface SoloCodeBanInput {
15
+ readonly skill: string;
16
+ readonly command: string;
17
+ }
18
+ export interface SoloCodeBanResult {
19
+ readonly denied: boolean;
20
+ readonly reason: string;
21
+ }
22
+ export declare function isSoloCodeCommit(skill: string, command: string): boolean;
23
+ export declare function evaluateSoloCodeBan(input: SoloCodeBanInput): SoloCodeBanResult;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Solo-code-ban enforcer — PreToolUse Bash guard.
3
+ *
4
+ * Per L2 redesign §5.4. Deny `git commit` or `git apply` invocations from
5
+ * a peaks-* skill. The Solo Code-Change Red Line says peaks-solo / peaks-rd
6
+ * are orchestrators, not implementers; the actual `git commit` step must
7
+ * go through `peaks request transition`, which itself enforces spec-locked
8
+ * + tech-doc-presence.
9
+ *
10
+ * Trust red line (per `gate-enforcement-hook.md`): if the registry or
11
+ * manifest read fails, the hook must fail-OPEN (warn + allow). The LLM is
12
+ * never bricked by a peaks bug.
13
+ */
14
+ const COMMIT_APPLY_PATTERN = /^\s*git\s+(commit|apply)\b/;
15
+ const DENY_REASON = 'Solo Code-Change Red Line: peaks-* skills must go through peaks-solo / peaks-rd. ' +
16
+ 'Use `peaks request transition` instead of `git commit` / `git apply` directly.';
17
+ export function isSoloCodeCommit(skill, command) {
18
+ if (!skill.startsWith('peaks-'))
19
+ return false;
20
+ return COMMIT_APPLY_PATTERN.test(command);
21
+ }
22
+ export function evaluateSoloCodeBan(input) {
23
+ if (isSoloCodeCommit(input.skill, input.command)) {
24
+ return { denied: true, reason: DENY_REASON };
25
+ }
26
+ return { denied: false, reason: '' };
27
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * sub-agent-sid enforcer — dogfood of Slice 0.5 sid-naming-guard.
3
+ *
4
+ * Per L2 redesign §5.4, "sub-agent-sid" is one of the 5 P0 red lines.
5
+ * Slice 0.5 (Task 7) shipped `isValidSessionId` in
6
+ * `src/services/workspace/sid-naming-guard.ts`. This enforcer exposes the
7
+ * same check to the red-line audit framework, so any invalid sid under
8
+ * `.peaks/_sub_agents/` shows up as a backable red line in the audit.
9
+ */
10
+ export interface SubAgentSidCheckResult {
11
+ readonly invalid: readonly string[];
12
+ readonly valid: readonly string[];
13
+ readonly scanned: boolean;
14
+ }
15
+ /**
16
+ * Find sids under `.peaks/_sub_agents/<sid>/` that fail `isValidSessionId`.
17
+ * Bare forms (sid-3, unknown-sid) and date-mismatched sids all fail.
18
+ */
19
+ export declare function findInvalidSubAgentSids(projectRoot: string): SubAgentSidCheckResult;
20
+ /**
21
+ * Same check, for `.peaks/_runtime/<sid>/`. Slice 0.5's clean-service
22
+ * already handles the sub-agents dir; the audit framework also wants to
23
+ * know about invalid runtime sids (which would be a different failure mode).
24
+ */
25
+ export declare function findInvalidRuntimeSids(projectRoot: string): SubAgentSidCheckResult;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * sub-agent-sid enforcer — dogfood of Slice 0.5 sid-naming-guard.
3
+ *
4
+ * Per L2 redesign §5.4, "sub-agent-sid" is one of the 5 P0 red lines.
5
+ * Slice 0.5 (Task 7) shipped `isValidSessionId` in
6
+ * `src/services/workspace/sid-naming-guard.ts`. This enforcer exposes the
7
+ * same check to the red-line audit framework, so any invalid sid under
8
+ * `.peaks/_sub_agents/` shows up as a backable red line in the audit.
9
+ */
10
+ import { existsSync, readdirSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { isValidSessionId } from '../../workspace/sid-naming-guard.js';
13
+ const SUB_AGENTS_DIR = '.peaks/_sub_agents';
14
+ const RUNTIME_DIR = '.peaks/_runtime';
15
+ /**
16
+ * Find sids under `.peaks/_sub_agents/<sid>/` that fail `isValidSessionId`.
17
+ * Bare forms (sid-3, unknown-sid) and date-mismatched sids all fail.
18
+ */
19
+ export function findInvalidSubAgentSids(projectRoot) {
20
+ const subAgentsRoot = join(projectRoot, SUB_AGENTS_DIR);
21
+ if (!existsSync(subAgentsRoot)) {
22
+ return { invalid: [], valid: [], scanned: false };
23
+ }
24
+ const entries = readdirSync(subAgentsRoot, { withFileTypes: true })
25
+ .filter((e) => e.isDirectory())
26
+ .map((e) => e.name);
27
+ const invalid = [];
28
+ const valid = [];
29
+ for (const name of entries) {
30
+ if (isValidSessionId(name)) {
31
+ valid.push(name);
32
+ }
33
+ else {
34
+ invalid.push(name);
35
+ }
36
+ }
37
+ return { invalid, valid, scanned: true };
38
+ }
39
+ /**
40
+ * Same check, for `.peaks/_runtime/<sid>/`. Slice 0.5's clean-service
41
+ * already handles the sub-agents dir; the audit framework also wants to
42
+ * know about invalid runtime sids (which would be a different failure mode).
43
+ */
44
+ export function findInvalidRuntimeSids(projectRoot) {
45
+ const runtimeRoot = join(projectRoot, RUNTIME_DIR);
46
+ if (!existsSync(runtimeRoot)) {
47
+ return { invalid: [], valid: [], scanned: false };
48
+ }
49
+ const entries = readdirSync(runtimeRoot, { withFileTypes: true })
50
+ .filter((e) => e.isDirectory())
51
+ .map((e) => e.name);
52
+ const invalid = [];
53
+ const valid = [];
54
+ for (const name of entries) {
55
+ if (isValidSessionId(name)) {
56
+ valid.push(name);
57
+ }
58
+ else {
59
+ invalid.push(name);
60
+ }
61
+ }
62
+ return { invalid, valid, scanned: true };
63
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * tech-doc-presence enforcer — refuses `peaks request transition <rid>
3
+ * spec-locked` if the slice's tech-doc.md is missing.
4
+ *
5
+ * Per L2 redesign §5.4. The tech-doc is the BLOCKING artifact for the
6
+ * spec-locked transition. This enforcer is called from
7
+ * `request-transition-service.ts` BEFORE the state machine runs.
8
+ *
9
+ * The session id is resolved from the current working directory using
10
+ * the canonical workspace resolver (per
11
+ * `src-services-session-canonical-workspace-resolver.md` memory).
12
+ */
13
+ export interface TechDocPresenceInput {
14
+ readonly projectRoot: string;
15
+ readonly sessionId: string;
16
+ }
17
+ export interface TechDocPresenceResult {
18
+ readonly exists: boolean;
19
+ readonly path: string;
20
+ readonly isEmpty: boolean;
21
+ }
22
+ export declare function checkTechDocPresence(input: TechDocPresenceInput): TechDocPresenceResult;
23
+ /**
24
+ * Hard-coded error message used by `request-transition-service.ts` when
25
+ * the spec-locked transition is refused.
26
+ */
27
+ export declare const TECH_DOC_MISSING_MESSAGE: string;
28
+ export declare const TECH_DOC_MISSING_CODE = "TECH_DOC_MISSING";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * tech-doc-presence enforcer — refuses `peaks request transition <rid>
3
+ * spec-locked` if the slice's tech-doc.md is missing.
4
+ *
5
+ * Per L2 redesign §5.4. The tech-doc is the BLOCKING artifact for the
6
+ * spec-locked transition. This enforcer is called from
7
+ * `request-transition-service.ts` BEFORE the state machine runs.
8
+ *
9
+ * The session id is resolved from the current working directory using
10
+ * the canonical workspace resolver (per
11
+ * `src-services-session-canonical-workspace-resolver.md` memory).
12
+ */
13
+ import { existsSync, statSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ export function checkTechDocPresence(input) {
16
+ const docPath = join(input.projectRoot, '.peaks/_runtime', input.sessionId, 'rd/tech-doc.md');
17
+ if (!existsSync(docPath)) {
18
+ return { exists: false, path: docPath, isEmpty: false };
19
+ }
20
+ let stat;
21
+ try {
22
+ stat = statSync(docPath);
23
+ }
24
+ catch {
25
+ return { exists: false, path: docPath, isEmpty: false };
26
+ }
27
+ return { exists: true, path: docPath, isEmpty: stat.size === 0 };
28
+ }
29
+ /**
30
+ * Hard-coded error message used by `request-transition-service.ts` when
31
+ * the spec-locked transition is refused.
32
+ */
33
+ export const TECH_DOC_MISSING_MESSAGE = 'spec-locked transition requires `rd/tech-doc.md` to exist and be non-empty. ' +
34
+ 'Run `peaks-rd` to produce the tech-doc first, then retry the transition.';
35
+ export const TECH_DOC_MISSING_CODE = 'TECH_DOC_MISSING';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Red-line catalog — P2-a entries (Slice #6 L2.3).
3
+ *
4
+ * These 25 entries close the lint-style gap left by L2.1 (P0) and
5
+ * L2.2 (P1). Per spec §5.4, P2-a targets 25-40 lint-style red-lines
6
+ * for SKILL.md, references/, and openspec/. They are small,
7
+ * pattern-based, and reference the existing CLI surface (no new
8
+ * runtime dependencies).
9
+ *
10
+ * Enforcer functions live in `enforcers/lint-style-*.ts` and are
11
+ * wired into the audit framework via the same `enforcerRef`
12
+ * discovery path as the P0 / P1 entries.
13
+ */
14
+ import type { RedLineCatalogEntry } from './red-line-catalog.js';
15
+ /**
16
+ * The 25 P2-a entries, in stable display order. Appending to a single
17
+ * readonly array keeps the catalog growable: future slices (L2.4, L3.x)
18
+ * can spread this list into RED_LINE_CATALOG and add their own without
19
+ * touching this file.
20
+ */
21
+ export declare const RED_LINE_CATALOG_P2_A: readonly RedLineCatalogEntry[];