peaks-loop 4.1.2 → 4.1.3
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.
- package/.claude-plugin/marketplace.json +136 -47
- package/CHANGELOG.md +56 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/contracts/readonly-argv-whitelist.json +211 -0
- package/dist/cli/commands/audit-artifact-runners.d.ts +16 -0
- package/dist/cli/commands/audit-artifact-runners.js +170 -0
- package/dist/cli/commands/audit-command-shared.d.ts +97 -0
- package/dist/cli/commands/audit-command-shared.js +60 -0
- package/dist/cli/commands/audit-commands.d.ts +7 -31
- package/dist/cli/commands/audit-commands.js +64 -473
- package/dist/cli/commands/audit-goal-runners.d.ts +17 -0
- package/dist/cli/commands/audit-goal-runners.js +114 -0
- package/dist/cli/commands/audit-red-lines-runners.d.ts +11 -0
- package/dist/cli/commands/audit-red-lines-runners.js +43 -0
- package/dist/cli/commands/audit-refusal-data.d.ts +23 -0
- package/dist/cli/commands/audit-refusal-data.js +69 -0
- package/dist/cli/commands/audit-static-runners.d.ts +13 -0
- package/dist/cli/commands/audit-static-runners.js +139 -0
- package/dist/cli/commands/await-runners.d.ts +11 -0
- package/dist/cli/commands/await-runners.js +148 -0
- package/dist/cli/commands/code-auto-compact-command.d.ts +3 -0
- package/dist/cli/commands/code-auto-compact-command.js +124 -0
- package/dist/cli/commands/code-auto-compact-envelope.d.ts +22 -0
- package/dist/cli/commands/code-auto-compact-envelope.js +39 -0
- package/dist/cli/commands/code-context-audit-command.d.ts +3 -0
- package/dist/cli/commands/code-context-audit-command.js +68 -0
- package/dist/cli/commands/code-context-now-command.d.ts +3 -0
- package/dist/cli/commands/code-context-now-command.js +124 -0
- package/dist/cli/commands/code-context-now-envelope.d.ts +66 -0
- package/dist/cli/commands/code-context-now-envelope.js +136 -0
- package/dist/cli/commands/code-context-now-mapping.d.ts +27 -0
- package/dist/cli/commands/code-context-now-mapping.js +43 -0
- package/dist/cli/commands/code-emit-handoff-command.d.ts +3 -0
- package/dist/cli/commands/code-emit-handoff-command.js +99 -0
- package/dist/cli/commands/code-gate-step-08-command.d.ts +3 -0
- package/dist/cli/commands/code-gate-step-08-command.js +139 -0
- package/dist/cli/commands/code-post-compact-detect-command.d.ts +3 -0
- package/dist/cli/commands/code-post-compact-detect-command.js +50 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +9 -43
- package/dist/cli/commands/code-runtime-commands.js +20 -672
- package/dist/cli/commands/code-runtime-session.d.ts +1 -0
- package/dist/cli/commands/code-runtime-session.js +15 -0
- package/dist/cli/commands/compact-command.d.ts +6 -2
- package/dist/cli/commands/compact-command.js +12 -559
- package/dist/cli/commands/compact-dry-run-command.d.ts +3 -0
- package/dist/cli/commands/compact-dry-run-command.js +87 -0
- package/dist/cli/commands/compact-force-command.d.ts +3 -0
- package/dist/cli/commands/compact-force-command.js +98 -0
- package/dist/cli/commands/compact-harness-window-command.d.ts +3 -0
- package/dist/cli/commands/compact-harness-window-command.js +174 -0
- package/dist/cli/commands/compact-history-command.d.ts +3 -0
- package/dist/cli/commands/compact-history-command.js +89 -0
- package/dist/cli/commands/compact-read-commands.d.ts +3 -0
- package/dist/cli/commands/compact-read-commands.js +123 -0
- package/dist/cli/commands/compact-settle-command.d.ts +3 -0
- package/dist/cli/commands/compact-settle-command.js +130 -0
- package/dist/cli/commands/compact-shared.d.ts +9 -0
- package/dist/cli/commands/compact-shared.js +26 -0
- package/dist/cli/commands/core/skill-command.d.ts +1 -44
- package/dist/cli/commands/core/skill-command.js +13 -541
- package/dist/cli/commands/core/skill-context-verdict.d.ts +38 -0
- package/dist/cli/commands/core/skill-context-verdict.js +63 -0
- package/dist/cli/commands/core/skill-generated-config-notice.d.ts +41 -0
- package/dist/cli/commands/core/skill-generated-config-notice.js +75 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-heartbeat-commands.js +50 -0
- package/dist/cli/commands/core/skill-presence-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-commands.js +123 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-presence-housekeeping-commands.js +165 -0
- package/dist/cli/commands/core/skill-registry-commands.d.ts +3 -0
- package/dist/cli/commands/core/skill-registry-commands.js +117 -0
- package/dist/cli/commands/core/skill-runbook-command.d.ts +3 -0
- package/dist/cli/commands/core/skill-runbook-command.js +29 -0
- package/dist/cli/commands/dispatch-command-spec.d.ts +2 -0
- package/dist/cli/commands/dispatch-command-spec.js +62 -0
- package/dist/cli/commands/dispatch-commands.js +159 -775
- package/dist/cli/commands/dispatch-detached-branch.d.ts +3 -0
- package/dist/cli/commands/dispatch-detached-branch.js +58 -0
- package/dist/cli/commands/dispatch-envelope.d.ts +36 -0
- package/dist/cli/commands/dispatch-envelope.js +104 -0
- package/dist/cli/commands/dispatch-isolation-lease.d.ts +17 -0
- package/dist/cli/commands/dispatch-isolation-lease.js +149 -0
- package/dist/cli/commands/dispatch-preflight.d.ts +7 -0
- package/dist/cli/commands/dispatch-preflight.js +105 -0
- package/dist/cli/commands/dispatch-prompt-blocks.d.ts +10 -0
- package/dist/cli/commands/dispatch-prompt-blocks.js +56 -0
- package/dist/cli/commands/dispatch-prompt-composition.d.ts +30 -0
- package/dist/cli/commands/dispatch-prompt-composition.js +171 -0
- package/dist/cli/commands/dispatch-record-write.d.ts +24 -0
- package/dist/cli/commands/dispatch-record-write.js +96 -0
- package/dist/cli/commands/dispatch-tool-call.d.ts +15 -0
- package/dist/cli/commands/dispatch-tool-call.js +128 -0
- package/dist/cli/commands/evolution-command-shared.d.ts +78 -0
- package/dist/cli/commands/evolution-command-shared.js +107 -0
- package/dist/cli/commands/evolution-commands.d.ts +11 -1
- package/dist/cli/commands/evolution-commands.js +19 -350
- package/dist/cli/commands/evolution-evaluate-command.d.ts +24 -0
- package/dist/cli/commands/evolution-evaluate-command.js +138 -0
- package/dist/cli/commands/evolution-propose-command.d.ts +23 -0
- package/dist/cli/commands/evolution-propose-command.js +145 -0
- package/dist/cli/commands/evolution-status-command.d.ts +17 -0
- package/dist/cli/commands/evolution-status-command.js +41 -0
- package/dist/cli/commands/evolution-verdict-commands.d.ts +30 -0
- package/dist/cli/commands/evolution-verdict-commands.js +100 -0
- package/dist/cli/commands/finalize-accumulator.d.ts +44 -0
- package/dist/cli/commands/finalize-accumulator.js +55 -0
- package/dist/cli/commands/finalize-branches.d.ts +34 -0
- package/dist/cli/commands/finalize-branches.js +119 -0
- package/dist/cli/commands/finalize-runners.d.ts +3 -0
- package/dist/cli/commands/finalize-runners.js +92 -0
- package/dist/cli/commands/finalize-selection.d.ts +65 -0
- package/dist/cli/commands/finalize-selection.js +35 -0
- package/dist/cli/commands/gate-commands.js +26 -8
- package/dist/cli/commands/job-checkpoint-commands.d.ts +3 -0
- package/dist/cli/commands/job-checkpoint-commands.js +157 -0
- package/dist/cli/commands/job-command-shared.d.ts +109 -0
- package/dist/cli/commands/job-command-shared.js +72 -0
- package/dist/cli/commands/job-commands.d.ts +4 -64
- package/dist/cli/commands/job-commands.js +42 -552
- package/dist/cli/commands/job-cost-check-command.d.ts +3 -0
- package/dist/cli/commands/job-cost-check-command.js +41 -0
- package/dist/cli/commands/job-init-status-commands.d.ts +3 -0
- package/dist/cli/commands/job-init-status-commands.js +149 -0
- package/dist/cli/commands/job-run-commands.d.ts +3 -0
- package/dist/cli/commands/job-run-commands.js +97 -0
- package/dist/cli/commands/job-state-root.d.ts +35 -0
- package/dist/cli/commands/job-state-root.js +76 -0
- package/dist/cli/commands/loop-eval-capture-index.d.ts +4 -0
- package/dist/cli/commands/loop-eval-capture-index.js +44 -0
- package/dist/cli/commands/loop-eval-commands.d.ts +8 -5
- package/dist/cli/commands/loop-eval-commands.js +12 -626
- package/dist/cli/commands/loop-eval-eval-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-eval-command.js +168 -0
- package/dist/cli/commands/loop-eval-monotonic-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-monotonic-command.js +90 -0
- package/dist/cli/commands/loop-eval-parents.d.ts +7 -0
- package/dist/cli/commands/loop-eval-parents.js +20 -0
- package/dist/cli/commands/loop-eval-run-code.d.ts +7 -0
- package/dist/cli/commands/loop-eval-run-code.js +40 -0
- package/dist/cli/commands/loop-eval-run-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-run-command.js +113 -0
- package/dist/cli/commands/loop-eval-spec-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-commands.js +165 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.d.ts +3 -0
- package/dist/cli/commands/loop-eval-spec-lint-command.js +42 -0
- package/dist/cli/commands/loop-eval-workflow-commands.d.ts +3 -0
- package/dist/cli/commands/loop-eval-workflow-commands.js +141 -0
- package/dist/cli/commands/mcp-commands.d.ts +10 -0
- package/dist/cli/commands/mcp-commands.js +157 -0
- package/dist/cli/commands/request-command-options.d.ts +49 -0
- package/dist/cli/commands/request-command-options.js +1 -0
- package/dist/cli/commands/request-commands.d.ts +3 -9
- package/dist/cli/commands/request-commands.js +17 -522
- package/dist/cli/commands/request-init-command.d.ts +3 -0
- package/dist/cli/commands/request-init-command.js +100 -0
- package/dist/cli/commands/request-lint-command.d.ts +3 -0
- package/dist/cli/commands/request-lint-command.js +50 -0
- package/dist/cli/commands/request-list-command.d.ts +10 -0
- package/dist/cli/commands/request-list-command.js +53 -0
- package/dist/cli/commands/request-repair-status-command.d.ts +3 -0
- package/dist/cli/commands/request-repair-status-command.js +61 -0
- package/dist/cli/commands/request-show-command.d.ts +3 -0
- package/dist/cli/commands/request-show-command.js +56 -0
- package/dist/cli/commands/request-transition-command.d.ts +3 -0
- package/dist/cli/commands/request-transition-command.js +122 -0
- package/dist/cli/commands/request-transition-failures.d.ts +8 -0
- package/dist/cli/commands/request-transition-failures.js +90 -0
- package/dist/cli/commands/request-transition-hooks.d.ts +50 -0
- package/dist/cli/commands/request-transition-hooks.js +115 -0
- package/dist/cli/commands/request-transition-preflight.d.ts +10 -0
- package/dist/cli/commands/request-transition-preflight.js +107 -0
- package/dist/cli/commands/scan-command-shared.d.ts +96 -0
- package/dist/cli/commands/scan-command-shared.js +83 -0
- package/dist/cli/commands/scan-commands.d.ts +3 -2
- package/dist/cli/commands/scan-commands.js +23 -463
- package/dist/cli/commands/scan-inventory-commands.d.ts +3 -0
- package/dist/cli/commands/scan-inventory-commands.js +96 -0
- package/dist/cli/commands/scan-project-commands.d.ts +3 -0
- package/dist/cli/commands/scan-project-commands.js +65 -0
- package/dist/cli/commands/scan-scope-commands.d.ts +3 -0
- package/dist/cli/commands/scan-scope-commands.js +149 -0
- package/dist/cli/commands/scan-scope-helpers.d.ts +8 -0
- package/dist/cli/commands/scan-scope-helpers.js +54 -0
- package/dist/cli/commands/scan-structural-commands.d.ts +3 -0
- package/dist/cli/commands/scan-structural-commands.js +113 -0
- package/dist/cli/commands/share-commands.d.ts +12 -68
- package/dist/cli/commands/share-commands.js +47 -556
- package/dist/cli/commands/share-error-hints.d.ts +14 -0
- package/dist/cli/commands/share-error-hints.js +38 -0
- package/dist/cli/commands/share-identity.d.ts +13 -0
- package/dist/cli/commands/share-identity.js +22 -0
- package/dist/cli/commands/share-record-paths.d.ts +37 -0
- package/dist/cli/commands/share-record-paths.js +68 -0
- package/dist/cli/commands/share-runners.d.ts +3 -0
- package/dist/cli/commands/share-runners.js +158 -0
- package/dist/cli/commands/shared-read-runners.d.ts +3 -0
- package/dist/cli/commands/shared-read-runners.js +51 -0
- package/dist/cli/commands/slice-check-command.d.ts +3 -0
- package/dist/cli/commands/slice-check-command.js +54 -0
- package/dist/cli/commands/slice-commands.d.ts +2 -22
- package/dist/cli/commands/slice-commands.js +18 -517
- package/dist/cli/commands/slice-decompose-artifacts.d.ts +18 -0
- package/dist/cli/commands/slice-decompose-artifacts.js +79 -0
- package/dist/cli/commands/slice-decompose-command.d.ts +3 -0
- package/dist/cli/commands/slice-decompose-command.js +123 -0
- package/dist/cli/commands/slice-list-commands.d.ts +3 -0
- package/dist/cli/commands/slice-list-commands.js +182 -0
- package/dist/cli/commands/slice-pick-command.d.ts +3 -0
- package/dist/cli/commands/slice-pick-command.js +60 -0
- package/dist/cli/commands/slice-plan-command.d.ts +24 -0
- package/dist/cli/commands/slice-plan-command.js +111 -0
- package/dist/cli/commands/workflow-commands.d.ts +10 -1
- package/dist/cli/commands/workflow-commands.js +6 -594
- package/dist/cli/commands/workflow-gate-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-gate-commands.js +116 -0
- package/dist/cli/commands/workflow-plan-helpers.d.ts +48 -0
- package/dist/cli/commands/workflow-plan-helpers.js +103 -0
- package/dist/cli/commands/workflow-planning-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-planning-commands.js +56 -0
- package/dist/cli/commands/workflow-swarm-actions.d.ts +7 -0
- package/dist/cli/commands/workflow-swarm-actions.js +109 -0
- package/dist/cli/commands/workflow-swarm-commands.d.ts +3 -0
- package/dist/cli/commands/workflow-swarm-commands.js +138 -0
- package/dist/cli/commands/workflow-tech-actions.d.ts +10 -0
- package/dist/cli/commands/workflow-tech-actions.js +150 -0
- package/dist/cli/commands/workspace/init-actions.d.ts +10 -0
- package/dist/cli/commands/workspace/init-actions.js +199 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.d.ts +10 -0
- package/dist/cli/commands/workspace/init-codegraph-stake.js +54 -0
- package/dist/cli/commands/workspace/init-command.d.ts +8 -124
- package/dist/cli/commands/workspace/init-command.js +59 -623
- package/dist/cli/commands/workspace/init-context.d.ts +58 -0
- package/dist/cli/commands/workspace/init-context.js +121 -0
- package/dist/cli/commands/workspace/init-envelope.d.ts +3 -0
- package/dist/cli/commands/workspace/init-envelope.js +34 -0
- package/dist/cli/commands/workspace/init-failures.d.ts +3 -0
- package/dist/cli/commands/workspace/init-failures.js +84 -0
- package/dist/cli/commands/workspace/init-hooks-decision.d.ts +24 -0
- package/dist/cli/commands/workspace/init-hooks-decision.js +189 -0
- package/dist/cli/commands/workspace/init-options.d.ts +102 -0
- package/dist/cli/commands/workspace/init-options.js +1 -0
- package/dist/cli/commands/worktree-lease-commands.d.ts +10 -6
- package/dist/cli/commands/worktree-lease-commands.js +42 -512
- package/dist/cli/commands/worktree-lease-gc-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-gc-command.js +117 -0
- package/dist/cli/commands/worktree-lease-git.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-git.js +48 -0
- package/dist/cli/commands/worktree-lease-list-command.d.ts +12 -0
- package/dist/cli/commands/worktree-lease-list-command.js +100 -0
- package/dist/cli/commands/worktree-lease-load.d.ts +37 -0
- package/dist/cli/commands/worktree-lease-load.js +28 -0
- package/dist/cli/commands/worktree-lease-release-command.d.ts +13 -0
- package/dist/cli/commands/worktree-lease-release-command.js +105 -0
- package/dist/cli/commands/worktree-lease-renew-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-renew-command.js +99 -0
- package/dist/cli/commands/worktree-lease-session.d.ts +22 -0
- package/dist/cli/commands/worktree-lease-session.js +29 -0
- package/dist/cli/commands/worktree-lease-spawn-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-spawn-command.js +119 -0
- package/dist/cli/commands/worktree-lease-status-command.d.ts +11 -0
- package/dist/cli/commands/worktree-lease-status-command.js +86 -0
- package/dist/cli/commands/worktree-lease-store.d.ts +19 -0
- package/dist/cli/commands/worktree-lease-store.js +24 -0
- package/dist/cli/program.js +5 -0
- package/dist/services/distribution/mcp-install-runner.d.ts +21 -0
- package/dist/services/distribution/mcp-install-runner.js +59 -0
- package/dist/services/distribution/mcp-install.d.ts +95 -0
- package/dist/services/distribution/mcp-install.js +179 -0
- package/dist/services/distribution/plugin-manifest.d.ts +97 -0
- package/dist/services/distribution/plugin-manifest.js +126 -0
- package/dist/services/hooks/mcp-surface-gate.d.ts +19 -0
- package/dist/services/hooks/mcp-surface-gate.js +87 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +4 -35
- package/dist/services/ide/adapters/claude-code-adapter.js +48 -171
- package/dist/services/ide/adapters/claude-code-local-state.d.ts +28 -0
- package/dist/services/ide/adapters/claude-code-local-state.js +112 -0
- package/dist/services/ide/adapters/claude-code-model-window.d.ts +37 -0
- package/dist/services/ide/adapters/claude-code-model-window.js +113 -0
- package/dist/services/ide/ide-mcp-install-types.d.ts +78 -0
- package/dist/services/ide/ide-mcp-install-types.js +104 -0
- package/dist/services/ide/ide-types.d.ts +45 -1
- package/dist/services/ide/mcp-tool-matcher.d.ts +34 -0
- package/dist/services/ide/mcp-tool-matcher.js +84 -0
- package/dist/services/lint/npx-resolver.js +7 -0
- package/dist/services/mcp/cli-executor.d.ts +36 -0
- package/dist/services/mcp/cli-executor.js +97 -0
- package/dist/services/mcp/json-rpc.d.ts +81 -0
- package/dist/services/mcp/json-rpc.js +116 -0
- package/dist/services/mcp/memory-tool.d.ts +3 -0
- package/dist/services/mcp/memory-tool.js +62 -0
- package/dist/services/mcp/server-main.d.ts +1 -0
- package/dist/services/mcp/server-main.js +66 -0
- package/dist/services/mcp/server.d.ts +22 -0
- package/dist/services/mcp/server.js +152 -0
- package/dist/services/mcp/surface.d.ts +62 -0
- package/dist/services/mcp/surface.js +198 -0
- package/dist/services/mcp/tool-core.d.ts +42 -0
- package/dist/services/mcp/tool-core.js +137 -0
- package/dist/services/mcp/tools.d.ts +13 -0
- package/dist/services/mcp/tools.js +58 -0
- package/dist/services/readonly-surface/argv-guard.d.ts +32 -0
- package/dist/services/readonly-surface/argv-guard.js +164 -0
- package/dist/services/readonly-surface/readonly-whitelist.d.ts +147 -0
- package/dist/services/readonly-surface/readonly-whitelist.js +107 -0
- package/dist/services/readonly-surface/whitelist-generator.d.ts +64 -0
- package/dist/services/readonly-surface/whitelist-generator.js +212 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +26 -0
- package/dist/services/skills/hooks-settings-service.js +10 -2
- package/package.json +17 -6
- package/scripts/install-skills-bundles.mjs +233 -0
- package/scripts/install-skills-fs.mjs +148 -0
- package/scripts/install-skills-ide.mjs +279 -0
- package/scripts/install-skills-output-style-default.mjs +196 -0
- package/scripts/install-skills-output-styles.mjs +121 -0
- package/scripts/install-skills-report.mjs +42 -0
- package/scripts/install-skills-user-config.mjs +198 -0
- package/scripts/install-skills.mjs +36 -1066
- package/scripts/watch-tree.mjs +140 -0
- package/scripts/watch.mjs +4 -126
- package/skills/peaks-status/SKILL.md +31 -13
|
@@ -1,636 +1,72 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* `peaks workspace init` — the registrar.
|
|
2
3
|
*
|
|
3
4
|
* Extracted from `src/cli/commands/workspace-commands.ts` (slice
|
|
4
|
-
* 800-line Karpathy cap.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - missing-standards diagnostic surface (AC1-AC7) and `--init-standards` apply
|
|
5
|
+
* 800-line Karpathy cap). Split further by responsibility.
|
|
6
|
+
* The option/outcome shapes, the first-time hooks
|
|
7
|
+
* decision, project-root + session resolution, the nextAction/warning
|
|
8
|
+
* collectors, the codegraph stake, the envelope builder and the typed failure
|
|
9
|
+
* arms each live in their own module under `./`. This file wires them up.
|
|
10
10
|
*
|
|
11
11
|
* Imports the shared hooks-decision marker helpers from `./helpers.ts`.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.command('init')
|
|
36
|
-
.description('Create the .peaks/_runtime/<session-id>/ directory with ONLY the session.json metadata file (slice 006: role subdirs prd/ui/rd/qa/sc/txt and the system/ subdir are created lazily by writers, not pre-created at init). Pass --session-id to use a specific id, or omit it to auto-generate one (and adopt an existing binding if present). On the first call for a project, also handles the one-time "install peaks hooks" decision (sticky-marker stored in .peaks/.peaks-init-hooks-decision.json).')
|
|
37
|
-
.requiredOption('--project <path>', 'target project root')
|
|
38
|
-
.option('--session-id <id>', 'optional session id in YYYY-MM-DD-<kebab-slug> format. When omitted, the CLI is the single source of truth: an existing binding is reused, otherwise a fresh id is auto-generated.')
|
|
39
|
-
.option('--allow-session-rebind', 'overwrite an existing session binding when the requested session id differs from the project current one', false)
|
|
40
|
-
.option('--no-rotate-on-outer-mismatch', 'suppress the auto-rotation of the project session binding when the outer (Claude / harness) session id has changed. Default rotates on mismatch.')
|
|
41
|
-
.option('--install-hooks <mode>', 'first-time hooks install behaviour: ask (default in TTY, prompt once + sticky-marker), auto (default in --json / non-TTY, install silently + sticky-marker), skip (sticky-marker skipped, do not install)', (value) => {
|
|
42
|
-
if (value !== 'ask' && value !== 'auto' && value !== 'skip') {
|
|
43
|
-
throw new Error(`--install-hooks must be one of: ask, auto, skip (got "${value}")`);
|
|
44
|
-
}
|
|
45
|
-
return value;
|
|
46
|
-
})
|
|
47
|
-
.option('--no-claude-hooks', 'do NOT materialize .claude/settings.local.json (slice 2.0.1-bug3 fact-forcing bypass). Default: hooks installed so tool calls inside .peaks/** are not blocked by the [Fact-Forcing Gate].')
|
|
48
|
-
.option('--init-standards', "slice 2026-06-16-peaks-code-auto-scaffold: when the consumer project's .claude/rules/ is missing or empty, auto-apply `peaks standards init --project <path> --apply` after emitting the diagnostic. Default: diagnostic only (no write).")
|
|
49
|
-
.option('--no-project-scan-bootstrap', 'slice 2026-07-15-project-scan-bootstrap: skip the bootstrap of .peaks/project-scan/{project-scan.md + 4 audit/business templates} that runs after init succeeds. Default: bootstrap runs (idempotent: existing files are kept unless --force-project-scan-templates is also passed).')
|
|
50
|
-
.option('--force-project-scan-templates', 'slice 2026-07-15-project-scan-bootstrap (AC10): overwrite the 4 audit/business templates under .peaks/project-scan/ (business-knowledge.md, security-template.md, perf-template.md, audit-output-schema.md) even when they already exist. Default: skip existing files (sediment-preserved).', false)).action(async (options) => {
|
|
51
|
-
try {
|
|
52
|
-
// Resolve the session id. Two paths:
|
|
53
|
-
// - explicit --session-id: use it as the requested binding target
|
|
54
|
-
// (ConflictingSessionError fires if it conflicts with an in-flight
|
|
55
|
-
// session, unless --allow-session-rebind is set)
|
|
56
|
-
// - omitted: defer to ensureSession(), which reuses an existing
|
|
57
|
-
// binding or auto-generates a fresh one. The init then writes
|
|
58
|
-
// .peaks/_runtime/session.json so the binding sticks.
|
|
59
|
-
//
|
|
60
|
-
// Before that: canonicalise the project root. If the user (or the
|
|
61
|
-
// LLM via "$(pwd)") passed a sub-directory of a real git repo
|
|
62
|
-
// (e.g. prompt-project/prompt-project/ inside the outer
|
|
63
|
-
// prompt-project/.git), promote the path to the git root. Without
|
|
64
|
-
// this, peaks would build a parallel .peaks/ tree under the
|
|
65
|
-
// nested sub-folder and silently break the project-binding model
|
|
66
|
-
// (the same regression that produced prompt-project/.peaks/ in
|
|
67
|
-
// the 5/27-5/29 sessions). When startPath is not inside any
|
|
68
|
-
// git repo, the helper falls through to the cwd verbatim.
|
|
69
|
-
// `resolveWritableProjectRoot` = `resolveCanonicalProjectRoot` plus the
|
|
70
|
-
// home-directory refusal. It has to be this one, and it has to run here:
|
|
71
|
-
// `ensureSessionWithRotation` below WRITES (it mints/binds a session), so
|
|
72
|
-
// a guard placed after it would leave a `.peaks/_runtime/` tree in the
|
|
73
|
-
// user's home even though the init was refused.
|
|
74
|
-
const projectRoot = resolveWritableProjectRoot(options.project);
|
|
75
|
-
// user did NOT pass --session-id explicitly, run
|
|
76
|
-
// `ensureSessionWithRotation` so the binding is rotated on
|
|
77
|
-
// outer-mismatch before `initWorkspace` is called. The
|
|
78
|
-
// rotation result is surfaced in the JSON envelope via
|
|
79
|
-
// `data.rotation`. When --session-id IS passed, the user has
|
|
80
|
-
// explicitly told us which session to bind — we honor that
|
|
81
|
-
// verbatim and do NOT rotate (rotation only fires for the
|
|
82
|
-
// auto-detect path).
|
|
83
|
-
let sessionId;
|
|
84
|
-
let rotation = {
|
|
85
|
-
previousSessionId: null,
|
|
86
|
-
reason: null
|
|
87
|
-
};
|
|
88
|
-
if (options.sessionId !== undefined && options.sessionId.length > 0) {
|
|
89
|
-
sessionId = options.sessionId;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
const result = await ensureSessionWithRotation(projectRoot, {
|
|
93
|
-
// Commander translates `--no-rotate-on-outer-mismatch` into
|
|
94
|
-
// `options.rotateOnOuterMismatch = false` (the `--no-` prefix
|
|
95
|
-
// is consumed and the remainder becomes the JS property name,
|
|
96
|
-
// with the boolean value flipped). The pre-slice-014 anti-
|
|
97
|
-
// pattern (reading `options.<flag-with-no-prefix> === true`)
|
|
98
|
-
// is NOT used here. The default (no flag) leaves
|
|
99
|
-
// `options.rotateOnOuterMismatch` undefined, which is not
|
|
100
|
-
// equal to `false`, so the default is "rotate on mismatch"
|
|
101
|
-
// (the new auto-roll).
|
|
102
|
-
skipRotateOnOuterMismatch: options.rotateOnOuterMismatch === false
|
|
103
|
-
});
|
|
104
|
-
sessionId = result.sessionId;
|
|
105
|
-
rotation = {
|
|
106
|
-
previousSessionId: result.previousSessionId,
|
|
107
|
-
reason: result.rotationReason
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
const report = await initWorkspace({
|
|
111
|
-
projectRoot,
|
|
112
|
-
sessionId,
|
|
113
|
-
allowSessionRebind: options.allowSessionRebind === true,
|
|
114
|
-
// Commander translates `--no-claude-hooks` into
|
|
115
|
-
// `options.claudeHooks = false`. The default (no flag) leaves
|
|
116
|
-
// `options.claudeHooks` undefined, which is not equal to
|
|
117
|
-
// `false`, so the default is "install hooks" (the bypass is
|
|
118
|
-
// on). Pass `--no-claude-hooks` to opt out.
|
|
119
|
-
noClaudeHooks: options.claudeHooks === false,
|
|
120
|
-
// auto-apply for the missing-standards scaffold. Default false
|
|
121
|
-
// — only the diagnostic is emitted. Pass --init-standards to
|
|
122
|
-
// also run `executeProjectStandardsInit({ apply: true })`.
|
|
123
|
-
initStandards: options.initStandards === true
|
|
124
|
-
});
|
|
125
|
-
// After the workspace dir is initialized, also bootstrap the
|
|
126
|
-
// `.peaks/project-scan/` artifact tree (project-scan.md + 4
|
|
127
|
-
// bundled audit/business templates). Idempotent — when the user
|
|
128
|
-
// re-runs `workspace init`, the existing files are kept (their
|
|
129
|
-
// sediment is user-authored). Pass `--no-project-scan-bootstrap`
|
|
130
|
-
// to skip, or `--force-project-scan-templates` to overwrite the
|
|
131
|
-
// 4 bundled templates anyway.
|
|
132
|
-
let projectScanEnvelope = null;
|
|
133
|
-
let projectScanError = null;
|
|
134
|
-
if (options.projectScanBootstrap === false) {
|
|
135
|
-
// Explicit opt-out. The envelope stays null; the JSON
|
|
136
|
-
// response carries `projectScan: { skipped: true, ... }` so
|
|
137
|
-
// callers can distinguish "skipped by flag" from "skipped by
|
|
138
|
-
// idempotency".
|
|
139
|
-
projectScanEnvelope = {
|
|
140
|
-
created: false,
|
|
141
|
-
skipped: true,
|
|
142
|
-
templatesBooted: 0,
|
|
143
|
-
templatesSkipped: 0,
|
|
144
|
-
projectScanPath: `${projectRoot}/.peaks/project-scan/project-scan.md`,
|
|
145
|
-
durationMs: 0
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
try {
|
|
150
|
-
projectScanEnvelope = await bootstrapProjectScan({
|
|
151
|
-
projectRoot,
|
|
152
|
-
...(options.forceProjectScanTemplates === true ? { forceTemplates: true } : {})
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
// Don't fail init over a project-scan issue — the workspace
|
|
157
|
-
// is already initialized and the user has the JSON envelope.
|
|
158
|
-
// Surface the failure as a warning so the LLM can decide.
|
|
159
|
-
projectScanError = getErrorMessage(error);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const nextActions = [];
|
|
163
|
-
if (report.previousSessionId !== null && report.bound) {
|
|
164
|
-
nextActions.push(`Replaced prior session binding "${report.previousSessionId}" with "${report.sessionId}".`);
|
|
165
|
-
}
|
|
166
|
-
if (rotation.previousSessionId !== null && rotation.reason === 'outer-session-mismatch') {
|
|
167
|
-
// Outer-session-mismatch rotation: the previous Claude / harness
|
|
168
|
-
// session is no longer the LLM driver. The new binding is fresh,
|
|
169
|
-
// the old session dir is preserved on disk.
|
|
170
|
-
nextActions.push(`Auto-rotated session binding: outer session id changed (was "${rotation.previousSessionId}"). ` +
|
|
171
|
-
`New binding is "${sessionId}". The previous session dir is preserved at .peaks/_runtime/${rotation.previousSessionId}/. ` +
|
|
172
|
-
`Re-run with --no-rotate-on-outer-mismatch to suppress this rotation.`);
|
|
173
|
-
// OLD outer session is now stale (defect A from the PRD).
|
|
174
|
-
// peaks-code Step 1 would otherwise pick up the old `mode`
|
|
175
|
-
// field and silently lock the new session into a mode the
|
|
176
|
-
// user never explicitly chose. Clear it here so Step 1's
|
|
177
|
-
// presence:check-stale reports `reason: 'no-presence'` and
|
|
178
|
-
// the re-ask fires.
|
|
179
|
-
const presenceClearOutcome = clearStalePresenceOnRotation({
|
|
180
|
-
projectRootOverride: projectRoot,
|
|
181
|
-
currentOuterSessionId: process.env.PEAKS_OUTER_SESSION_ID ?? process.env.CLAUDE_CODE_SESSION_ID,
|
|
182
|
-
rotatedOutSessionId: rotation.previousSessionId
|
|
183
|
-
});
|
|
184
|
-
if (presenceClearOutcome.cleared) {
|
|
185
|
-
nextActions.push(`Auto-cleared stale skill presence (recorded outer id "${presenceClearOutcome.recordedOuter ?? '?'}" did not match the new outer session). ` +
|
|
186
|
-
'peaks-code Step 1 will now AskUserQuestion to confirm the mode.');
|
|
187
|
-
}
|
|
188
|
-
else if (presenceClearOutcome.reason === 'recorded-by-different-outer') {
|
|
189
|
-
nextActions.push(`Kept skill presence: it was recorded by a different live outer session (id "${presenceClearOutcome.recordedOuter ?? '?'}"). ` +
|
|
190
|
-
'The new outer session will not auto-clear it.');
|
|
191
|
-
}
|
|
192
|
-
else if (presenceClearOutcome.reason === 'not-stale') {
|
|
193
|
-
nextActions.push('Kept skill presence: it was re-stamped by the new outer session during the rotation window (not stale).');
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
if (report.created.length === 0) {
|
|
197
|
-
nextActions.push('Workspace already initialized — proceed to project scan.');
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
nextActions.push('Run `peaks scan archetype --project <path> --json` next to populate rd/project-scan.md.');
|
|
201
|
-
}
|
|
202
|
-
// Slice 2.0.1-bug3-fact-forcing-bypass: surface the consumer-
|
|
203
|
-
// project .claude/settings.local.json materialization outcome.
|
|
204
|
-
// When the bypass is in effect, the LLM knows subsequent Writes
|
|
205
|
-
// and Bash calls targeting .peaks/** will not be blocked by the
|
|
206
|
-
// [Fact-Forcing Gate]. When the user opted out, we surface a
|
|
207
|
-
// nextAction so the manual recovery is documented.
|
|
208
|
-
if (report.claudeSettings.action === 'written' ||
|
|
209
|
-
report.claudeSettings.action === 'refreshed') {
|
|
210
|
-
nextActions.push(`Materialized .claude/settings.local.json (action: ${report.claudeSettings.action}) — ` +
|
|
211
|
-
`the [Fact-Forcing Gate] is bypassed for tool calls inside .peaks/**. ` +
|
|
212
|
-
'Restart Claude Code so the hooks take effect.');
|
|
213
|
-
}
|
|
214
|
-
else if (report.claudeSettings.action === 'already-current') {
|
|
215
|
-
// No-op: the bypass is already in effect and matches the
|
|
216
|
-
// current release. Do not spam the nextAction list on every
|
|
217
|
-
// init.
|
|
218
|
-
}
|
|
219
|
-
else if (report.claudeSettings.action === 'skipped') {
|
|
220
|
-
nextActions.push('Skipped .claude/settings.local.json materialization (--no-claude-hooks). ' +
|
|
221
|
-
'If the [Fact-Forcing Gate] blocks subsequent Writes, run `peaks workspace init` ' +
|
|
222
|
-
'again without --no-claude-hooks, or drop the contents of ' +
|
|
223
|
-
'`.peaks/.claude-settings-template.json` into `.claude/settings.local.json` manually.');
|
|
224
|
-
}
|
|
225
|
-
// the self-heal outcome for the offline
|
|
226
|
-
// `.peaks/.claude-settings-template.json` copy. When the offline
|
|
227
|
-
// file was refreshed (i.e. the previous peaks-loop release left
|
|
228
|
-
// a stale version without the `node -e "..."` wrapper), the user
|
|
229
|
-
// benefits from seeing that the manual-recovery anchor now
|
|
230
|
-
// points at the corrected template. We surface `written` and
|
|
231
|
-
// `refreshed` as the actionable events; `already-current` is
|
|
232
|
-
// silent (same rationale as the consumer-project no-op above).
|
|
233
|
-
//
|
|
234
|
-
// The `refreshed` nextAction also carries a loud warning that any
|
|
235
|
-
// MANUAL EDITS the user made to the offline template have been
|
|
236
|
-
// overwritten — drift detection cannot tell stale-from-prior-release
|
|
237
|
-
// apart from user-customised, so we surface the warning unconditionally
|
|
238
|
-
// to make sure anyone who customised sees the prompt.
|
|
239
|
-
if (report.claudeSettings.offlineTemplate.action === 'refreshed') {
|
|
240
|
-
nextActions.push(`Self-healed .peaks/.claude-settings-template.json (action: refreshed) — ` +
|
|
241
|
-
'the offline recovery anchor now matches the current peaks-loop template. ' +
|
|
242
|
-
'No action required; future manual recoveries will copy the corrected wrapper.');
|
|
243
|
-
nextActions.push('⚠️ If you had manually edited .peaks/.claude-settings-template.json, ' +
|
|
244
|
-
'those edits have been overwritten by the self-heal. ' +
|
|
245
|
-
'Re-apply your custom matchers / commands on top of the freshly-written template, ' +
|
|
246
|
-
'or open an issue if your customisation is a recurring need (the team may promote it to the canonical template).');
|
|
247
|
-
}
|
|
248
|
-
else if (report.claudeSettings.offlineTemplate.action === 'written') {
|
|
249
|
-
nextActions.push(`Wrote .peaks/.claude-settings-template.json (action: written) — ` +
|
|
250
|
-
'the offline recovery anchor is now in place for future manual recoveries.');
|
|
251
|
-
}
|
|
252
|
-
// bootstrap outcome. The envelope carries the write counts and
|
|
253
|
-
// duration so the LLM (and the human) see what landed. The
|
|
254
|
-
// nextAction only fires when the project-scan tree is fresh —
|
|
255
|
-
// idempotent re-runs are silent to keep the nextAction list
|
|
256
|
-
// scannable.
|
|
257
|
-
// (deferred until after warningsForEnvelope is declared below)
|
|
258
|
-
// First-time hooks install decision. Sticky-marker at
|
|
259
|
-
// .peaks/.peaks-init-hooks-decision.json records the user's answer
|
|
260
|
-
// (or the auto-decision) so subsequent inits for new sessions in the
|
|
261
|
-
// same project do not re-prompt. The marker is the only state that
|
|
262
|
-
// survives across sessions — without it, every new session would
|
|
263
|
-
// re-trigger the question.
|
|
264
|
-
const hooksOutcome = await resolveFirstTimeHooksInstall({
|
|
265
|
-
projectRoot,
|
|
266
|
-
...(options.installHooks !== undefined ? { explicitMode: options.installHooks } : {}),
|
|
267
|
-
jsonMode: options.json === true
|
|
268
|
-
});
|
|
269
|
-
if (hooksOutcome.decision === 'installed') {
|
|
270
|
-
nextActions.push(hooksOutcome.action === 'reinstalled'
|
|
271
|
-
? 'Re-installed the peaks-managed PreToolUse hooks (Bash→gate enforce, Task→progress start) — the marker said installed but the hooks were missing.'
|
|
272
|
-
: 'Installed the peaks-managed PreToolUse hooks (Bash→gate enforce, Task→progress start). Restart Claude Code so the hooks take effect.');
|
|
273
|
-
}
|
|
274
|
-
else if (hooksOutcome.action === 'first-decision' && hooksOutcome.decision === 'skipped') {
|
|
275
|
-
nextActions.push('Skipped peaks-managed hook install for this project. Re-run with --install-hooks=auto (or peaks hooks install) to install later.');
|
|
276
|
-
}
|
|
277
|
-
// - When the consumer project's `.claude/rules/` is missing or
|
|
278
|
-
// empty, emit the copy-pasteable diagnostic to stderr (via
|
|
279
|
-
// the JSON envelope `warnings` array) AND surface the
|
|
280
|
-
// structured descriptor in `data.standardsMissing`.
|
|
281
|
-
// - AC7: skip the diagnostic banner on subsequent invocations
|
|
282
|
-
// within the same session — the once-per-session marker
|
|
283
|
-
// `.peaks/_runtime/<sid>/.standards-checked` is written after
|
|
284
|
-
// the FIRST diagnostic emit.
|
|
285
|
-
// - AC3: when --init-standards was passed and the detector
|
|
286
|
-
// reported missing, `report.standardsApplied` already lists the
|
|
287
|
-
// freshly-written files. Surface that as a nextAction so the
|
|
288
|
-
// human sees what was written.
|
|
289
|
-
const warningsForEnvelope = [];
|
|
290
|
-
// Slice 4.0.8 (D3): after session binding, run the same
|
|
291
|
-
// canonical GC sweep `peaks skill presence:set` would
|
|
292
|
-
// perform. A `peaks workspace init` is a natural sweep
|
|
293
|
-
// trigger (the session id was just minted, all prior leases
|
|
294
|
-
// for the project are by definition same-project). Failures
|
|
295
|
-
// surface as warnings; the GC never blocks init.
|
|
296
|
-
try {
|
|
297
|
-
const gcResult = await gcStalePresenceLeases({
|
|
298
|
-
projectRoot,
|
|
299
|
-
trigger: 'workspace-init'
|
|
300
|
-
});
|
|
301
|
-
if (gcResult.removed > 0) {
|
|
302
|
-
warningsForEnvelope.push(`Stale lease sweep removed ${gcResult.removed} lease(s) for the canonical project (retained ${gcResult.retained}).`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
catch (err) {
|
|
306
|
-
warningsForEnvelope.push(`lease sweep failed: ${getErrorMessage(err)}`);
|
|
307
|
-
}
|
|
308
|
-
if (report.standardsMissing.missing && !hasStandardsCheckedMarker(projectRoot, sessionId)) {
|
|
309
|
-
warningsForEnvelope.push(report.standardsMissing.remediation);
|
|
310
|
-
nextActions.push(`Run \`peaks workspace init --init-standards --project ${projectRoot}\` to auto-apply the scaffold, or \`peaks standards init --project ${projectRoot} --apply\` manually.`);
|
|
311
|
-
}
|
|
312
|
-
if (report.standardsApplied !== undefined) {
|
|
313
|
-
nextActions.push(`Auto-applied .claude/rules/${report.standardsApplied.language}/ scaffold (slice 2026-06-16-peaks-code-auto-scaffold): ` +
|
|
314
|
-
`wrote ${report.standardsApplied.writtenFiles.length} file(s), ` +
|
|
315
|
-
`kept ${report.standardsApplied.skippedFiles.length} existing file(s).`);
|
|
316
|
-
}
|
|
317
|
-
// bootstrap outcome. The envelope carries the write counts and
|
|
318
|
-
// duration so the LLM (and the human) see what landed. The
|
|
319
|
-
// nextAction only fires when the project-scan tree is fresh —
|
|
320
|
-
// idempotent re-runs are silent to keep the nextAction list
|
|
321
|
-
// scannable.
|
|
322
|
-
if (projectScanError !== null) {
|
|
323
|
-
warningsForEnvelope.push(`project-scan bootstrap failed: ${projectScanError}`);
|
|
324
|
-
}
|
|
325
|
-
else if (projectScanEnvelope !== null && projectScanEnvelope.created) {
|
|
326
|
-
nextActions.push(`Bootstrapped .peaks/project-scan/ (project-scan.md + 4 audit/business templates) — ` +
|
|
327
|
-
`${projectScanEnvelope.templatesBooted} file(s) written, ${projectScanEnvelope.templatesSkipped} preserved. ` +
|
|
328
|
-
`Run \`peaks scan archetype --project ${projectRoot}\` and \`peaks scan libraries --project ${projectRoot}\` next to refresh the scan.`);
|
|
329
|
-
}
|
|
330
|
-
// Write the once-per-session marker AFTER the envelope is built
|
|
331
|
-
// (so subsequent inits skip the warning even on rapid back-to-back
|
|
332
|
-
// calls).
|
|
333
|
-
if (report.standardsMissing.missing) {
|
|
334
|
-
markStandardsChecked(projectRoot, sessionId);
|
|
335
|
-
}
|
|
336
|
-
// directory after a successful workspace init. Calls the conflict
|
|
337
|
-
// silently overwritten. We now run a REAL upstream `codegraph init`
|
|
338
|
-
// (not just mkdir + marker) so the schema actually has a
|
|
339
|
-
// `codegraph.db`, which is what post-slice auto-refresh and the
|
|
340
|
-
// pre-dispatch preflight gate on. `codegraph init` (WITHOUT
|
|
341
|
-
// `--index`) is fast (~1 s) and offline-safe — it only `Parser.init()`s
|
|
342
|
-
// WASM grammars that resolve from node_modules; the slow/offline
|
|
343
|
-
// concern applies to `index` (full build), not `init`.
|
|
344
|
-
let codegraphAutoOutcome = null;
|
|
345
|
-
try {
|
|
346
|
-
const guard = defaultCodegraphInitGuard(projectRoot);
|
|
347
|
-
if (guard.status === 'fresh') {
|
|
348
|
-
// 'fresh' here also covers the dangling state (marker present but
|
|
349
|
-
// no codegraph.db) — running init self-heals both.
|
|
350
|
-
const initResult = await executeCodegraphInvocation(createCodegraphInvocation({ subcommand: 'init', project: projectRoot }));
|
|
351
|
-
if (initResult.exitCode !== 0) {
|
|
352
|
-
warningsForEnvelope.push(`codegraph auto-stake init failed (exit ${String(initResult.exitCode)}). ` +
|
|
353
|
-
'Run `peaks codegraph init --project <path>` manually to enable codegraph.');
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
// Upstream init creates `.codegraph/`; mkdir is a no-op in the
|
|
357
|
-
// real path and lets the marker write succeed. Best-effort.
|
|
358
|
-
mkdirSync(guard.codegraphDir, { recursive: true });
|
|
359
|
-
writeCodegraphMarker(guard.codegraphDir);
|
|
360
|
-
codegraphAutoOutcome = { status: 'fresh' };
|
|
361
|
-
nextActions.push(`Auto-staked a peaks-loop-managed ${guard.codegraphDir}/ (upstream init + marker). ` +
|
|
362
|
-
'Run `peaks codegraph index --project <path>` next to build the SQLite-backed index.');
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
else if (guard.status === 'noop-already-peaks-loop') {
|
|
366
|
-
codegraphAutoOutcome = { status: 'noop' };
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
codegraphAutoOutcome = { status: 'conflict' };
|
|
370
|
-
warningsForEnvelope.push(`${guard.codegraphDir} already exists with a non-peaks-loop schema; ` +
|
|
371
|
-
'refusing to auto-init codegraph. Run `peaks codegraph init --project <path>` after moving the foreign directory.');
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
catch (err) {
|
|
375
|
-
warningsForEnvelope.push(`codegraph auto-stake failed: ${getErrorMessage(err)}`);
|
|
376
|
-
}
|
|
377
|
-
printResult(io, ok('workspace.init', {
|
|
378
|
-
...report,
|
|
379
|
-
// JSON envelope so the LLM and the human both see the swap.
|
|
380
|
-
// Field is omitted (not null) when no rotation fired.
|
|
381
|
-
...(rotation.previousSessionId !== null && rotation.reason !== null
|
|
382
|
-
? {
|
|
383
|
-
rotation: {
|
|
384
|
-
previousSessionId: rotation.previousSessionId,
|
|
385
|
-
reason: rotation.reason
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
: {}),
|
|
389
|
-
hooksInstall: {
|
|
390
|
-
decision: hooksOutcome.decision,
|
|
391
|
-
action: hooksOutcome.action,
|
|
392
|
-
scope: hooksOutcome.scope,
|
|
393
|
-
...(hooksOutcome.reason !== undefined ? { reason: hooksOutcome.reason } : {})
|
|
394
|
-
},
|
|
395
|
-
...(codegraphAutoOutcome !== null ? { codegraphAutoStake: codegraphAutoOutcome } : {}),
|
|
396
|
-
// for the .peaks/project-scan/ bootstrap. Always present
|
|
397
|
-
// (even when skipped / errored) so downstream readers can
|
|
398
|
-
// rely on the shape.
|
|
399
|
-
...(projectScanEnvelope !== null
|
|
400
|
-
? {
|
|
401
|
-
projectScan: {
|
|
402
|
-
...projectScanEnvelope,
|
|
403
|
-
...(projectScanError !== null ? { error: projectScanError } : {})
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
: {})
|
|
407
|
-
}, warningsForEnvelope, nextActions), options.json);
|
|
408
|
-
}
|
|
409
|
-
catch (error) {
|
|
410
|
-
if (error instanceof UnsafeProjectRootError) {
|
|
411
|
-
// A typed refusal, not a crash: "you are standing in your home
|
|
412
|
-
// directory, which is not a project" is a thing the caller can fix in
|
|
413
|
-
// one step, and the envelope has to say which directory was refused and
|
|
414
|
-
// what to pass instead. `WORKSPACE_INIT_FAILED` below would have buried
|
|
415
|
-
// it as an unexplained failure.
|
|
416
|
-
printResult(io, fail('workspace.init', 'UNSAFE_PROJECT_ROOT', error.message, {
|
|
417
|
-
resolvedProjectRoot: error.projectRoot,
|
|
418
|
-
projectOption: options.project ?? null
|
|
419
|
-
}, [
|
|
420
|
-
'Pass --project <path-to-your-project> instead of a path that resolves to your home directory.',
|
|
421
|
-
'This command creates .peaks/, .gitignore, .claude/settings.local.json and a codegraph index; none of those belong in $HOME.',
|
|
422
|
-
'If you meant to initialize the current directory, cd into a project directory first — peaks will not write into the home directory itself.'
|
|
423
|
-
]), options.json);
|
|
424
|
-
process.exitCode = 1;
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (error instanceof InvalidSessionIdError) {
|
|
428
|
-
printResult(io, fail('workspace.init', error.code, error.message, { sessionId: options.sessionId }, [
|
|
429
|
-
'Use a date-prefixed kebab slug like 2026-05-25-add-user-auth'
|
|
430
|
-
]), options.json);
|
|
431
|
-
process.exitCode = 1;
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
if (error instanceof ConflictingSessionError) {
|
|
435
|
-
printResult(io, fail('workspace.init', error.code, error.message, {
|
|
436
|
-
existingSessionId: error.existingSessionId,
|
|
437
|
-
requestedSessionId: error.requestedSessionId
|
|
438
|
-
}, [
|
|
439
|
-
`Finish or abandon session "${error.existingSessionId}" first, then re-run workspace init.`,
|
|
440
|
-
'Or pass --allow-session-rebind to override the binding (overwrites the prior binding).'
|
|
441
|
-
]), options.json);
|
|
442
|
-
process.exitCode = 1;
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
if (error instanceof LegacyChangeIdSiblingError) {
|
|
446
|
-
// Slice 2.8.3: a 2.8.0-era orphan `.peaks/_runtime/<change-id>/` was
|
|
447
|
-
// found at top level. The CLI surfaces the migration steps
|
|
448
|
-
// verbatim from the error message plus three concrete
|
|
449
|
-
// nextActions so the user (or LLM driver) has an unambiguous
|
|
450
|
-
// recovery path. We do NOT auto-migrate because the legacy
|
|
451
|
-
// sibling dir may contain user-authored content.
|
|
452
|
-
printResult(io, fail('workspace.init', error.code, error.message, {
|
|
453
|
-
sessionId: error.sessionId,
|
|
454
|
-
legacyPath: error.legacyPath
|
|
455
|
-
}, [
|
|
456
|
-
`Inspect ${error.legacyPath} for any user-authored content you want to keep.`,
|
|
457
|
-
`Move any desired files into .peaks/_runtime/<sessionId>/<role>/ (gitignored), then delete ${error.legacyPath}.`,
|
|
458
|
-
`Re-run \`peaks workspace init --project <path>\` to bind a fresh session.`
|
|
459
|
-
]), options.json);
|
|
460
|
-
process.exitCode = 1;
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
// `LegacyChangeIdBindingError` catch branch is gone — the change-id
|
|
464
|
-
// binding file at `.peaks/_runtime/current-change` is no longer
|
|
465
|
-
// written by init, so there is no legacy binding to detect.
|
|
466
|
-
printResult(io, fail('workspace.init', 'WORKSPACE_INIT_FAILED', getErrorMessage(error), { projectRoot: options.project, sessionId: options.sessionId }, ['Verify the project path exists and is writable']), options.json);
|
|
467
|
-
process.exitCode = 1;
|
|
468
|
-
}
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
/**
|
|
472
|
-
* Resolve the first-time "install peaks hooks" decision for this project.
|
|
473
|
-
* Decision tree:
|
|
474
|
-
*
|
|
475
|
-
* 1. Read the sticky marker.
|
|
476
|
-
* - Marker present:
|
|
477
|
-
* - marker.decision === 'installed' AND hooks are present → action: marker-honored, no side effects
|
|
478
|
-
* - marker.decision === 'installed' AND hooks are MISSING → re-install, action: reinstalled
|
|
479
|
-
* - marker.decision === 'skipped' → action: marker-honored, no install
|
|
480
|
-
* - Marker absent:
|
|
481
|
-
* - hooks already present → write a fresh 'installed' marker, action: already-installed
|
|
482
|
-
* - otherwise:
|
|
483
|
-
* - explicit --install-hooks=auto → install + marker, action: first-decision
|
|
484
|
-
* - explicit --install-hooks=skip → marker only, action: first-decision
|
|
485
|
-
* - explicit --install-hooks=ask OR default in TTY:
|
|
486
|
-
* - jsonMode → silently auto-install (LLM cannot answer), action: first-decision
|
|
487
|
-
* - TTY → prompt; on yes install + marker, on no marker-only
|
|
488
|
-
* - default in non-TTY → auto-install, action: first-decision
|
|
489
|
-
*
|
|
490
|
-
* Project scope is the only supported scope here; global scope is reserved
|
|
491
|
-
* for explicit `peaks hooks install --global` invocations.
|
|
492
|
-
*/
|
|
493
|
-
export async function resolveFirstTimeHooksInstall(options) {
|
|
494
|
-
const { projectRoot, jsonMode } = options;
|
|
495
|
-
const existingMarker = readDecisionMarker(projectRoot);
|
|
496
|
-
// readHookStatus can throw (e.g. .claude is a symlink → safety check rejects).
|
|
497
|
-
// Treat any throw as "hooks status unknown → treat as not-installed" so the
|
|
498
|
-
// function still reaches the install path; the install will surface the same
|
|
499
|
-
// error in a more specific reason field.
|
|
500
|
-
let hookStatus;
|
|
501
|
-
try {
|
|
502
|
-
hookStatus = readHookStatus('project', projectRoot);
|
|
503
|
-
}
|
|
504
|
-
catch (error) {
|
|
505
|
-
hookStatus = { installed: false };
|
|
506
|
-
// Fall through to the install path; the failure will be captured below.
|
|
507
|
-
void error;
|
|
508
|
-
}
|
|
509
|
-
if (existingMarker !== null) {
|
|
510
|
-
if (existingMarker.decision === 'installed' && !hookStatus.installed) {
|
|
511
|
-
try {
|
|
512
|
-
applyHookInstall('project', projectRoot);
|
|
513
|
-
return {
|
|
514
|
-
decision: 'installed',
|
|
515
|
-
action: 'reinstalled',
|
|
516
|
-
scope: 'project',
|
|
517
|
-
reason: 'marker-said-installed-hooks-missing'
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
catch (error) {
|
|
521
|
-
return {
|
|
522
|
-
decision: existingMarker.decision,
|
|
523
|
-
action: 'marker-honored',
|
|
524
|
-
scope: 'project',
|
|
525
|
-
reason: `reinstall-failed: ${getErrorMessage(error)}`
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return {
|
|
530
|
-
decision: existingMarker.decision,
|
|
531
|
-
action: 'marker-honored',
|
|
532
|
-
scope: existingMarker.scope
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
// No marker yet — first decision.
|
|
536
|
-
if (hookStatus.installed) {
|
|
537
|
-
writeDecisionMarker(projectRoot, 'installed');
|
|
538
|
-
return { decision: 'installed', action: 'already-installed', scope: 'project' };
|
|
539
|
-
}
|
|
540
|
-
// Determine effective mode (explicit flag wins; default depends on TTY + jsonMode).
|
|
541
|
-
const explicitMode = options.explicitMode;
|
|
542
|
-
const effectiveMode = explicitMode ?? (jsonMode ? 'auto' : process.stdin.isTTY === true ? 'ask' : 'auto');
|
|
543
|
-
if (effectiveMode === 'skip') {
|
|
544
|
-
writeDecisionMarker(projectRoot, 'skipped');
|
|
545
|
-
return {
|
|
546
|
-
decision: 'skipped',
|
|
547
|
-
action: 'first-decision',
|
|
548
|
-
scope: 'project',
|
|
549
|
-
reason: 'explicit-skip'
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
if (effectiveMode === 'auto' || jsonMode) {
|
|
553
|
-
// The reason code distinguishes the path the user took to reach auto-install:
|
|
554
|
-
// - explicit-auto: user passed --install-hooks=auto
|
|
555
|
-
// - json-mode: no --install-hooks flag, but --json was set
|
|
556
|
-
// - non-tty-default: no flag, no --json, stdin is not a TTY
|
|
557
|
-
let autoReason;
|
|
558
|
-
if (explicitMode === 'auto') {
|
|
559
|
-
autoReason = 'explicit-auto';
|
|
560
|
-
}
|
|
561
|
-
else if (jsonMode) {
|
|
562
|
-
autoReason = 'json-mode';
|
|
563
|
-
}
|
|
564
|
-
else {
|
|
565
|
-
autoReason = 'non-tty-default';
|
|
566
|
-
}
|
|
567
|
-
try {
|
|
568
|
-
applyHookInstall('project', projectRoot);
|
|
569
|
-
writeDecisionMarker(projectRoot, 'installed');
|
|
570
|
-
return {
|
|
571
|
-
decision: 'installed',
|
|
572
|
-
action: 'first-decision',
|
|
573
|
-
scope: 'project',
|
|
574
|
-
reason: autoReason
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
catch (error) {
|
|
578
|
-
// Auto-install failed: still record the decision so we do not keep retrying
|
|
579
|
-
// every workspace init. The user can fix the underlying problem and run
|
|
580
|
-
// `peaks hooks install` manually.
|
|
581
|
-
writeDecisionMarker(projectRoot, 'installed');
|
|
582
|
-
return {
|
|
583
|
-
decision: 'installed',
|
|
584
|
-
action: 'first-decision',
|
|
585
|
-
scope: 'project',
|
|
586
|
-
reason: `install-failed: ${getErrorMessage(error)}`
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
// effectiveMode === 'ask' AND TTY: prompt once.
|
|
591
|
-
process.stderr.write('\nPeaks-Cli: install the PreToolUse hooks for this project now?\n' +
|
|
592
|
-
' → Bash matcher: `peaks gate enforce` (SOP gate enforcement)\n' +
|
|
593
|
-
'The gate-enforce hook runs on every Claude Code tool call without further prompting. The decision is sticky\n' +
|
|
594
|
-
'(recorded in .peaks/.peaks-init-hooks-decision.json) and re-runs of `workspace init` will\n' +
|
|
595
|
-
'honour it. Re-run with --install-hooks=skip or --install-hooks=auto to override.\n\n' +
|
|
596
|
-
'Install now? [Y/n]: ');
|
|
597
|
-
const answer = await promptYesNo('');
|
|
598
|
-
if (answer === null) {
|
|
599
|
-
// TTY disappeared mid-prompt (rare): treat as skip + write marker.
|
|
600
|
-
writeDecisionMarker(projectRoot, 'skipped');
|
|
601
|
-
return {
|
|
602
|
-
decision: 'skipped',
|
|
603
|
-
action: 'first-decision',
|
|
604
|
-
scope: 'project',
|
|
605
|
-
reason: 'tty-prompt-aborted'
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
if (!answer) {
|
|
609
|
-
writeDecisionMarker(projectRoot, 'skipped');
|
|
610
|
-
return {
|
|
611
|
-
decision: 'skipped',
|
|
612
|
-
action: 'first-decision',
|
|
613
|
-
scope: 'project',
|
|
614
|
-
reason: 'user-answered-no'
|
|
615
|
-
};
|
|
13
|
+
import { ok } from 'peaks-loop-shared/result';
|
|
14
|
+
import { addJsonOption, printResult } from '../../cli-helpers.js';
|
|
15
|
+
import { bootstrapProjectScanForInit, resolveInitTarget, runInitWorkspace } from './init-context.js';
|
|
16
|
+
import { collectInitEnvelope } from './init-actions.js';
|
|
17
|
+
import { buildInitEnvelopeData } from './init-envelope.js';
|
|
18
|
+
import { reportWorkspaceInitFailure } from './init-failures.js';
|
|
19
|
+
// Published import paths preserved: `workspace-commands.ts` (and tests) import
|
|
20
|
+
// the hooks-decision primitive and its two types from this module.
|
|
21
|
+
export { resolveFirstTimeHooksInstall } from './init-hooks-decision.js';
|
|
22
|
+
const INIT_DESCRIPTION = 'Create the .peaks/_runtime/<session-id>/ directory with ONLY the session.json metadata file (slice 006: role subdirs prd/ui/rd/qa/sc/txt and the system/ subdir are created lazily by writers, not pre-created at init). Pass --session-id to use a specific id, or omit it to auto-generate one (and adopt an existing binding if present). On the first call for a project, also handles the one-time "install peaks hooks" decision (sticky-marker stored in .peaks/.peaks-init-hooks-decision.json).';
|
|
23
|
+
const INIT_PROJECT_HELP = 'target project root';
|
|
24
|
+
const INIT_SESSION_HELP = 'optional session id in YYYY-MM-DD-<kebab-slug> format. When omitted, the CLI is the single source of truth: an existing binding is reused, otherwise a fresh id is auto-generated.';
|
|
25
|
+
const INIT_REBIND_HELP = 'overwrite an existing session binding when the requested session id differs from the project current one';
|
|
26
|
+
const INIT_NO_ROTATE_HELP = 'suppress the auto-rotation of the project session binding when the outer (Claude / harness) session id has changed. Default rotates on mismatch.';
|
|
27
|
+
const INIT_INSTALL_HOOKS_HELP = 'first-time hooks install behaviour: ask (default in TTY, prompt once + sticky-marker), auto (default in --json / non-TTY, install silently + sticky-marker), skip (sticky-marker skipped, do not install)';
|
|
28
|
+
const INIT_NO_CLAUDE_HOOKS_HELP = 'do NOT materialize .claude/settings.local.json (slice 2.0.1-bug3 fact-forcing bypass). Default: hooks installed so tool calls inside .peaks/** are not blocked by the [Fact-Forcing Gate].';
|
|
29
|
+
const INIT_INIT_STANDARDS_HELP = "slice 2026-06-16-peaks-code-auto-scaffold: when the consumer project's .claude/rules/ is missing or empty, auto-apply `peaks standards init --project <path> --apply` after emitting the diagnostic. Default: diagnostic only (no write).";
|
|
30
|
+
const INIT_NO_PROJECT_SCAN_HELP = 'slice 2026-07-15-project-scan-bootstrap: skip the bootstrap of .peaks/project-scan/{project-scan.md + 4 audit/business templates} that runs after init succeeds. Default: bootstrap runs (idempotent: existing files are kept unless --force-project-scan-templates is also passed).';
|
|
31
|
+
const INIT_FORCE_TEMPLATES_HELP = 'slice 2026-07-15-project-scan-bootstrap (AC10): overwrite the 4 audit/business templates under .peaks/project-scan/ (business-knowledge.md, security-template.md, perf-template.md, audit-output-schema.md) even when they already exist. Default: skip existing files (sediment-preserved).';
|
|
32
|
+
function parseInstallHooks(value) {
|
|
33
|
+
if (value !== 'ask' && value !== 'auto' && value !== 'skip') {
|
|
34
|
+
throw new Error(`--install-hooks must be one of: ask, auto, skip (got "${value}")`);
|
|
616
35
|
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
async function runWorkspaceInit(options, io) {
|
|
617
39
|
try {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
40
|
+
const { projectRoot, sessionId, rotation } = await resolveInitTarget(options);
|
|
41
|
+
const report = await runInitWorkspace(options, projectRoot, sessionId);
|
|
42
|
+
const scan = await bootstrapProjectScanForInit(options, projectRoot);
|
|
43
|
+
const ctx = {
|
|
44
|
+
options,
|
|
45
|
+
projectRoot,
|
|
46
|
+
sessionId,
|
|
47
|
+
rotation,
|
|
48
|
+
report,
|
|
49
|
+
projectScanEnvelope: scan.envelope,
|
|
50
|
+
projectScanError: scan.error
|
|
625
51
|
};
|
|
52
|
+
const parts = await collectInitEnvelope(ctx);
|
|
53
|
+
printResult(io, ok('workspace.init', buildInitEnvelopeData(ctx, parts.hooksOutcome, parts.codegraphAutoOutcome), parts.warnings, parts.nextActions), options.json);
|
|
626
54
|
}
|
|
627
55
|
catch (error) {
|
|
628
|
-
|
|
629
|
-
return {
|
|
630
|
-
decision: 'installed',
|
|
631
|
-
action: 'first-decision',
|
|
632
|
-
scope: 'project',
|
|
633
|
-
reason: `install-failed: ${getErrorMessage(error)}`
|
|
634
|
-
};
|
|
56
|
+
reportWorkspaceInitFailure(io, error, options);
|
|
635
57
|
}
|
|
636
58
|
}
|
|
59
|
+
export function registerWorkspaceInitCommand(workspace, io) {
|
|
60
|
+
addJsonOption(workspace
|
|
61
|
+
.command('init')
|
|
62
|
+
.description(INIT_DESCRIPTION)
|
|
63
|
+
.requiredOption('--project <path>', INIT_PROJECT_HELP)
|
|
64
|
+
.option('--session-id <id>', INIT_SESSION_HELP)
|
|
65
|
+
.option('--allow-session-rebind', INIT_REBIND_HELP, false)
|
|
66
|
+
.option('--no-rotate-on-outer-mismatch', INIT_NO_ROTATE_HELP)
|
|
67
|
+
.option('--install-hooks <mode>', INIT_INSTALL_HOOKS_HELP, parseInstallHooks)
|
|
68
|
+
.option('--no-claude-hooks', INIT_NO_CLAUDE_HOOKS_HELP)
|
|
69
|
+
.option('--init-standards', INIT_INIT_STANDARDS_HELP)
|
|
70
|
+
.option('--no-project-scan-bootstrap', INIT_NO_PROJECT_SCAN_HELP)
|
|
71
|
+
.option('--force-project-scan-templates', INIT_FORCE_TEMPLATES_HELP, false)).action((options) => runWorkspaceInit(options, io));
|
|
72
|
+
}
|