peaks-loop 4.0.7 → 4.0.9

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 (156) hide show
  1. package/CHANGELOG.md +58 -288
  2. package/dist/cli/commands/_register.js +2 -0
  3. package/dist/cli/commands/baseline-commands.d.ts +3 -0
  4. package/dist/cli/commands/baseline-commands.js +146 -0
  5. package/dist/cli/commands/code-job-shape-commands.js +8 -11
  6. package/dist/cli/commands/code-mode-gate-commands.js +1 -43
  7. package/dist/cli/commands/code-runtime-commands.js +36 -14
  8. package/dist/cli/commands/core/skill-command.js +75 -13
  9. package/dist/cli/commands/core/standards-command.js +5 -50
  10. package/dist/cli/commands/dispatch-commands.d.ts +20 -0
  11. package/dist/cli/commands/dispatch-commands.js +39 -38
  12. package/dist/cli/commands/dispatch-from-dag.js +17 -0
  13. package/dist/cli/commands/heartbeat-commands.d.ts +21 -0
  14. package/dist/cli/commands/heartbeat-commands.js +40 -0
  15. package/dist/cli/commands/hook-handle.js +5 -2
  16. package/dist/cli/commands/hooks-commands.js +9 -26
  17. package/dist/cli/commands/loop-eval-commands.js +5 -2
  18. package/dist/cli/commands/statusline-commands.js +1 -1
  19. package/dist/cli/commands/sub-agent-commands.js +0 -2
  20. package/dist/cli/commands/sub-agent-shared.d.ts +7 -9
  21. package/dist/cli/commands/workflow-commands.js +8 -0
  22. package/dist/cli/commands/workflow-lifecycle-commands.d.ts +63 -0
  23. package/dist/cli/commands/workflow-lifecycle-commands.js +302 -0
  24. package/dist/cli/commands/workspace/init-command.js +19 -0
  25. package/dist/services/audit/backing-detector.d.ts +2 -19
  26. package/dist/services/audit/backing-detector.js +7 -66
  27. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +25 -9
  28. package/dist/services/audit/enforcers/active-skill-resolver.js +83 -21
  29. package/dist/services/audit/enforcers/lint-audit-regression.js +1 -10
  30. package/dist/services/capability-audit-service/cross-check.d.ts +8 -0
  31. package/dist/services/capability-audit-service/cross-check.js +11 -0
  32. package/dist/services/capability-audit-service/index.d.ts +3 -0
  33. package/dist/services/capability-audit-service/index.js +2 -0
  34. package/dist/services/capability-audit-service/runner.d.ts +27 -0
  35. package/dist/services/capability-audit-service/runner.js +40 -0
  36. package/dist/services/capability-audit-service/staleness.d.ts +1 -0
  37. package/dist/services/capability-audit-service/staleness.js +7 -0
  38. package/dist/services/capability-audit-service/types.d.ts +24 -0
  39. package/dist/services/capability-audit-service/types.js +1 -0
  40. package/dist/services/capability-baseline/store.d.ts +33 -0
  41. package/dist/services/capability-baseline/store.js +80 -0
  42. package/dist/services/capability-baseline/types.d.ts +45 -0
  43. package/dist/services/capability-baseline/types.js +5 -0
  44. package/dist/services/capability-baseline/validator.d.ts +19 -0
  45. package/dist/services/capability-baseline/validator.js +38 -0
  46. package/dist/services/capability-guard-runner/contracts/J01.d.ts +2 -0
  47. package/dist/services/capability-guard-runner/contracts/J01.js +48 -0
  48. package/dist/services/capability-guard-runner/contracts/J02.d.ts +2 -0
  49. package/dist/services/capability-guard-runner/contracts/J02.js +34 -0
  50. package/dist/services/capability-guard-runner/contracts/J03.d.ts +2 -0
  51. package/dist/services/capability-guard-runner/contracts/J03.js +24 -0
  52. package/dist/services/capability-guard-runner/contracts/J04.d.ts +2 -0
  53. package/dist/services/capability-guard-runner/contracts/J04.js +40 -0
  54. package/dist/services/capability-guard-runner/contracts/J05.d.ts +2 -0
  55. package/dist/services/capability-guard-runner/contracts/J05.js +21 -0
  56. package/dist/services/capability-guard-runner/contracts/J06.d.ts +2 -0
  57. package/dist/services/capability-guard-runner/contracts/J06.js +43 -0
  58. package/dist/services/capability-guard-runner/contracts/J07.d.ts +2 -0
  59. package/dist/services/capability-guard-runner/contracts/J07.js +53 -0
  60. package/dist/services/capability-guard-runner/contracts/J08.d.ts +2 -0
  61. package/dist/services/capability-guard-runner/contracts/J08.js +43 -0
  62. package/dist/services/capability-guard-runner/contracts/J09.d.ts +2 -0
  63. package/dist/services/capability-guard-runner/contracts/J09.js +44 -0
  64. package/dist/services/capability-guard-runner/contracts/J10.d.ts +2 -0
  65. package/dist/services/capability-guard-runner/contracts/J10.js +40 -0
  66. package/dist/services/capability-guard-runner/contracts/J11.d.ts +2 -0
  67. package/dist/services/capability-guard-runner/contracts/J11.js +39 -0
  68. package/dist/services/capability-guard-runner/contracts/J12.d.ts +2 -0
  69. package/dist/services/capability-guard-runner/contracts/J12.js +34 -0
  70. package/dist/services/capability-guard-runner/contracts/J13.d.ts +2 -0
  71. package/dist/services/capability-guard-runner/contracts/J13.js +45 -0
  72. package/dist/services/capability-guard-runner/contracts/J14.d.ts +2 -0
  73. package/dist/services/capability-guard-runner/contracts/J14.js +36 -0
  74. package/dist/services/capability-guard-runner/contracts/J15.d.ts +2 -0
  75. package/dist/services/capability-guard-runner/contracts/J15.js +40 -0
  76. package/dist/services/capability-guard-runner/diff.d.ts +2 -0
  77. package/dist/services/capability-guard-runner/diff.js +7 -0
  78. package/dist/services/capability-guard-runner/runner.d.ts +9 -0
  79. package/dist/services/capability-guard-runner/runner.js +22 -0
  80. package/dist/services/capability-guard-runner/types.d.ts +39 -0
  81. package/dist/services/capability-guard-runner/types.js +1 -0
  82. package/dist/services/code/auto-compact-orchestrator.d.ts +35 -2
  83. package/dist/services/code/auto-compact-orchestrator.js +29 -9
  84. package/dist/services/context/build-dispatch-system-prompt.js +0 -18
  85. package/dist/services/dispatch/dispatch-record-writer.d.ts +25 -1
  86. package/dist/services/dispatch/dispatch-record-writer.js +85 -15
  87. package/dist/services/dispatch/test-tool-detection.d.ts +0 -14
  88. package/dist/services/dispatch/test-tool-detection.js +0 -22
  89. package/dist/services/doctor/doctor-service/checks/skill-presence.d.ts +8 -0
  90. package/dist/services/doctor/doctor-service/checks/skill-presence.js +9 -1
  91. package/dist/services/evolution/evolution-types.d.ts +16 -16
  92. package/dist/services/final-review/final-review-service.d.ts +8 -0
  93. package/dist/services/final-review/final-review-service.js +14 -0
  94. package/dist/services/final-review/final-review-types.d.ts +1 -1
  95. package/dist/services/hooks/presence-marker-detector.d.ts +6 -0
  96. package/dist/services/hooks/presence-marker-detector.js +48 -0
  97. package/dist/services/ide/adapters/claude-code-adapter.js +20 -0
  98. package/dist/services/ide/adapters/codex-adapter.js +20 -1
  99. package/dist/services/ide/adapters/cursor-adapter.js +21 -1
  100. package/dist/services/ide/adapters/hermes-adapter.js +20 -1
  101. package/dist/services/ide/adapters/openclaw-adapter.js +20 -1
  102. package/dist/services/ide/adapters/qoder-adapter.js +20 -1
  103. package/dist/services/ide/adapters/tongyi-lingma-adapter.js +20 -1
  104. package/dist/services/ide/adapters/trae-adapter.js +21 -1
  105. package/dist/services/ide/adapters/zcode-adapter.js +19 -0
  106. package/dist/services/ide/ide-types.d.ts +15 -0
  107. package/dist/services/job/job-progress-store.d.ts +2 -2
  108. package/dist/services/job/job-types.d.ts +26 -26
  109. package/dist/services/observability/observability-service.d.ts +4 -4
  110. package/dist/services/scan/archetype-service.js +2 -52
  111. package/dist/services/sediment/json-schema.d.ts +4 -4
  112. package/dist/services/session/caller-binding-service.d.ts +21 -0
  113. package/dist/services/session/caller-binding-service.js +35 -0
  114. package/dist/services/session/caller-id-types.d.ts +62 -13
  115. package/dist/services/session/caller-id-types.js +25 -13
  116. package/dist/services/session/index.d.ts +4 -2
  117. package/dist/services/session/index.js +8 -2
  118. package/dist/services/session/platform-fallbacks.d.ts +25 -17
  119. package/dist/services/session/platform-fallbacks.js +26 -28
  120. package/dist/services/session/resolve-caller-id.d.ts +64 -29
  121. package/dist/services/session/resolve-caller-id.js +122 -48
  122. package/dist/services/session/session-manager.d.ts +0 -29
  123. package/dist/services/session/session-manager.js +0 -33
  124. package/dist/services/session/session-resume-service.d.ts +4 -0
  125. package/dist/services/session/session-resume-service.js +4 -0
  126. package/dist/services/skills/hooks-settings-service.d.ts +0 -41
  127. package/dist/services/skills/hooks-settings-service.js +5 -61
  128. package/dist/services/skills/presence-lease-service.d.ts +73 -0
  129. package/dist/services/skills/presence-lease-service.js +358 -0
  130. package/dist/services/skills/presence-lease-types.d.ts +75 -0
  131. package/dist/services/skills/presence-lease-types.js +16 -0
  132. package/dist/services/skills/skill-presence-service.js +86 -22
  133. package/dist/services/skills/skill-statusline-renderer.d.ts +46 -56
  134. package/dist/services/skills/skill-statusline-renderer.js +312 -100
  135. package/dist/services/slice/slice-check-service.js +0 -59
  136. package/dist/services/standards/project-standards-service.js +0 -44
  137. package/dist/services/workflow/workflow-graph-store.d.ts +77 -0
  138. package/dist/services/workflow/workflow-graph-store.js +278 -0
  139. package/dist/services/workflow/workflow-graph-types.d.ts +56 -0
  140. package/dist/services/workflow/workflow-graph-types.js +67 -0
  141. package/dist/services/workflow/workflow-inflight-probe.d.ts +52 -0
  142. package/dist/services/workflow/workflow-inflight-probe.js +86 -0
  143. package/dist/services/workflow/workflow-node-lifecycle.d.ts +67 -0
  144. package/dist/services/workflow/workflow-node-lifecycle.js +348 -0
  145. package/dist/services/workflow/workflow-presence-lifecycle.d.ts +58 -0
  146. package/dist/services/workflow/workflow-presence-lifecycle.js +196 -0
  147. package/dist/services/workspace/reconcile-service.d.ts +31 -0
  148. package/dist/services/workspace/reconcile-service.js +132 -1
  149. package/package.json +5 -4
  150. package/skills/peaks-code/SKILL.md +5 -43
  151. package/skills/peaks-code/references/completion-handoff.md +1 -1
  152. package/skills/peaks-code/references/skill-presence-and-title.md +1 -1
  153. package/dist/cli/commands/sub-agent-exec-command.d.ts +0 -25
  154. package/dist/cli/commands/sub-agent-exec-command.js +0 -132
  155. package/dist/services/standards/find-skills-integration.d.ts +0 -59
  156. package/dist/services/standards/find-skills-integration.js +0 -225
@@ -16,7 +16,7 @@
16
16
  * Pure hand-rolled; uses only node:fs, node:path, and the existing
17
17
  * session-manager helper for writing the binding. No new dependencies.
18
18
  */
19
- import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync } from 'node:fs';
19
+ import { copyFileSync, existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
20
20
  import { dirname, join, resolve } from 'node:path';
21
21
  import { getSessionIdCanonical, setCurrentSessionBinding } from '../session/session-manager.js';
22
22
  const SESSION_ID_PATTERN = /^\d{4}-\d{2}-\d{2}-session-[a-f0-9]+$/;
@@ -681,3 +681,134 @@ export function reconcileWorkspace(options) {
681
681
  systemCleaned
682
682
  };
683
683
  }
684
+ /**
685
+ * Idempotent migration of legacy presence markers
686
+ * (`.peaks/_runtime/active-skill.json` and
687
+ * `.peaks/_runtime/<sid>/active-skill-*.json`) into the canonical
688
+ * 4.0.8 lease + index + graph layout. Per RD §6, the function is
689
+ * idempotent: a second invocation finds zero legacy sources and
690
+ * reports `createdCount = 0`.
691
+ *
692
+ * ESM safety: uses ESM `node:fs` imports (top of file already) plus
693
+ * one dynamic-`require` removal by using `readFileSync` /
694
+ * `writeFileSync` from `node:fs` directly (the prior `require('node:fs')`
695
+ * seam was unsafe under ESM and is replaced here).
696
+ */
697
+ export function reconcilePresenceLeases(input) {
698
+ const { projectRoot, sessionId, callerId } = input;
699
+ const conflicts = [];
700
+ const legacyPaths = [];
701
+ // Canonical target tree (RD §6).
702
+ const runtimeRoot = join(projectRoot, '.peaks', '_runtime');
703
+ const sessionRoot = join(runtimeRoot, sessionId);
704
+ const leasesDir = join(sessionRoot, 'leases');
705
+ const presenceIndexDir = join(sessionRoot, 'presence-index');
706
+ const migrationsDir = join(sessionRoot, 'migrations');
707
+ // Detect canonical corruption first (RD §3 D3 + §6): a malformed
708
+ // active-skill.json is a typed conflict, not a silent fallback.
709
+ const newPath = join(runtimeRoot, 'active-skill.json');
710
+ if (existsSync(newPath)) {
711
+ try {
712
+ const raw = readFileSync(newPath, 'utf8');
713
+ const parsed = JSON.parse(raw);
714
+ if (typeof parsed?.skill !== 'string') {
715
+ conflicts.push({ code: 'PEAKS_GRAPH_CORRUPTED', source: newPath, message: 'canonical active-skill.json malformed' });
716
+ }
717
+ }
718
+ catch (err) {
719
+ conflicts.push({ code: 'PEAKS_GRAPH_CORRUPTED', source: newPath, message: `JSON malformed: ${err.message}` });
720
+ }
721
+ }
722
+ // Legacy sources (per-caller markers + the singleton).
723
+ const legacyPath2 = join(runtimeRoot, 'legacy-active-skill.json');
724
+ if (existsSync(legacyPath2)) {
725
+ legacyPaths.push(legacyPath2);
726
+ }
727
+ // Receipt path: idempotency contract.
728
+ const receiptPath = join(migrationsDir, 'presence-v1.json');
729
+ let migratedCount = 0;
730
+ if (existsSync(receiptPath)) {
731
+ try {
732
+ const receipt = JSON.parse(readFileSync(receiptPath, 'utf8'));
733
+ migratedCount = typeof receipt.migratedCount === 'number' ? receipt.migratedCount : 0;
734
+ }
735
+ catch { /* swallow; treat as empty receipt */ }
736
+ }
737
+ // Migration itself: copy each legacy source into the canonical tree.
738
+ if (!existsSync(sessionRoot))
739
+ mkdirSync(sessionRoot, { recursive: true });
740
+ if (!existsSync(leasesDir))
741
+ mkdirSync(leasesDir, { recursive: true });
742
+ if (!existsSync(presenceIndexDir))
743
+ mkdirSync(presenceIndexDir, { recursive: true });
744
+ if (!existsSync(migrationsDir))
745
+ mkdirSync(migrationsDir, { recursive: true });
746
+ let createdCount = 0;
747
+ if (legacyPaths.length === 0 && conflicts.length === 0) {
748
+ // Idempotent path: nothing to do.
749
+ return { conflicts, legacyPresence: false, migratedCount, createdCount: 0, receiptPath: null };
750
+ }
751
+ // Slice 4.0.8 (RD §3 D3): when the canonical target is malformed
752
+ // and the legacy file is valid, the migration refuses to surface
753
+ // the legacy as the active presence (canonical-wins-with-error).
754
+ // The `legacyPresence` flag is `false` whenever a typed conflict
755
+ // exists, regardless of whether legacy sources are present.
756
+ const hasCanonicalConflict = conflicts.some((c) => c.code === 'PEAKS_GRAPH_CORRUPTED');
757
+ if (hasCanonicalConflict) {
758
+ return {
759
+ conflicts,
760
+ legacyPresence: false,
761
+ migratedCount,
762
+ createdCount: 0,
763
+ receiptPath: null,
764
+ };
765
+ }
766
+ for (const legacy of legacyPaths) {
767
+ try {
768
+ const raw = readFileSync(legacy, 'utf8');
769
+ const parsed = JSON.parse(raw);
770
+ const skill = typeof parsed.skill === 'string' ? parsed.skill : 'peaks-code';
771
+ const workflowId = `wf-migrated-${Date.now().toString(36)}`;
772
+ const caller = callerId ?? 'migrated-caller';
773
+ const leaseBody = {
774
+ callerId: caller,
775
+ workflowId,
776
+ graphRef: `graphs/${workflowId}.json`,
777
+ skill,
778
+ depth: 0,
779
+ startedAt: new Date().toISOString(),
780
+ lastHeartbeat: new Date().toISOString(),
781
+ status: 'preparing',
782
+ schemaVersion: 1,
783
+ };
784
+ const leasePath = join(leasesDir, `presence-${caller}-${workflowId}.json`);
785
+ const tmpPath = `${leasePath}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
786
+ writeFileSync(tmpPath, JSON.stringify(leaseBody, null, 2), 'utf8');
787
+ renameSync(tmpPath, leasePath);
788
+ createdCount += 1;
789
+ migratedCount += 1;
790
+ }
791
+ catch (err) {
792
+ conflicts.push({ code: 'PEAKS_LEASE_IO_FAILED', source: legacy, message: err.message });
793
+ }
794
+ }
795
+ if (createdCount > 0) {
796
+ const receipt = {
797
+ schemaVersion: 1,
798
+ migratedCount,
799
+ sources: legacyPaths,
800
+ ts: new Date().toISOString(),
801
+ };
802
+ try {
803
+ writeFileSync(receiptPath, JSON.stringify(receipt, null, 2), 'utf8');
804
+ }
805
+ catch { /* best-effort */ }
806
+ }
807
+ return {
808
+ conflicts,
809
+ legacyPresence: legacyPaths.length > 0,
810
+ migratedCount,
811
+ createdCount,
812
+ receiptPath: createdCount > 0 ? receiptPath : null,
813
+ };
814
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "4.0.7",
3
+ "version": "4.0.9",
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-shared-channel": "0.0.16",
102
- "peaks-loop-shared": "0.0.37",
103
- "peaks-loop-mut": "0.1.12"
101
+ "peaks-loop-mut": "0.1.15",
102
+ "peaks-loop-shared": "0.0.40",
103
+ "peaks-loop-shared-channel": "0.0.19"
104
104
  },
105
105
  "peerDependencies": {
106
106
  "@alibaba-group/open-code-review": "1.3.1"
@@ -142,6 +142,7 @@
142
142
  "test:dev:cli": "vitest run tests/unit",
143
143
  "test:unit": "vitest run tests/unit",
144
144
  "test:integration": "vitest run tests/integration",
145
+ "test:capability-guard": "vitest run --config vitest.config.integration.ts tests/integration/capability-guard",
145
146
  "test:cli": "vitest run tests/unit",
146
147
  "test:workflow": "vitest run tests/unit",
147
148
  "test:audit:silent-warning": "node ./scripts/lint/silent-warning-detector.mjs",
@@ -63,41 +63,6 @@ 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
-
101
66
  ## Peaks-Loop Superpowers 协作边界 (BRIDGE — MANDATORY, effective 2026-07-24)
102
67
 
103
68
  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.
@@ -149,7 +114,7 @@ Full content extracted to **`references/startup-sequence.md`** (Steps 0 / 0.5-0.
149
114
  | `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. |
150
115
  | `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. |
151
116
 
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).
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.
153
118
 
154
119
  **Enforcement layers (defense in depth):**
155
120
  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.
@@ -162,21 +127,18 @@ Full content extracted to **`references/startup-sequence.md`** (Steps 0 / 0.5-0.
162
127
 
163
128
  If the prose audit (`peaks audit red-lines`) flags any of the above, the slice is **blocked** until the prose is rewritten.
164
129
 
165
- ## CLI Drift Index (sediment 2026-07-09, refreshed 2026-08-02)
130
+ ## CLI Drift Index (sediment 2026-07-09)
166
131
 
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).
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.
168
133
 
169
134
  | Drift ID | Step | Symptom | Fix | Inline location |
170
135
  |---|---|---|---|---|
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 |
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 |
172
137
  | **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 |
173
138
  | **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 |
174
139
  | **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 |
178
140
 
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).
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`
180
142
 
181
143
  ## Peaks-Loop GStack integration
182
144
 
@@ -8,7 +8,7 @@ Use Peaks-Loop TXT for the compact handoff capsule: mode, validated decisions, a
8
8
 
9
9
  ## Workflow completion (no auto-exit)
10
10
 
11
- peaks-code does NOT itself call `peaks skill presence:clear --project <repo>` at workflow end. Presence management is delegated to the last downstream skill in the workflow (peaks-rd, peaks-qa, peaks-txt); each of those skills owns its own presence:clear step per its SKILL.md. peaks-code only sets presence: it does not unset it.
11
+ peaks-code does NOT itself call `peaks skill presence:clear --project <repo>` at workflow end. Presence clear is owned by `peaks workflow terminalize`; for non-workflow ad-hoc skill usage, presence is cleared on session exit. Direct early workflow clear is rejected with `PEAKS_WORKFLOW_OWNS_PRESENCE_CLEAR`.
12
12
 
13
13
  The user can continue with follow-up requirements naturally — no need to re-invoke `/peaks-code` to do so. The header continues to display whatever skill is active; the user can `/peaks-code` again to re-anchor.
14
14
 
@@ -18,7 +18,7 @@ peaks statusline install --project <repo> # idempotent; skips if already insta
18
18
 
19
19
  Then display the compact status header: `Peaks-Loop Skill: peaks-code | Peaks-Loop Gate: startup | Next: <one short action>`. Display this header on EVERY turn while the skill is active.
20
20
 
21
- Update with `peaks skill presence:set peaks-code --project <repo> --mode <mode> --gate <gate>` when gates change. The presence file persists across the full workflow lifecycle — do NOT clear it at workflow end.
21
+ Update with `peaks skill presence:set peaks-code --project <repo> --mode <mode> --gate <gate>` when gates change. The presence file persists across the full workflow lifecycle — clear is owned by workflow terminalize; follow-up workflows re-claim via new workflow init.
22
22
 
23
23
  ## Step 2.5 — Set session title
24
24
 
@@ -1,25 +0,0 @@
1
- import type { Command } from 'commander';
2
- import { type ProgramIO } from '../cli-helpers.js';
3
- /**
4
- * Shape of a dispatch record on disk (slice 2026-06-07 +
5
- * dispatch-record metadata). We only read the fields `exec` needs
6
- * to re-emit the toolCall envelope; everything else (heartbeat,
7
- * artifacts, lease, isolation) is preserved by the dispatch record
8
- * itself and surfaced in the re-emitted envelope.
9
- */
10
- interface DispatchRecordToolCall {
11
- name: string;
12
- args: Record<string, unknown>;
13
- }
14
- interface DispatchRecord {
15
- toolCall?: DispatchRecordToolCall;
16
- promptSize?: number;
17
- originalPromptSize?: number;
18
- batchId?: string;
19
- rid?: string;
20
- role?: string;
21
- sessionId?: string;
22
- }
23
- export declare function registerSubAgentExecCommand(parent: Command, io: ProgramIO): void;
24
- export declare function readDispatchRecordForExec(dispatchRecordPath: string): DispatchRecord;
25
- export {};
@@ -1,132 +0,0 @@
1
- /**
2
- * `peaks sub-agent exec` — slice 4.0.7-PR-meta-6.
3
- *
4
- * Companion to `peaks sub-agent dispatch` (slice 2026-06-07). The
5
- * dispatch CLI is dry-run by design (per skill-first / CLI-auxiliary
6
- * red line PB-4): it returns a `toolCall` envelope that the LLM
7
- * extracts args from and re-executes via the Agent tool. The 4.0.7
8
- * ice-cola dogfood pass surfaced the friction: the LLM often
9
- * skipped the dispatch altogether instead of paying the parse
10
- * cost, and routed `Edit` calls directly. PR-meta-4 lowered the
11
- * friction by adding `--emit-bash-script`, which prints a
12
- * ready-to-exec bash script alongside the JSON envelope. PR-meta-6
13
- * ships the actual surface the script invokes: this `exec`
14
- * sub-command reads the dispatch record from disk (the same
15
- * record the dispatch CLI wrote) and re-emits the canonical
16
- * toolCall envelope so the LLM does not have to hand-parse the
17
- * JSON to retry a dispatch.
18
- *
19
- * Architecture (PR-meta-6):
20
- * 1. The user (or the LLM via Bash tool) runs the script
21
- * emitted by `peaks sub-agent dispatch --emit-bash-script`.
22
- * 2. The script invokes `peaks sub-agent exec --session-id ...
23
- * --request-id ... --role ... --batch-id ... --dispatch-record ...
24
- * --project ...`.
25
- * 3. `exec` reads the dispatch record from disk, re-emits the
26
- * same toolCall envelope, and prints a single next-action
27
- * line: "Run this Task tool call to dispatch."
28
- *
29
- * The dry-run architecture is preserved: the LLM still has the
30
- * final say on whether to actually spawn the Task tool. The
31
- * script + exec pair is a transparent retry path, not a side-step.
32
- */
33
- import { existsSync, readFileSync } from 'node:fs';
34
- import { dirname } from 'node:path';
35
- import { addJsonOption, printResult } from '../cli-helpers.js';
36
- import { fail, ok } from 'peaks-loop-shared/result';
37
- function failExec(command, message, options, hint) {
38
- printResult(undefined, // io is plumbed below
39
- fail(command, 'SUB_AGENT_EXEC_FAILED', message, null, [hint]), options.json);
40
- // The above `printResult` expects io — the real call site passes
41
- // io explicitly. This helper is only used for type-level
42
- // documentation; the actual printing is inline in the action.
43
- throw new Error(message);
44
- }
45
- export function registerSubAgentExecCommand(parent, io) {
46
- addJsonOption(parent
47
- .command('exec')
48
- .description('Slice 4.0.7-PR-meta-6. Re-emit a stored dispatch envelope. Companion to `peaks sub-agent dispatch --emit-bash-script`. Reads the dispatch record from `--dispatch-record` and prints the canonical toolCall envelope so the LLM does not have to hand-parse the JSON to retry. Dry-run: the LLM still owns the actual Agent tool invocation.')
49
- .requiredOption('--session-id <sid>', 'session id the dispatch was created in')
50
- .requiredOption('--request-id <rid>', 'request id the dispatch was created for')
51
- .requiredOption('--role <role>', 'sub-agent role (rd | qa | ui | sc | txt | qa-business | qa-business-api)')
52
- .requiredOption('--batch-id <uuid>', 'batch id the dispatch was created in')
53
- .requiredOption('--dispatch-record <path>', 'path to the dispatch record JSON (the same file the dispatch CLI wrote under .peaks/_sub_agents/<sid>/)')
54
- .requiredOption('--project <path>', 'target project root (echoed back in the envelope; not used to read files)')).action((options) => {
55
- if (options.dispatchRecord === undefined || !existsSync(options.dispatchRecord)) {
56
- printResult(io, fail('sub-agent.exec', 'DISPATCH_RECORD_MISSING', `Dispatch record not found at ${options.dispatchRecord ?? '(unset)'}`, null, [
57
- 'Re-run `peaks sub-agent dispatch <role> --prompt <text> --request-id <rid> --json` to create the record.',
58
- 'Then pass the emitted `dispatchRecordPath` to `peaks sub-agent exec --dispatch-record <path>`.'
59
- ]), options.json);
60
- process.exitCode = 1;
61
- return;
62
- }
63
- let record;
64
- try {
65
- record = JSON.parse(readFileSync(options.dispatchRecord, 'utf8'));
66
- }
67
- catch (err) {
68
- printResult(io, fail('sub-agent.exec', 'DISPATCH_RECORD_INVALID', `Failed to parse dispatch record: ${err instanceof Error ? err.message : String(err)}`, { dispatchRecord: options.dispatchRecord }, [
69
- 'The dispatch record may be truncated. Re-run `peaks sub-agent dispatch` to create a fresh record.'
70
- ]), options.json);
71
- process.exitCode = 1;
72
- return;
73
- }
74
- if (record.toolCall === undefined) {
75
- printResult(io, fail('sub-agent.exec', 'DISPATCH_RECORD_NO_TOOL_CALL', 'Dispatch record has no `toolCall` field. The record may be from a pre-toolCall era; re-run `peaks sub-agent dispatch` to create a fresh record.', { dispatchRecord: options.dispatchRecord }, [
76
- 'Re-run `peaks sub-agent dispatch` to create a fresh record with a `toolCall` envelope.'
77
- ]), options.json);
78
- process.exitCode = 1;
79
- return;
80
- }
81
- // Sanity check: the record's sessionId / rid / batchId / role
82
- // should match the CLI flags (if the caller passed them). This
83
- // is defensive: a mismatch usually means the operator is
84
- // running the wrong script. We surface a warning but still
85
- // re-emit the envelope (do not block — the dispatch record
86
- // is the canonical source of truth).
87
- const warnings = [];
88
- if (options.sessionId !== undefined && record.sessionId !== undefined && record.sessionId !== options.sessionId) {
89
- warnings.push(`session-id mismatch: record=${record.sessionId} vs --session-id=${options.sessionId}; using the record's value as the canonical source.`);
90
- }
91
- if (options.requestId !== undefined && record.rid !== undefined && record.rid !== options.requestId) {
92
- warnings.push(`request-id mismatch: record=${record.rid} vs --request-id=${options.requestId}; using the record's value.`);
93
- }
94
- if (options.batchId !== undefined && record.batchId !== undefined && record.batchId !== options.batchId) {
95
- warnings.push(`batch-id mismatch: record=${record.batchId} vs --batch-id=${options.batchId}; using the record's value.`);
96
- }
97
- if (options.role !== undefined && record.role !== undefined && record.role !== options.role) {
98
- warnings.push(`role mismatch: record=${record.role} vs --role=${options.role}; using the record's value.`);
99
- }
100
- const nextActions = [
101
- 'This is a dry-run re-emit. Run the Task tool with the printed `toolCall.args` to actually dispatch the sub-agent.',
102
- `After dispatching, the sub-agent should call \`peaks sub-agent heartbeat --record ${options.dispatchRecord}\` periodically.`
103
- ];
104
- // Re-emit the same envelope shape as `peaks sub-agent dispatch`
105
- // so consumers (LLM or downstream tooling) can treat both CLIs
106
- // as the same surface. Slice 4.0.7-PR-meta-6: the envelope
107
- // version is bumped to 2.4.0 to mark the new `viaExec` field
108
- // (true here; false in dispatch CLI output).
109
- printResult(io, ok('sub-agent.exec', {
110
- envelopeVersion: '2.4.0',
111
- viaExec: true,
112
- sourceDispatchRecord: options.dispatchRecord,
113
- role: record.role ?? options.role,
114
- sessionId: record.sessionId ?? options.sessionId,
115
- rid: record.rid ?? options.requestId,
116
- batchId: record.batchId ?? options.batchId,
117
- promptSize: record.promptSize ?? 0,
118
- originalPromptSize: record.originalPromptSize ?? 0,
119
- toolCall: record.toolCall
120
- }, warnings, nextActions), options.json);
121
- });
122
- }
123
- // Helper export so tests can drive the underlying logic without
124
- // invoking the action handler. The action handler is the
125
- // integration path; the helper is the unit path.
126
- export function readDispatchRecordForExec(dispatchRecordPath) {
127
- if (!existsSync(dispatchRecordPath)) {
128
- throw new Error(`DISPATCH_RECORD_MISSING: ${dispatchRecordPath}`);
129
- }
130
- return JSON.parse(readFileSync(dispatchRecordPath, 'utf8'));
131
- }
132
- void dirname;
@@ -1,59 +0,0 @@
1
- import type { ProjectContext } from './project-context.js';
2
- export interface SkillRecommendation {
3
- /** GitHub owner/repo, e.g. "vercel-labs/agent-skills". */
4
- readonly owner: string;
5
- /** Skill slug within the repo, e.g. "react-best-practices". */
6
- readonly skillSlug: string;
7
- /** Display name (best-effort; may equal slug). */
8
- readonly displayName: string;
9
- /** Short description from the registry; empty when not parseable. */
10
- readonly description: string;
11
- /** Install count from the registry listing; null when unparseable. */
12
- readonly installCount: number | null;
13
- /** The shell command the user can paste to install this skill. */
14
- readonly installCommand: string;
15
- }
16
- export interface FindSkillsResult {
17
- readonly recommendations: readonly SkillRecommendation[];
18
- /** Queries that found zero candidates (operator-visible). */
19
- readonly emptyQueries: readonly string[];
20
- /** `npx skills find` runs that failed (network / registry); non-fatal. */
21
- readonly failedRuns: readonly {
22
- readonly query: string;
23
- readonly reason: string;
24
- }[];
25
- }
26
- /**
27
- * Map a ProjectContext to a small query set. Returns at most 8
28
- * queries to keep the network surface bounded; ordering is
29
- * best-to-worst (more specific first, then broad). Deliberately
30
- * no hardcoded owner whitelist — `findSkillsForContext` runs
31
- * `npx skills find <query>` with no `--owner` filter so the
32
- * registry / leaderboard surfaces whatever the user might want
33
- * (per find-skills SKILL.md Step 2-3 guidance).
34
- */
35
- export declare function queriesForContext(ctx: ProjectContext): readonly string[];
36
- /**
37
- * Spawn runner injection point. The default spawns
38
- * `npx skills find <query>` (no `--owner` filter, so the registry
39
- * decides what surfaces). Tests pass a deterministic stub.
40
- */
41
- export type SkillFindRunner = (query: string) => Promise<string>;
42
- export declare const defaultSkillFindRunner: SkillFindRunner;
43
- /**
44
- * Parse a `npx skills find` stdout blob into SkillRecommendation rows.
45
- * The CLI output is line-oriented; a row looks like
46
- * <owner>/<repo>@<slug> <install-count> <description>
47
- * The parser is lenient — any line that contains a
48
- * `<owner>/<repo>@<slug>` triple is captured.
49
- */
50
- export declare function parseFindOutput(stdout: string): readonly SkillRecommendation[];
51
- /**
52
- * Main entry: query the registry for each query, dedup, sort by
53
- * install-count desc (the registry's leaderboard already orders by
54
- * popularity; we keep the same ordering). Returns top-N rows.
55
- */
56
- export declare function findSkillsForContext(ctx: ProjectContext, options?: {
57
- readonly topN?: number;
58
- readonly findRunner?: SkillFindRunner;
59
- }): Promise<FindSkillsResult>;