peaks-loop 4.0.0-beta → 4.0.0-beta.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.
Files changed (179) hide show
  1. package/CHANGELOG.md +105 -87
  2. package/README-en.md +17 -4
  3. package/README.md +17 -4
  4. package/dist/src/cli/commands/asset-commands.d.ts +45 -0
  5. package/dist/src/cli/commands/asset-commands.js +404 -0
  6. package/dist/src/cli/commands/bee-commands.d.ts +26 -0
  7. package/dist/src/cli/commands/bee-commands.js +169 -0
  8. package/dist/src/cli/commands/{solo-commands.d.ts → code-commands.d.ts} +24 -24
  9. package/dist/src/cli/commands/{solo-commands.js → code-commands.js} +78 -78
  10. package/dist/src/cli/commands/core/skill-command.js +1 -1
  11. package/dist/src/cli/commands/dispatch-from-dag.js +1 -1
  12. package/dist/src/cli/commands/evolution-commands.d.ts +27 -0
  13. package/dist/src/cli/commands/evolution-commands.js +393 -0
  14. package/dist/src/cli/commands/hook-handle.js +7 -7
  15. package/dist/src/cli/commands/job-commands.js +2 -2
  16. package/dist/src/cli/commands/loop-commands.d.ts +19 -13
  17. package/dist/src/cli/commands/loop-commands.js +147 -13
  18. package/dist/src/cli/commands/sediment-commands.js +9 -0
  19. package/dist/src/cli/commands/session-auto-compact-hook-command.js +2 -2
  20. package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.d.ts +67 -0
  21. package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.js +173 -0
  22. package/dist/src/cli/commands/sub-agent-shared.d.ts +1 -1
  23. package/dist/src/cli/commands/user-touchpoint-commands.d.ts +4 -4
  24. package/dist/src/cli/commands/user-touchpoint-commands.js +14 -14
  25. package/dist/src/cli/commands/workflow-commands.js +22 -22
  26. package/dist/src/cli/program.js +23 -6
  27. package/dist/src/services/artifacts/repair-cycle-service.js +1 -1
  28. package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +1 -1
  29. package/dist/src/services/audit/enforcers/active-skill-resolver.js +1 -1
  30. package/dist/src/services/audit/enforcers/{solo-code-ban.d.ts → code-ban.d.ts} +6 -6
  31. package/dist/src/services/audit/enforcers/{solo-code-ban.js → code-ban.js} +6 -6
  32. package/dist/src/services/audit/red-line-catalog.d.ts +2 -2
  33. package/dist/src/services/audit/red-line-catalog.js +5 -5
  34. package/dist/src/services/audit/types.d.ts +2 -2
  35. package/dist/src/services/{solo → code}/auto-compact-orchestrator.d.ts +1 -1
  36. package/dist/src/services/{solo → code}/auto-compact-orchestrator.js +3 -3
  37. package/dist/src/services/{solo → code}/dag-orchestrator.d.ts +1 -1
  38. package/dist/src/services/{solo → code}/dag-orchestrator.js +1 -1
  39. package/dist/src/services/{solo → code}/emit-handoff.d.ts +1 -1
  40. package/dist/src/services/{solo → code}/emit-handoff.js +1 -1
  41. package/dist/src/services/{solo → code}/job-shape-decision.d.ts +1 -1
  42. package/dist/src/services/{solo → code}/job-shape-decision.js +5 -5
  43. package/dist/src/services/{solo → code}/mode-gate.d.ts +9 -9
  44. package/dist/src/services/{solo → code}/mode-gate.js +5 -5
  45. package/dist/src/services/{solo → code}/post-compact-detector.d.ts +3 -3
  46. package/dist/src/services/{solo → code}/post-compact-detector.js +4 -4
  47. package/dist/src/services/{solo → code}/step-08-gate.d.ts +4 -4
  48. package/dist/src/services/{solo → code}/step-08-gate.js +4 -4
  49. package/dist/src/services/{solo → code}/user-touchpoint-classifier.d.ts +1 -1
  50. package/dist/src/services/{solo → code}/user-touchpoint-classifier.js +1 -1
  51. package/dist/src/services/context/auto-compact-dispatcher.d.ts +2 -2
  52. package/dist/src/services/context/auto-compact-dispatcher.js +2 -2
  53. package/dist/src/services/context/auto-compact-types.d.ts +1 -1
  54. package/dist/src/services/crystallization/crystallization-service.d.ts +239 -0
  55. package/dist/src/services/crystallization/crystallization-service.js +397 -0
  56. package/dist/src/services/crystallization/crystallization-store.d.ts +75 -0
  57. package/dist/src/services/crystallization/crystallization-store.js +193 -0
  58. package/dist/src/services/crystallization/crystallization-types.d.ts +313 -0
  59. package/dist/src/services/crystallization/crystallization-types.js +299 -0
  60. package/dist/src/services/crystallization/evidence-brief-builder.d.ts +237 -0
  61. package/dist/src/services/crystallization/evidence-brief-builder.js +240 -0
  62. package/dist/src/services/dispatch/slice-dag.d.ts +2 -2
  63. package/dist/src/services/dispatch/test-tool-detection.d.ts +1 -1
  64. package/dist/src/services/dispatch/test-tool-detection.js +1 -1
  65. package/dist/src/services/evolution/evolution-service.d.ts +137 -0
  66. package/dist/src/services/evolution/evolution-service.js +281 -0
  67. package/dist/src/services/evolution/evolution-store.d.ts +57 -0
  68. package/dist/src/services/evolution/evolution-store.js +232 -0
  69. package/dist/src/services/evolution/evolution-types.d.ts +706 -0
  70. package/dist/src/services/evolution/evolution-types.js +286 -0
  71. package/dist/src/services/evolution/independent-evaluator-runner.d.ts +90 -0
  72. package/dist/src/services/evolution/independent-evaluator-runner.js +106 -0
  73. package/dist/src/services/evolution/regression-skeptic-runner.d.ts +64 -0
  74. package/dist/src/services/evolution/regression-skeptic-runner.js +113 -0
  75. package/dist/src/services/feedback/feedback-promotion-service.js +2 -2
  76. package/dist/src/services/job/job-progress-store.d.ts +1 -1
  77. package/dist/src/services/job/job-progress-store.js +1 -1
  78. package/dist/src/services/loop/loop-bee-relation-service.d.ts +107 -0
  79. package/dist/src/services/loop/loop-bee-relation-service.js +211 -0
  80. package/dist/src/services/loop/loop-bee-relation-store.d.ts +53 -0
  81. package/dist/src/services/loop/loop-bee-relation-store.js +130 -0
  82. package/dist/src/services/loop/loop-bee-relation-types.d.ts +118 -0
  83. package/dist/src/services/loop/loop-bee-relation-types.js +114 -0
  84. package/dist/src/services/loop/loop-release-service.d.ts +46 -0
  85. package/dist/src/services/loop/loop-release-service.js +66 -0
  86. package/dist/src/services/loop/loop-release-store.d.ts +55 -0
  87. package/dist/src/services/loop/loop-release-store.js +134 -0
  88. package/dist/src/services/loop/loop-release-types.d.ts +250 -0
  89. package/dist/src/services/loop/loop-release-types.js +162 -0
  90. package/dist/src/services/preferences/preferences-service.d.ts +1 -1
  91. package/dist/src/services/preferences/preferences-types.d.ts +1 -1
  92. package/dist/src/services/rd/standards-diagnostic.d.ts +1 -1
  93. package/dist/src/services/rd/standards-diagnostic.js +2 -2
  94. package/dist/src/services/recommendations/capability-seed-items.js +1 -1
  95. package/dist/src/services/recommendations/capability-seed-mappings.js +1 -1
  96. package/dist/src/services/recommendations/recommendation-service.js +1 -1
  97. package/dist/src/services/refactor/refactor-service.d.ts +1 -1
  98. package/dist/src/services/share/bundle-reader.d.ts +100 -0
  99. package/dist/src/services/share/bundle-reader.js +434 -0
  100. package/dist/src/services/share/bundle-types.d.ts +392 -0
  101. package/dist/src/services/share/bundle-types.js +318 -0
  102. package/dist/src/services/share/bundle-writer.d.ts +97 -0
  103. package/dist/src/services/share/bundle-writer.js +429 -0
  104. package/dist/src/services/share/run-state-contract.d.ts +133 -0
  105. package/dist/src/services/share/run-state-contract.js +138 -0
  106. package/dist/src/services/signal/cancel-handler.js +3 -3
  107. package/dist/src/services/skill/resume-detector.js +1 -1
  108. package/dist/src/services/skillhub/release-import.js +7 -2
  109. package/dist/src/services/skillhub/release-retain.js +6 -1
  110. package/dist/src/services/skillhub/sqlite-store.js +17 -1
  111. package/dist/src/services/skillhub/types.d.ts +10 -0
  112. package/dist/src/services/skills/skill-presence-service.d.ts +1 -1
  113. package/dist/src/services/standards/loop-engineering-lint.d.ts +38 -0
  114. package/dist/src/services/standards/loop-engineering-lint.js +101 -0
  115. package/dist/src/services/standards/loop-engineering-readiness-lint.d.ts +111 -0
  116. package/dist/src/services/standards/loop-engineering-readiness-lint.js +248 -0
  117. package/dist/src/services/upgrade/1x-detector-service.js +1 -1
  118. package/dist/src/services/workflow/pipeline-verify-service.d.ts +1 -1
  119. package/dist/src/services/workflow/pipeline-verify-service.js +3 -3
  120. package/dist/src/services/workflow/workflow-autonomous-service.d.ts +3 -3
  121. package/dist/src/services/workflow/workflow-autonomous-service.js +2 -2
  122. package/dist/src/services/workflow/workflow-router-service.d.ts +6 -6
  123. package/dist/src/services/workflow/workflow-router-service.js +33 -33
  124. package/dist/src/services/workspace/claude-settings-template.d.ts +1 -1
  125. package/dist/src/services/workspace/claude-settings-template.js +5 -5
  126. package/dist/src/shared/version.d.ts +1 -1
  127. package/dist/src/shared/version.js +1 -1
  128. package/examples/video-demo/README.md +57 -0
  129. package/examples/video-demo/out/en-closing-960.png +0 -0
  130. package/examples/video-demo/out/zh-closing-960.png +0 -0
  131. package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
  132. package/examples/video-demo/preview/peaks-loop-demo.mp4 +0 -0
  133. package/output-styles/peaks-skill-swarm.md +3 -3
  134. package/package.json +6 -2
  135. package/skills/bee/peaks-qa/SKILL.md +1 -1
  136. package/skills/bee/peaks-qa/references/artifact-per-request.md +3 -3
  137. package/skills/bee/peaks-qa/references/qa-sub-agent-dispatch.md +6 -6
  138. package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +1 -1
  139. package/skills/bee/peaks-rd/references/rd-runbook.md +1 -1
  140. package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +11 -11
  141. package/skills/bee/peaks-rd/references/rd-transition-gates.md +1 -1
  142. package/skills/bee/peaks-sc/SKILL.md +1 -1
  143. package/skills/bee/peaks-txt/SKILL.md +3 -3
  144. package/skills/bee/peaks-ui/SKILL.md +8 -8
  145. package/skills/peaks-code/SKILL.md +34 -21
  146. package/skills/peaks-code/references/anchoring-and-session-info.md +2 -2
  147. package/skills/peaks-code/references/artifact-contracts.md +1 -1
  148. package/skills/peaks-code/references/boundaries.md +3 -3
  149. package/skills/peaks-code/references/codegraph-orchestration.md +1 -1
  150. package/skills/peaks-code/references/dag-orchestrator.md +7 -7
  151. package/skills/peaks-code/references/external-references.md +2 -2
  152. package/skills/peaks-code/references/fanout-mandatory.md +3 -3
  153. package/skills/peaks-code/references/fast-mode.md +2 -2
  154. package/skills/peaks-code/references/frontend-only-mode.md +9 -9
  155. package/skills/peaks-code/references/job-loop.md +1 -1
  156. package/skills/peaks-code/references/local-artifact-workspace.md +2 -2
  157. package/skills/peaks-code/references/micro-cycle.md +14 -14
  158. package/skills/peaks-code/references/mode-selection-with-stale-presence.md +5 -5
  159. package/skills/peaks-code/references/mode-selection.md +3 -3
  160. package/skills/peaks-code/references/openspec-workflow.md +16 -16
  161. package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
  162. package/skills/peaks-code/references/quality-gate-cheatsheet.md +1 -1
  163. package/skills/peaks-code/references/refactor-mode.md +1 -1
  164. package/skills/peaks-code/references/runbook.md +15 -15
  165. package/skills/peaks-code/references/step-0-55-1x-detection.md +1 -1
  166. package/skills/peaks-code/references/step-0-8-gate.md +11 -11
  167. package/skills/peaks-code/references/sub-agent-dispatch.md +8 -8
  168. package/skills/peaks-code/references/swarm-dispatch-contract.md +17 -17
  169. package/skills/peaks-code/references/workflow-gates-and-types.md +2 -2
  170. package/skills/peaks-code/references/workflow.md +5 -5
  171. package/skills/peaks-content/SKILL.md +272 -0
  172. package/skills/peaks-issue-fix-orchestrator/SKILL.md +307 -0
  173. package/skills/peaks-resume/SKILL.md +2 -2
  174. package/skills/peaks-status/SKILL.md +2 -2
  175. package/skills/peaks-test/SKILL.md +2 -2
  176. /package/dist/src/services/{solo → code}/batch-heartbeat-poller.d.ts +0 -0
  177. /package/dist/src/services/{solo → code}/batch-heartbeat-poller.js +0 -0
  178. /package/dist/src/services/{solo → code}/status-line-renderer.d.ts +0 -0
  179. /package/dist/src/services/{solo → code}/status-line-renderer.js +0 -0
@@ -0,0 +1,393 @@
1
+ /**
2
+ * peaks evolution * CLI — M4 / spec §4.4 / §6 / §7.4
3
+ *
4
+ * Adds the Darwin-style ratchet CLI surface:
5
+ *
6
+ * peaks evolution propose --target <kind:id> --dimension <name> \
7
+ * --before-score <n> --after-score <n> [--delta-min <n>] \
8
+ * --author <id> [--brief-pointer <path>] [--project <root>] [--json]
9
+ * peaks evolution evaluate --proposal <id> \
10
+ * --evaluator <id> --skeptic <id> \
11
+ * --evaluator-score <n> [--refute-paragraph <text>] \
12
+ * [--risk-tag <tag>]... [--brief-pointer <path>] [--project <root>] [--json]
13
+ * peaks evolution revert --proposal <id> [--user-confirmation <ptr>] [--project <root>] [--json]
14
+ * peaks evolution status [--target <kind:id>] [--project <root>] [--json]
15
+ *
16
+ * Each verb prints a structured JSON envelope (`printResult` with
17
+ * `asJson=true`). The CLI is a thin shim around EvolutionService +
18
+ * the two runner modules. Author / evaluator / skeptic identities
19
+ * are LLM-supplied; the user is NEVER asked to type them.
20
+ *
21
+ * Defense in depth: the EvolutionService enforces the ratchet
22
+ * rules (AC-8 / AC-10 / AC-11); the CLI only translates flags
23
+ * into the service payload.
24
+ */
25
+ import { existsSync, mkdirSync } from "node:fs";
26
+ import { dirname, join } from "node:path";
27
+ import { openStateDb } from "../../services/skillhub/sqlite-store.js";
28
+ import { EvolutionService, EvolutionIntegrityError, } from "../../services/evolution/evolution-service.js";
29
+ import { EVOLUTION_TARGET_KINDS, } from "../../services/evolution/evolution-types.js";
30
+ import { findProjectRoot } from "../../services/config/config-safety.js";
31
+ import { runIndependentEvaluator, } from "../../services/evolution/independent-evaluator-runner.js";
32
+ import { addJsonOption, getErrorMessage, printResult } from "../cli-helpers.js";
33
+ import { fail, ok } from "../../shared/result.js";
34
+ /**
35
+ * Parse `--target <kind:id>`. Returns the kind + id, or `null` on
36
+ * failure. Validates `kind` against the EVOLUTION_TARGET_KINDS
37
+ * union.
38
+ */
39
+ function parseTargetFlag(raw) {
40
+ const idx = raw.indexOf(":");
41
+ if (idx <= 0 || idx === raw.length - 1)
42
+ return null;
43
+ const kind = raw.slice(0, idx);
44
+ const id = raw.slice(idx + 1);
45
+ if (!EVOLUTION_TARGET_KINDS.includes(kind))
46
+ return null;
47
+ return { kind: kind, id };
48
+ }
49
+ export function registerEvolutionCommands(program, io) {
50
+ // Reuse the existing `evolution` parent if one is registered;
51
+ // otherwise create it. The add-a-new-subcommand-check-for-existing-
52
+ // top-level-first rule requires this guard.
53
+ const existing = program.commands.find((c) => c.name() === "evolution");
54
+ const evolution = existing ?? program.command("evolution").description("Darwin-style ratchet: propose / evaluate / revert / status (spec §6)");
55
+ // peaks evolution propose
56
+ addJsonOption(evolution
57
+ .command("propose")
58
+ .description("M4: persist a new evolution proposal. Enforces AC-8 (single object + single dimension). The proposal's verdict starts at 'needs-user-decision' until the scoring + skeptic step lands.")
59
+ .requiredOption("--target <kind:id>", "target asset, e.g. 'loop:loop-onboarding-research' or 'bee:42'")
60
+ .requiredOption("--dimension <name>", "single optimization dimension (e.g. 'clarity')")
61
+ .requiredOption("--before-score <n>", "score on the 0..10 scale BEFORE the change")
62
+ .requiredOption("--after-score <n>", "score on the 0..10 scale AFTER the change (LLM-claimed)")
63
+ .option("--delta-min <n>", `minimum score delta for promotion (default: 1.0; spec §6.1 #5)`, "1.0")
64
+ .requiredOption("--author <id>", "author agent id (the LLM proposing the change)")
65
+ .option("--brief-pointer <ptr>", "pointer to the evidence brief used in the recommendation (spec §4.7 / §4.4)")
66
+ .option("--project <path>", "project root (default: cwd)")).action((options) => {
67
+ try {
68
+ const target = parseTargetFlag(options.target);
69
+ if (!target) {
70
+ printResult(io, fail("evolution.propose", "EVOLUTION_INVALID_TARGET", `--target must be '<kind>:<id>' where kind ∈ {${EVOLUTION_TARGET_KINDS.join(",")}}`, { target: options.target }, ["Pass --target loop:<loop-id> or --target bee:<bee-id>."]), options.json);
71
+ process.exitCode = 1;
72
+ return;
73
+ }
74
+ const before = Number(options.beforeScore);
75
+ const after = Number(options.afterScore);
76
+ const deltaMin = Number(options.deltaMin);
77
+ if (!Number.isFinite(before) || before < 0 || before > 10) {
78
+ printResult(io, fail("evolution.propose", "EVOLUTION_INVALID_BEFORE_SCORE", `before_score must be a finite number in [0, 10] (got "${options.beforeScore}")`, { beforeScore: options.beforeScore }, ["Pass a numeric --before-score in [0, 10]."]), options.json);
79
+ process.exitCode = 1;
80
+ return;
81
+ }
82
+ if (!Number.isFinite(after) || after < 0 || after > 10) {
83
+ printResult(io, fail("evolution.propose", "EVOLUTION_INVALID_AFTER_SCORE", `after_score must be a finite number in [0, 10] (got "${options.afterScore}")`, { afterScore: options.afterScore }, ["Pass a numeric --after-score in [0, 10]."]), options.json);
84
+ process.exitCode = 1;
85
+ return;
86
+ }
87
+ if (!Number.isFinite(deltaMin) || deltaMin < 0) {
88
+ printResult(io, fail("evolution.propose", "EVOLUTION_INVALID_DELTA_MIN", `delta_min must be a finite number >= 0 (got "${options.deltaMin}")`, { deltaMin: options.deltaMin }, ["Pass a numeric --delta-min >= 0 (default 1.0)."]), options.json);
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
93
+ if (!existsSync(join(projectRoot, ".peaks"))) {
94
+ // The CLI is allowed to bootstrap an empty .peaks dir so
95
+ // tests do not need a full peaks project.
96
+ mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
97
+ }
98
+ const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
99
+ try {
100
+ const svc = new EvolutionService(db);
101
+ const input = {
102
+ target_kind: target.kind,
103
+ target_release_id: target.id,
104
+ optimization_dimension: options.dimension,
105
+ before_snapshot: {},
106
+ after_snapshot: { after_score: after },
107
+ diff: {},
108
+ before_score: before,
109
+ after_score: after,
110
+ score_delta_min: deltaMin,
111
+ author_id: options.author,
112
+ single_object: true,
113
+ single_optimization_dimension: true,
114
+ rubric: {},
115
+ red_lines: [],
116
+ source_traces: [],
117
+ };
118
+ const proposal = svc.createProposal(input);
119
+ printResult(io, ok("evolution.propose", {
120
+ proposal: {
121
+ id: proposal.id,
122
+ target_kind: proposal.target_kind,
123
+ target_release_id: proposal.target_release_id,
124
+ optimization_dimension: proposal.optimization_dimension,
125
+ before_score: proposal.before_score,
126
+ after_score: proposal.after_score,
127
+ score_delta: proposal.score_delta,
128
+ score_delta_min: proposal.score_delta_min,
129
+ author_id: proposal.author_id,
130
+ verdict: "needs-user-decision",
131
+ ...(options.briefPointer !== undefined ? { brief_pointer: options.briefPointer } : {}),
132
+ },
133
+ nextActions: [
134
+ `Run \`peaks evolution evaluate --proposal ${proposal.id} --evaluator <id> --skeptic <id> --evaluator-score <n> --json\` to score the proposal.`,
135
+ ],
136
+ }, [], []), options.json);
137
+ }
138
+ finally {
139
+ db.close();
140
+ }
141
+ }
142
+ catch (err) {
143
+ if (err instanceof EvolutionIntegrityError) {
144
+ printResult(io, fail("evolution.propose", err.code, err.message, { findings: err.findings }, [
145
+ "Re-shape the proposal per spec §6: single object, single dimension, separate scorer.",
146
+ "If you intended a multi-object change, split it into multiple rounds.",
147
+ ]), options.json);
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ printResult(io, fail("evolution.propose", "EVOLUTION_PROPOSE_FAILED", getErrorMessage(err), {}, [
152
+ "Verify the target flag and dimension."
153
+ ]), options.json);
154
+ process.exitCode = 1;
155
+ }
156
+ });
157
+ // peaks evolution evaluate
158
+ addJsonOption(evolution
159
+ .command("evaluate")
160
+ .description("M4: score a proposal with an independent evaluator (AC-12/AC-13) and a regression skeptic (AC-14). The result is the FINAL verdict: 'keep' (after user confirmation), 'revert' (skeptic blocker or score delta below threshold), or 'needs-user-decision'.")
161
+ .requiredOption("--proposal <id>", "proposal id returned by `peaks evolution propose`")
162
+ .requiredOption("--evaluator <id>", "independent scorer id (MUST differ from --author; AC-10)")
163
+ .requiredOption("--skeptic <id>", "regression skeptic id (MUST differ from --evaluator and --author; AC-12/AC-14)")
164
+ .requiredOption("--evaluator-score <n>", "the independent scorer's score on the 0..10 scale (LLM-derived; NOT the author's after_score)")
165
+ .option("--refute-paragraph <text>", "the independent scorer's one-paragraph refute (LLM-authored)", "")
166
+ .option("--risk-tag <tag>", "add a risk tag emitted by the evaluator (repeatable)", collectRepeatable, [])
167
+ .option("--brief-pointer <ptr>", "pointer to the evidence brief (spec §4.7 / §4.4)")
168
+ .option("--project <path>", "project root (default: cwd)")).action(async (options) => {
169
+ try {
170
+ const score = Number(options.evaluatorScore);
171
+ if (!Number.isFinite(score) || score < 0 || score > 10) {
172
+ printResult(io, fail("evolution.evaluate", "EVOLUTION_INVALID_EVALUATOR_SCORE", `evaluator-score must be a finite number in [0, 10] (got "${options.evaluatorScore}")`, { evaluatorScore: options.evaluatorScore }, ["Pass a numeric --evaluator-score in [0, 10]."]), options.json);
173
+ process.exitCode = 1;
174
+ return;
175
+ }
176
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
177
+ if (!existsSync(join(projectRoot, ".peaks"))) {
178
+ mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
179
+ }
180
+ const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
181
+ try {
182
+ const svc = new EvolutionService(db);
183
+ const proposal = svc.read(options.proposal);
184
+ if (!proposal) {
185
+ printResult(io, fail("evolution.evaluate", "EVOLUTION_PROPOSAL_NOT_FOUND", `proposal '${options.proposal}' not found`, { proposalId: options.proposal }, ["Verify the proposal id and that you have run `peaks evolution propose` first."]), options.json);
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ // The CLI runs the deterministic LLM stubs (no live LLM in
190
+ // M4). Real LLM wiring lands in M5.
191
+ const evaluatorResult = await runIndependentEvaluator(proposal.proposal);
192
+ // Force the CLI-supplied --evaluator-score to be the
193
+ // final score; the LLM stub's score is informational.
194
+ evaluatorResult.score = score;
195
+ if (options.refuteParagraph && options.refuteParagraph.length > 0) {
196
+ evaluatorResult.refuteParagraph = options.refuteParagraph;
197
+ }
198
+ if (Array.isArray(options.riskTag) && options.riskTag.length > 0) {
199
+ evaluatorResult.riskTags = [
200
+ ...evaluatorResult.riskTags,
201
+ ...options.riskTag,
202
+ ];
203
+ }
204
+ const evaluation = svc.score(options.proposal, {
205
+ evaluator_id: options.evaluator,
206
+ skeptic_id: options.skeptic,
207
+ evaluator_result: evaluatorResult,
208
+ skeptic_result: {
209
+ driftRisks: [],
210
+ overfitRisks: [],
211
+ safetyRegressionRisks: [],
212
+ },
213
+ ...(options.briefPointer !== undefined ? { brief_pointer: options.briefPointer } : {}),
214
+ });
215
+ printResult(io, ok("evolution.evaluate", {
216
+ proposalId: evaluation.id,
217
+ verdict: evaluation.verdict,
218
+ evaluator_id: evaluation.evaluator_id,
219
+ skeptic_id: evaluation.skeptic_id,
220
+ evaluator_result: evaluation.evaluator_result,
221
+ skeptic_result: evaluation.skeptic_result,
222
+ score_delta: evaluation.proposal.score_delta,
223
+ score_delta_min: evaluation.proposal.score_delta_min,
224
+ after_score: evaluation.proposal.after_score,
225
+ nextActions: evaluation.verdict === "needs-user-decision"
226
+ ? [
227
+ `User confirmation required. Run \`peaks evolution mark-keep --proposal ${evaluation.id} --user-confirmation <ptr> --json\` after the user picks "keep", OR \`peaks evolution revert --proposal ${evaluation.id} --json\`.`,
228
+ ]
229
+ : evaluation.verdict === "revert"
230
+ ? [
231
+ "Proposal reverted. Investigate the skeptic blocker or the score delta below threshold.",
232
+ ]
233
+ : [
234
+ "Proposal kept. The target asset remains unchanged on disk; full promotion lands in a later slice.",
235
+ ],
236
+ }, [], []), options.json);
237
+ if (evaluation.verdict === "revert")
238
+ process.exitCode = 1;
239
+ }
240
+ finally {
241
+ db.close();
242
+ }
243
+ }
244
+ catch (err) {
245
+ if (err instanceof EvolutionIntegrityError) {
246
+ printResult(io, fail("evolution.evaluate", err.code, err.message, { findings: err.findings }, [
247
+ "Re-shape per spec §6: evaluator and skeptic MUST be separate from the author; the score delta MUST be >= score_delta_min for keep.",
248
+ ]), options.json);
249
+ process.exitCode = 1;
250
+ return;
251
+ }
252
+ printResult(io, fail("evolution.evaluate", "EVOLUTION_EVALUATE_FAILED", getErrorMessage(err), { proposalId: options.proposal }, [
253
+ "Verify the proposal id and the evaluator / skeptic ids.",
254
+ ]), options.json);
255
+ process.exitCode = 1;
256
+ }
257
+ });
258
+ // peaks evolution mark-keep — explicit user confirmation.
259
+ addJsonOption(evolution
260
+ .command("mark-keep")
261
+ .description("M4: explicit user-confirmed `keep` verdict. Requires --user-confirmation <ptr> and a score_delta >= score_delta_min (AC-11/AC-15).")
262
+ .requiredOption("--proposal <id>", "proposal id")
263
+ .option("--user-confirmation <ptr>", "pointer to the user choice record (AC-15) — omit to surface the EVOLUTION_MISSING_USER_CONFIRMATION service-layer error")
264
+ .option("--project <path>", "project root (default: cwd)")).action((options) => {
265
+ try {
266
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
267
+ if (!existsSync(join(projectRoot, ".peaks"))) {
268
+ mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
269
+ }
270
+ const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
271
+ try {
272
+ const svc = new EvolutionService(db);
273
+ const updated = svc.markVerdict(options.proposal, "keep", options.userConfirmation);
274
+ if (!updated) {
275
+ printResult(io, fail("evolution.mark-keep", "EVOLUTION_PROPOSAL_NOT_FOUND", `proposal '${options.proposal}' not found`, { proposalId: options.proposal }, ["Verify the proposal id."]), options.json);
276
+ process.exitCode = 1;
277
+ return;
278
+ }
279
+ printResult(io, ok("evolution.mark-keep", {
280
+ proposalId: updated.id,
281
+ verdict: updated.verdict,
282
+ user_confirmation_pointer: updated.user_confirmation_pointer ?? null,
283
+ score_delta: updated.proposal.score_delta,
284
+ score_delta_min: updated.proposal.score_delta_min,
285
+ }, [], []), options.json);
286
+ }
287
+ finally {
288
+ db.close();
289
+ }
290
+ }
291
+ catch (err) {
292
+ if (err instanceof EvolutionIntegrityError) {
293
+ printResult(io, fail("evolution.mark-keep", err.code, err.message, { findings: err.findings }, [
294
+ "Re-shape the proposal or the score so score_delta >= score_delta_min.",
295
+ ]), options.json);
296
+ process.exitCode = 1;
297
+ return;
298
+ }
299
+ printResult(io, fail("evolution.mark-keep", "EVOLUTION_MARK_KEEP_FAILED", getErrorMessage(err), { proposalId: options.proposal }, [
300
+ "Verify the proposal id.",
301
+ ]), options.json);
302
+ process.exitCode = 1;
303
+ }
304
+ });
305
+ // peaks evolution revert
306
+ addJsonOption(evolution
307
+ .command("revert")
308
+ .description("M4: revert a proposal (universal recovery; spec §6.1 #8). Always allowed; the user may supply --user-confirmation for audit.")
309
+ .requiredOption("--proposal <id>", "proposal id")
310
+ .option("--user-confirmation <ptr>", "optional pointer to the user choice record")
311
+ .option("--project <path>", "project root (default: cwd)")).action((options) => {
312
+ try {
313
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
314
+ if (!existsSync(join(projectRoot, ".peaks"))) {
315
+ mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
316
+ }
317
+ const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
318
+ try {
319
+ const svc = new EvolutionService(db);
320
+ const updated = svc.revert(options.proposal, options.userConfirmation);
321
+ if (!updated) {
322
+ printResult(io, fail("evolution.revert", "EVOLUTION_PROPOSAL_NOT_FOUND", `proposal '${options.proposal}' not found`, { proposalId: options.proposal }, ["Verify the proposal id."]), options.json);
323
+ process.exitCode = 1;
324
+ return;
325
+ }
326
+ printResult(io, ok("evolution.revert", {
327
+ proposalId: updated.id,
328
+ verdict: updated.verdict,
329
+ user_confirmation_pointer: updated.user_confirmation_pointer ?? null,
330
+ }, [], []), options.json);
331
+ }
332
+ finally {
333
+ db.close();
334
+ }
335
+ }
336
+ catch (err) {
337
+ printResult(io, fail("evolution.revert", "EVOLUTION_REVERT_FAILED", getErrorMessage(err), { proposalId: options.proposal }, [
338
+ "Verify the proposal id.",
339
+ ]), options.json);
340
+ process.exitCode = 1;
341
+ }
342
+ });
343
+ // peaks evolution status
344
+ addJsonOption(evolution
345
+ .command("status")
346
+ .description("M4: read the status snapshot for a target (counts of evaluations by verdict + latest evaluation id).")
347
+ .requiredOption("--target <kind:id>", "target asset, e.g. 'loop:loop-onboarding-research'")
348
+ .option("--project <path>", "project root (default: cwd)")).action((options) => {
349
+ try {
350
+ const target = parseTargetFlag(options.target);
351
+ if (!target) {
352
+ printResult(io, fail("evolution.status", "EVOLUTION_INVALID_TARGET", `--target must be '<kind>:<id>' where kind ∈ {${EVOLUTION_TARGET_KINDS.join(",")}}`, { target: options.target }, ["Pass --target loop:<loop-id> or --target bee:<bee-id>."]), options.json);
353
+ process.exitCode = 1;
354
+ return;
355
+ }
356
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
357
+ if (!existsSync(join(projectRoot, ".peaks"))) {
358
+ mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
359
+ }
360
+ const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
361
+ try {
362
+ const svc = new EvolutionService(db);
363
+ const status = svc.status({
364
+ target_kind: target.kind,
365
+ target_release_id: target.id,
366
+ });
367
+ printResult(io, ok("evolution.status", status, [], []), options.json);
368
+ }
369
+ finally {
370
+ db.close();
371
+ }
372
+ }
373
+ catch (err) {
374
+ printResult(io, fail("evolution.status", "EVOLUTION_STATUS_FAILED", getErrorMessage(err), { target: options.target }, [
375
+ "Verify the target flag.",
376
+ ]), options.json);
377
+ process.exitCode = 1;
378
+ }
379
+ });
380
+ // Suppress unused-var warnings for the dirname import; it is
381
+ // reserved for the M5 path-pointer materialization.
382
+ void dirname;
383
+ }
384
+ /**
385
+ * Helper: collect repeatable `--risk-tag` values into an array.
386
+ * Commander's `collect` is not always available; we read
387
+ * `previous` from the default value.
388
+ */
389
+ function collectRepeatable(value, previous) {
390
+ if (Array.isArray(previous))
391
+ return [...previous, value];
392
+ return [value];
393
+ }
@@ -2,7 +2,7 @@ import { addJsonOption, printResult } from '../cli-helpers.js';
2
2
  import { detectIdeFromContext, parseAdapterStdin, parseClaudeShapeStdin, pluckObject, pluckString } from '../../services/ide/hook-translator.js';
3
3
  import { buildCanonicalHook, formatDecisionResponse } from '../../services/ide/hook-protocol.js';
4
4
  import { getAdapter } from '../../services/ide/ide-registry.js';
5
- import { evaluateSoloCodeBan } from '../../services/audit/enforcers/solo-code-ban.js';
5
+ import { evaluateCodeBan } from '../../services/audit/enforcers/code-ban.js';
6
6
  import { isRootWrite } from '../../services/audit/enforcers/no-root-pollution.js';
7
7
  import { checkLoginGate } from '../../services/audit/enforcers/login-gate.js';
8
8
  import { resolveActiveSkillForCaller } from '../../services/audit/enforcers/active-skill-resolver.js';
@@ -82,17 +82,17 @@ export function registerHookHandleCommand(program, io) {
82
82
  // Dispatch by toolName. For slice #1+, we only handle Bash. Task tool sub-agent dispatch goes through `peaks sub-agent dispatch` (slice #009) and does not need a hook entry.
83
83
  // Other tools: allow (no-op; future events will be added here).
84
84
  if (hook.toolName === 'Bash' && typeof fallbackCommand === 'string' && fallbackCommand.trim().length > 0) {
85
- // L2.1 P0 #1: solo-code-ban. Deny `git commit` / `git apply` from peaks-* skills
85
+ // L2.1 P0 #1: code-commit-ban. Deny `git commit` / `git apply` from peaks-* skills
86
86
  // BEFORE the SOP gate runs. The active skill is read from the per-caller
87
87
  // active-skill file (see active-skill-resolver.ts).
88
88
  const activeSkill = resolveActiveSkillForCaller(projectRoot);
89
89
  if (activeSkill.skill !== null) {
90
- const soloDecision = evaluateSoloCodeBan({ skill: activeSkill.skill, command: fallbackCommand });
91
- if (soloDecision.denied) {
92
- const formatted = formatDecisionResponse(ide, 'deny', soloDecision.reason);
90
+ const codeDecision = evaluateCodeBan({ skill: activeSkill.skill, command: fallbackCommand });
91
+ if (codeDecision.denied) {
92
+ const formatted = formatDecisionResponse(ide, 'deny', codeDecision.reason);
93
93
  emitDecision(io, formatted.stdout);
94
94
  if (options.json === true) {
95
- emitHint(io, JSON.stringify(ok('hook.handle', { ide, tool: hook.toolName, decision: 'deny', reason: soloDecision.reason, enforcer: 'solo-code-ban' })));
95
+ emitHint(io, JSON.stringify(ok('hook.handle', { ide, tool: hook.toolName, decision: 'deny', reason: codeDecision.reason, enforcer: 'code-ban' })));
96
96
  }
97
97
  return;
98
98
  }
@@ -109,7 +109,7 @@ export function registerHookHandleCommand(program, io) {
109
109
  return;
110
110
  }
111
111
  }
112
- // L2.2 P1 #1: login-gate. After solo-code-ban + gate-enforce pass,
112
+ // L2.2 P1 #1: login-gate. After code-commit-ban + gate-enforce pass,
113
113
  // flag destructive patterns (uninstall, force-push, --force, --hard,
114
114
  // rm -rf) so the LLM gets a soft warning (still allow). The user
115
115
  // gets the warning via the warn channel; the command proceeds.
@@ -174,7 +174,7 @@ export function registerJobCommands(program, io = { stdout: (t) => process.stdou
174
174
  await orch.checkpointDone({ jobId: parsed.data.jobId, sliceId: parsed.data.sliceId, ...(parsed.data.commitSha ? { commitSha: parsed.data.commitSha } : {}) });
175
175
  // v3.1.2: after each --state done, mirror slice progress to
176
176
  // .peaks/_runtime/<sessionId>/job/<jid>/progress.json so the
177
- // next LLM turn (or peaks solo gate-step-08 hook) can read it.
177
+ // next LLM turn (or peaks code gate-step-08 hook) can read it.
178
178
  const project = projectRoot(opts);
179
179
  const sessId = jobRoot.sessionId;
180
180
  const state = orch.status(parsed.data.jobId);
@@ -238,7 +238,7 @@ export function registerJobCommands(program, io = { stdout: (t) => process.stdou
238
238
  });
239
239
  addJsonOption(job.commands.find(c => c.name() === 'resume'));
240
240
  // v3.1.2: read the on-disk slice progress mirror written by `peaks
241
- // job checkpoint --state done`. Used by peaks solo gate-step-08 and
241
+ // job checkpoint --state done`. Used by peaks code gate-step-08 and
242
242
  // by peaks-code Step 0.7 (resume) to surface `Next: slice #N of M
243
243
  // (<currentSlice>)` without re-deriving from state.json.
244
244
  job
@@ -1,20 +1,26 @@
1
1
  /**
2
- * peaks loop * CLI (Slice #14) — L4 Agent Loop Integration.
2
+ * peaks loop * CLI (Slice #14 + M7 add).
3
3
  *
4
- * Per docs/superpowers/specs/2026-06-11-peaks-loop-l1-l2-l3-redesign.md §5.4
5
- * Slice #14, ships 5 sub-features:
4
+ * Slices:
5
+ * #14 sub-features (L4 Agent Loop integration): distill / preflight /
6
+ * detect-pattern / check-consistency / peaks goal compose.
6
7
  *
7
- * 14.1 peaks loop distill — extract patterns from past sessions
8
- * (delegates to peaks memory extract)
9
- * 14.2 peaks loop preflight — pre-run sanity checks (placeholder)
10
- * 14.3 peaks loop detect-pattern — find repeating patterns (placeholder)
11
- * 14.4 peaks loop check-consistency — verify state consistency (placeholder)
12
- * 14.5 peaks goal compose — autonomous goal composition (placeholder;
13
- * requires IDE adapter goalCommand capability
14
- * per Slice #0.7)
8
+ * M7 (2026-07-07 spec §7A.2):
9
+ * peaks loop export --loop <id> --out <path.tar.gz>
10
+ * peaks loop import --in <path.tar.gz> [--as <loop-id>]
15
11
  *
16
- * The 4 placeholders emit a clear nextActions list; the LLM-side UX
17
- * layer composes the actual runtime today.
12
+ * `peaks loop export` reads the source loop_release + relations +
13
+ * related bees + evidence briefs and emits a `peaks.bundle/1`
14
+ * tar.gz. Hard-blocks when the source has `shareable=false`.
15
+ *
16
+ * `peaks loop import` extracts a bundle, validates
17
+ * format_version_major=1, and lands the release as `candidate`
18
+ * (mandatory landing status per spec §7A.2). The receiver must run
19
+ * an independent evaluation before promoting to `stable`.
20
+ *
21
+ * The existing 14.x commands are kept under the same `loop` parent;
22
+ * M7 only ADDs export / import — it does not modify the existing
23
+ * surface.
18
24
  */
19
25
  import { Command } from 'commander';
20
26
  import { type ProgramIO } from '../cli-helpers.js';