peaks-loop 4.0.5 → 4.0.7

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 (31) hide show
  1. package/CHANGELOG.md +320 -0
  2. package/dist/cli/commands/code-job-shape-commands.js +11 -8
  3. package/dist/cli/commands/code-mode-gate-commands.js +43 -1
  4. package/dist/cli/commands/code-runtime-commands.js +11 -6
  5. package/dist/cli/commands/core/standards-command.js +50 -5
  6. package/dist/cli/commands/dispatch-commands.js +39 -3
  7. package/dist/cli/commands/hooks-commands.js +26 -9
  8. package/dist/cli/commands/sub-agent-commands.js +2 -0
  9. package/dist/cli/commands/sub-agent-exec-command.d.ts +25 -0
  10. package/dist/cli/commands/sub-agent-exec-command.js +132 -0
  11. package/dist/cli/commands/sub-agent-shared.d.ts +11 -0
  12. package/dist/services/audit/backing-detector.d.ts +19 -2
  13. package/dist/services/audit/backing-detector.js +66 -7
  14. package/dist/services/audit/enforcers/lint-audit-regression.js +10 -1
  15. package/dist/services/context/build-dispatch-system-prompt.js +18 -0
  16. package/dist/services/dispatch/test-tool-detection.d.ts +14 -0
  17. package/dist/services/dispatch/test-tool-detection.js +22 -0
  18. package/dist/services/scan/archetype-service.js +52 -2
  19. package/dist/services/session/index.d.ts +1 -1
  20. package/dist/services/session/index.js +1 -1
  21. package/dist/services/session/session-manager.d.ts +29 -0
  22. package/dist/services/session/session-manager.js +33 -0
  23. package/dist/services/skills/hooks-settings-service.d.ts +41 -0
  24. package/dist/services/skills/hooks-settings-service.js +61 -5
  25. package/dist/services/slice/slice-check-service.js +59 -0
  26. package/dist/services/standards/find-skills-integration.d.ts +59 -0
  27. package/dist/services/standards/find-skills-integration.js +225 -0
  28. package/dist/services/standards/project-standards-service.js +44 -0
  29. package/package.json +4 -4
  30. package/scripts/install-skills.mjs +171 -0
  31. package/skills/peaks-code/SKILL.md +43 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "4.0.5",
3
+ "version": "4.0.7",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "keywords": [
@@ -98,9 +98,9 @@
98
98
  "headroom-ai": "0.22.4",
99
99
  "yaml": "^2.9.0",
100
100
  "zod": "^3.25.76",
101
- "peaks-loop-mut": "0.1.9",
102
- "peaks-loop-shared": "0.0.35",
103
- "peaks-loop-shared-channel": "0.0.14"
101
+ "peaks-loop-shared-channel": "0.0.16",
102
+ "peaks-loop-shared": "0.0.37",
103
+ "peaks-loop-mut": "0.1.12"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "@alibaba-group/open-code-review": "1.3.1"
@@ -762,6 +762,143 @@ export function installBundledOutputStyles(options = {}) {
762
762
  return { installed, skipped };
763
763
  }
764
764
 
765
+ /**
766
+ * Slice 2026-08-02 — auto-register bundled output style.
767
+ *
768
+ * Real user feedback (2026-08-02): `npm i -g peaks-loop@latest` on a
769
+ * fresh 0-1 project copied `peaks-skill-swarm.md` into
770
+ * `~/.claude/output-styles/` but never wrote
771
+ * `outputStyle: 'peaks-skill-swarm'` into `~/.claude/settings.json`.
772
+ * Result: Claude Code loaded no Peaks output style in new sessions.
773
+ *
774
+ * The fix: when `peaks-skill-swarm.md` is present in the bundled output
775
+ * styles directory AND `~/.claude/settings.json` does NOT yet declare
776
+ * `outputStyle`, merge in `outputStyle: 'peaks-skill-swarm'` while
777
+ * preserving every other key. If the user already set `outputStyle`,
778
+ * leave it alone — the user is the source of truth.
779
+ *
780
+ * Resolution precedence:
781
+ * 1. options.settingsFile (explicit override / test hook)
782
+ * 2. PEAKS_CLAUDE_SETTINGS_FILE env var (CI override)
783
+ * 3. homedir-relative `<homedir>/.claude/settings.json` (default)
784
+ *
785
+ * Soft-fail contract: every error path (missing file, malformed JSON,
786
+ * missing bundled style file, permission denied) is logged to stderr
787
+ * and returns `{ skipped: true, ... }` instead of throwing. The
788
+ * postinstall must never fail on this step because the bundled file
789
+ * itself was already written successfully.
790
+ */
791
+ function resolveSettingsFilePath(options = {}) {
792
+ if (typeof options.settingsFile === 'string' && options.settingsFile.length > 0) {
793
+ return resolve(options.settingsFile);
794
+ }
795
+ const envOverride = process.env.PEAKS_CLAUDE_SETTINGS_FILE;
796
+ if (typeof envOverride === 'string' && envOverride.length > 0) {
797
+ return resolve(envOverride);
798
+ }
799
+ return resolve(homedir(), '.claude', 'settings.json');
800
+ }
801
+
802
+ export function installBundledOutputStyleDefault(options = {}) {
803
+ if (process.env.PEAKS_SKIP_SKILL_INSTALL === '1' || process.env.PEAKS_SKIP_OUTPUT_STYLE_DEFAULT === '1') {
804
+ return { skipped: true, reason: 'PEAKS_SKIP_OUTPUT_STYLE_DEFAULT=1' };
805
+ }
806
+
807
+ const bundledStylesDir = resolve(
808
+ options.targetRoot
809
+ ?? process.env.PEAKS_CLAUDE_OUTPUT_STYLES_DIR
810
+ ?? join(homedir(), '.claude', 'output-styles')
811
+ );
812
+ const settingsPath = resolveSettingsFilePath(options);
813
+ const bundledStyleName = 'peaks-skill-swarm.md';
814
+ const bundledStylePath = join(bundledStylesDir, bundledStyleName);
815
+
816
+ // Read the existing settings.json (if any) FIRST so a malformed file
817
+ // surfaces as a parse error (more actionable to the user than the
818
+ // generic "bundled style not present" message). Malformed JSON is a
819
+ // user-authored file we MUST NOT clobber — log and skip.
820
+ let existing = null;
821
+ if (existsSync(settingsPath)) {
822
+ const stats = getPathStats(settingsPath);
823
+ if (!stats || !stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) {
824
+ process.stderr.write(
825
+ `peaks install-skills: refusing to overwrite non-regular settings.json at ${settingsPath}\n`
826
+ );
827
+ return { skipped: true, reason: 'settings.json is not a regular file' };
828
+ }
829
+ try {
830
+ const raw = readFileSafely(settingsPath, 'Claude settings.json path changed during read');
831
+ const parsed = JSON.parse(raw);
832
+ if (!isPlainObject(parsed)) {
833
+ process.stderr.write(
834
+ `peaks install-skills: refusing to overwrite settings.json that is not a JSON object at ${settingsPath}\n`
835
+ );
836
+ return { skipped: true, reason: 'settings.json is not a JSON object' };
837
+ }
838
+ existing = parsed;
839
+ } catch (error) {
840
+ const message = error instanceof Error ? error.message : String(error);
841
+ process.stderr.write(
842
+ `peaks install-skills: failed to parse settings.json at ${settingsPath}: ${message}; leaving untouched\n`
843
+ );
844
+ return { skipped: true, reason: 'settings.json parse error' };
845
+ }
846
+ }
847
+
848
+ // Pre-condition: the bundled style file must be present in the
849
+ // dispatched target. If it's not, we MUST NOT inject
850
+ // `outputStyle: 'peaks-skill-swarm'` — Claude Code would then fail
851
+ // to load the style and fall back to default anyway, leaving the
852
+ // user with broken state. Skipping is the safe default.
853
+ if (!existsSync(bundledStylePath)) {
854
+ return { skipped: true, reason: `bundled output style not present at ${bundledStylePath}` };
855
+ }
856
+
857
+ // User-authored outputStyle wins — never overwrite.
858
+ if (existing !== null && typeof existing.outputStyle === 'string' && existing.outputStyle.length > 0) {
859
+ return { skipped: true, reason: `user-defined outputStyle already set: ${existing.outputStyle}` };
860
+ }
861
+
862
+ const next = existing === null
863
+ ? { outputStyle: 'peaks-skill-swarm' }
864
+ : { ...existing, outputStyle: 'peaks-skill-swarm' };
865
+
866
+ const existingJson = existing === null ? null : `${JSON.stringify(existing, null, 2)}\n`;
867
+ const nextJson = `${JSON.stringify(next, null, 2)}\n`;
868
+ if (existingJson === nextJson) {
869
+ return { skipped: true, reason: 'no change' };
870
+ }
871
+
872
+ try {
873
+ writeFileAtomically(
874
+ settingsPath,
875
+ nextJson,
876
+ 'Claude settings.json path changed during write',
877
+ () => {
878
+ if (!existsSync(settingsPath)) return;
879
+ const stats = getPathStats(settingsPath);
880
+ if (!stats || !stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1) {
881
+ throw new Error('Claude settings.json path is not a regular file');
882
+ }
883
+ }
884
+ );
885
+ } catch (error) {
886
+ const message = error instanceof Error ? error.message : String(error);
887
+ process.stderr.write(
888
+ `peaks install-skills: failed to write settings.json at ${settingsPath}: ${message}\n`
889
+ );
890
+ return { skipped: true, reason: 'write error', error: message };
891
+ }
892
+
893
+ return {
894
+ installed: true,
895
+ created: existing === null,
896
+ updated: existing !== null,
897
+ settingsPath,
898
+ outputStyle: 'peaks-skill-swarm',
899
+ };
900
+ }
901
+
765
902
  /**
766
903
  * Slice 7/7 — bundled agents (Claude Code sub-agent prompts).
767
904
  *
@@ -1205,6 +1342,40 @@ if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(resolve(p
1205
1342
  if (outputStylesResult.skipped.length > 0) {
1206
1343
  process.stderr.write(`Peaks output styles skipped because local files already exist: ${outputStylesResult.skipped.join(', ')}\n`);
1207
1344
  }
1345
+
1346
+ // Slice 2026-08-02 — auto-register bundled output style.
1347
+ //
1348
+ // After the bundled style file lands in `~/.claude/output-styles/`,
1349
+ // we also need Claude Code to actually USE it — otherwise the user
1350
+ // (real feedback 2026-08-02: fresh 0-1 project like
1351
+ // `Desktop\ticket-cross`) sees the default style in new sessions.
1352
+ //
1353
+ // This step reads `~/.claude/settings.json`, and only if the user
1354
+ // hasn't set `outputStyle` yet, writes
1355
+ // `outputStyle: 'peaks-skill-swarm'` while preserving every other
1356
+ // key. Malformed settings.json / IO errors / non-regular files are
1357
+ // soft-failed to stderr (the bundled file is already on disk, so
1358
+ // postinstall never aborts here).
1359
+ //
1360
+ // The `targetRoot` here mirrors the dispatch target of
1361
+ // `installBundledOutputStyles()` above so the env-var override
1362
+ // path (test fixtures + CI) stays consistent: if the bundled
1363
+ // style file was dispatched to an env-overridden dir, the
1364
+ // auto-register step checks THAT dir, not the user's homedir.
1365
+ try {
1366
+ const dispatchTargetRoot = process.env.PEAKS_CLAUDE_OUTPUT_STYLES_DIR
1367
+ ?? join(homedir(), '.claude', 'output-styles');
1368
+ const styleDefaultResult = installBundledOutputStyleDefault({ targetRoot: dispatchTargetRoot });
1369
+ if (styleDefaultResult.installed) {
1370
+ process.stdout.write(
1371
+ `Peaks output style auto-registered: ${styleDefaultResult.outputStyle}\n` +
1372
+ ` → wrote ${styleDefaultResult.settingsPath}\n`
1373
+ );
1374
+ }
1375
+ } catch (error) {
1376
+ const message = error instanceof Error ? error.message : String(error);
1377
+ process.stderr.write(`Peaks output style default was not installed: ${message}\n`);
1378
+ }
1208
1379
  if (userConfigResult.created) {
1209
1380
  process.stdout.write('Peaks user config created: ~/.peaks/config.json\n');
1210
1381
  }
@@ -63,6 +63,41 @@ This skill is the **primary surface**. The `peaks <cmd>` CLI is **auxiliary**
63
63
 
64
64
  When 24h mode is active and an in-flight sub-agent batch is detected, the LLM MAY persist its current turn context to disk through the `session spill-demo` helper. Creates a `SpillRecord` in `.peaks/_runtime/<sessionId>/spill/`. Round-trip is idempotent; existing in-flight deferral still works without spill/hydrate. **Opt-in experimental** — not required.
65
65
 
66
+ ## 24h mode explicit scope (slice 4.0.7-PR-meta-2, locked 2026-08-02)
67
+
68
+ The 4.0.7 dogfood pass on `ice-cola` surfaced a flow-level bug: 24h mode's "no AskUserQuestion" intent was over-extended by the LLM to **all** user instructions, including the Code-Change Red Line (the prose-only rule "you MUST NOT edit application source directly without going through peaks-rd"). The LLM routed 22 files / 632 lines of `Edit` calls straight through without a single `peaks sub-agent dispatch rd`. The fix below makes the scope explicit so future LLM sessions cannot make the same over-extension.
69
+
70
+ ### What 24h mode BYPASSES (the AskUserQuestion gates)
71
+
72
+ 24h mode is `peaks session 24h-mode transition --state 24H_ACTIVE`. Once active, the LLM MAY auto-proceed (recommended = chosen) at the following `peaks-code` steps without an AskUserQuestion:
73
+
74
+ - `step-0.5-openspec-opt-in` — OpenSpec opt-in
75
+ - `step-0.6-audit-goal` — audit + goal confirmation
76
+ - `step-0.7-resume-detection` — resume vs new-work
77
+ - `step-0.55-1x-upgrade` — 1.x → 2.0 upgrade detection (still surfaces the gate at the moment-of-impact, but does not pause to ASK)
78
+ - `step-1-mode-select` — full-auto / assisted / strict / swarm mode pick
79
+ - `step-2.5-session-title` — human-readable session title
80
+ - `phase-2-prd-confirm` — PRD's `Goals / Non-goals / Acceptance criteria` confirm (24h mode picks a recommended set; user can still reject post-hoc via the verdict loop)
81
+ - `phase-3-swarm-gate-b` — swarm selection (full-auto recommended; 24h mode skips the AskUserQuestion but still records the choice)
82
+ - `phase-6-qa-gate-d` — QA verdict gate (24h mode auto-proceeds; the verdict log is the audit trail)
83
+ - `phase-10-txt-memory-extract` — Step 11 memory sediment (24h mode auto-runs the extract)
84
+ - `step-n+1-final-review` — final review (24h mode auto-fires the 4-dim review; user can still inspect the artifact post-hoc)
85
+
86
+ ### What 24h mode DOES NOT BYPASS (the hard rules)
87
+
88
+ 24h mode is **only** a "no AskUserQuestion" override. It is NOT a "skip the entire peaks-code flow" override. The following peaks-code rules are **always** enforced under 24h mode:
89
+
90
+ - **Code-Change Red Line** (prose rule + slice 4.0.7-PR-meta-1 Edit-enforcement hook). Every code change goes through `peaks-code → peaks-rd → peaks-qa → verdict`. The LLM MUST NOT edit `src/**` directly. Use `peaks sub-agent dispatch rd`.
91
+ - **Sub-agent dispatch** for non-trivial work. Even in 24h mode, the LLM is not exempt from dispatching `peaks-rd` / `peaks-qa`. The `--apply` flag in 24h mode picks the recommended option in the dispatch envelope, but the dispatch still happens (slice 4.0.7-PR-meta-4).
92
+ - **Worktree authorization** (slice 2026-07-29-worktree-layer3-deny). 24h mode does NOT widen Layer 3's deny list. The 3-layer governance (L1 prose + L2 hook + L3 IDE permissions.deny) remains in force.
93
+ - **Request state machine transitions** (`spec-locked → implemented → qa-handoff → handed-off`). 24h mode is not a state-machine bypass.
94
+ - **External side effects** (`git push`, `git tag`, `npm publish`, `npm install -g peaks-loop`, `peaks global install`). The 5 commit-boundary hard-floor actions always pause, even in 24h mode.
95
+ - **Reverse 2.x → 1.x migration**, **git reset --hard**, **force-push to protected branches**. Same hard-floor logic.
96
+
97
+ ### The single-sentence rule (paste into any new peaks-code session that flips 24h mode)
98
+
99
+ > 24h mode lets the LLM auto-proceed at the AskUserQuestion gates (mode select, step confirm, swarm pick, etc.). It does **not** let the LLM skip the peaks-code 11-step flow, edit `src/**` directly, or bypass request-state transitions.
100
+
66
101
  ## Peaks-Loop Superpowers 协作边界 (BRIDGE — MANDATORY, effective 2026-07-24)
67
102
 
68
103
  Pins the boundary between `peaks-code` and the **superpowers** skill family (`brainstorming`, `writing-plans`, `executing-plans`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `test-driven-development`, `systematic-debugging`, `requesting-code-review`, `receiving-code-review`, `using-superpowers`, `using-git-worktrees`, `finishing-a-development-branch`). Closed under slice 2026-07-24-peaks-code-bridge-002-rootcause.
@@ -114,7 +149,7 @@ Full content extracted to **`references/startup-sequence.md`** (Steps 0 / 0.5-0.
114
149
  | `0.85 ≤ ratio < 0.95` | **pre-compact zone** | `peaks compact auto --execute` fires **automatically** (deferred only when in-flight sub-agent batch is running; fires the moment the batch lands). The LLM does not prompt the user. |
115
150
  | `ratio ≥ 0.95` | **red-line (Karpathy §4)** | synchronous gate — `peaks compact auto --execute` invoked immediately; `peaks code context-now` returns `action: 'red-line'` and refuses to advance until ratio drops below 0.85. **Karpathy §4 automatic exception** — LLM cannot opt out. |
116
151
 
117
- **Probe primitive (single source of truth):** `peaks code context-now --json`. Do NOT use `peaks context check --prompt-size` (deprecated, will silently under-report ratio). Returns `{ ratio, action: 'ok' | 'soft-warn' | 'auto-compact-now' | 'red-line' }` — Code reads `action` and dispatches `peaks compact auto --execute` on `auto-compact-now` or `red-line` without user confirmation.
152
+ **Probe primitive (single source of truth):** `peaks code context-now --json --project <path>`. Do NOT use `peaks context check --prompt-size` (deprecated, will silently under-report ratio). Returns `{ ratio, action: 'ok' | 'soft-warn' | 'auto-compact-now' | 'red-line' }` — Code reads `action` and dispatches `peaks compact auto --execute` on `auto-compact-now` or `red-line` without user confirmation. **Always pass `--project` explicitly** — the CLI does not auto-resolve cwd (see Drift Index D-013).
118
153
 
119
154
  **Enforcement layers (defense in depth):**
120
155
  1. `src/services/code/auto-compact-orchestrator.ts` — `evaluateAutoCompactDecision` default-returns `shouldCompact: true` for both `pre-compact` and `red-line`. Only deferral is `inFlightBatch.hasInFlightBatch` (D6.e); no LLM/human approval branch.
@@ -127,18 +162,21 @@ Full content extracted to **`references/startup-sequence.md`** (Steps 0 / 0.5-0.
127
162
 
128
163
  If the prose audit (`peaks audit red-lines`) flags any of the above, the slice is **blocked** until the prose is rewritten.
129
164
 
130
- ## CLI Drift Index (sediment 2026-07-09)
165
+ ## CLI Drift Index (sediment 2026-07-09, refreshed 2026-08-02)
131
166
 
132
- > **Reading guide:** Verified against peaks-loop 4.0.0-beta.6. Each drift below is annotated inline at the relevant step with a `> CLI reality check`. On `error: unknown option ...`, **read the inline reality check first** before guessing.
167
+ > **Reading guide:** Verified against peaks-loop 4.0.6. Each drift below is annotated inline at the relevant step with a `> CLI reality check`. On `error: unknown option ...`, **read the inline reality check first** before guessing. The 4.0.7 dogfood pass (ice-cola surface probe) re-validated all entries; D-001 was rewritten (the pre-4.0.7 entry was the inverse of 4.0.6's actual behavior).
133
168
 
134
169
  | Drift ID | Step | Symptom | Fix | Inline location |
135
170
  |---|---|---|---|---|
136
- | **D-001** | 0.8 | `peaks code detect-job --is-job ...` rejected with `error: unknown option '--is-job'` | Use `peaks job init --job-id <jid> --slice-list <list> --main-loop-strategy <single\|rotating>` | §Step 0.8 first paragraph |
171
+ | **D-001** | 0.8 | `peaks code detect-job --is-job ...` requires the flag (4.0.6+); pre-4.0.6 the flag was rejected. Either way, you must pass `--is-job <bool> --rationale <text> --suggested-job-id <slug>` | `peaks code detect-job --is-job <true\|false> --rationale "<1-3 sentences>" --suggested-job-id <slug>` | §Step 0.8 first paragraph |
137
172
  | **D-002** | 2.5 | `peaks session title --session-id <sid> ...` rejected with `error: unknown option '--session-id'` (this is the bare `<sid>` anti-pattern) | sid is positional: `peaks session title <sessionId> "<title>" --json` | §Step 2.5 |
138
173
  | **D-003** | 0.8 | `JOB_SHAPE_NOT_DECIDED` exception expected but never thrown | Current behavior is `peaks job status` reports `done: 0` passively — treat as recoverable miss, not hard error | §Step 0.8 third paragraph |
139
174
  | **D-010** | 11c | `peaks memory extract` returns `extractedCount: 0` despite `<!-- peaks-memory:start -->` existing | Block requires YAML frontmatter (`title:` + `kind:` + `---`) + closing `<!-- peaks-memory:end -->`. Bare `peaks-memory:start` is parsed silently but produces no writes | §Step 11c + 11d |
175
+ | **D-013** | N+2 | `peaks code context-now --json` (no `--project`) rejected with `error: required option '--project <path>' not specified` | Always pass `--project <path>` (use `.` for the cwd). The CLI does not auto-resolve cwd because monorepos + CI have surprising cwd semantics. | §Step N+2 probe primitive |
176
+ | **D-014** | 0.8 | `peaks code detect-job` / `read-job-shape` / `peaks workflow plan read --type <type>` / `peaks route --goal <g>` all reported `NO_ACTIVE_SESSION` even when `.peaks/_runtime/session.json` was a valid binding. The 4 CLIs read `getSkillPresence` (the `.peaks/.active-skill.json` file, set only by `peaks skill presence:set <skill>`) instead of the canonical binding. Fixed in 4.0.7 PR-2 via `resolveActiveSessionId` shared resolver — no action required at the call site, but if you see a `NO_ACTIVE_SESSION` error, run `peaks workspace init --project <path>` first. | §Step 0.8 third paragraph + §Step 1 mode select |
177
+ | **D-015** | 0.8 | `peaks code should-pause --step step-1-mode-select` reports `stale-presence: no-presence` even when 24h-mode is active. 24h-mode never sets skill presence by design (no sub-skill flips). Fixed in 4.0.7 PR-3 — gate short-circuits the stale-presence check when `24h-state.json#state === '24H_ACTIVE'`. | §Step 1 mode select + §24h mode |
140
178
 
141
- > **Sediment lesson (master record):** `.peaks/memory/peaks-code-runbook-4-0-0-beta-6-skill-md-cli-d-001-d-002-d-003-d-010.md`
179
+ > **Sediment lesson (master record):** `.peaks/memory/peaks-code-runbook-4-0-0-beta-6-skill-md-cli-d-001-d-002-d-003-d-010.md` (pre-4.0.7); `.peaks/memory/2026-08-02-ice-cola-dogfood-surface-probe.md` (4.0.7 fixes).
142
180
 
143
181
  ## Peaks-Loop GStack integration
144
182