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,434 @@
1
+ /**
2
+ * bundle-reader.ts — readBundle(inPath) (spec §7A.2).
3
+ *
4
+ * M7 / spec §7A.2 / §10 RL-9 / acceptance AC-25 / AC-26.
5
+ *
6
+ * Reads a `peaks.bundle/1` tar.gz and lands the contained release
7
+ * on the local SkillHub. The reader is the symmetrical counterpart
8
+ * of `bundle-writer.ts` — same layout, same content-addressed
9
+ * blobs/, but with three hard guards:
10
+ *
11
+ * 1. `format_version_major !== 1` is a HARD block. The reader
12
+ * throws `SHARE_BUNDLE_MAJOR_VERSION_MISMATCH` before any
13
+ * SQL side-effect. (Major-version bumps are reserved for
14
+ * breaking schema changes; the receiver is not assumed to
15
+ * know how to translate.)
16
+ * 2. `format_version_minor` mismatch is a non-fatal warn (the
17
+ * reader collects it as a warning on the return envelope).
18
+ * 3. The imported release ALWAYS lands as `candidate`. The
19
+ * reader refuses to write any other status; the receiver
20
+ * MUST run an independent evaluation before promoting the
21
+ * imported release to `stable` (AC-26 — peaks loop promote
22
+ * reads evolution_evaluation rows).
23
+ *
24
+ * The reader is also the integration point for AC-26 — the loop
25
+ * promote path (which lives in a future slice / CLI command) checks
26
+ * `evolution_evaluation.independent_scorer_verdict` before allowing
27
+ * a candidate → stable transition; this slice does not add a new
28
+ * `peaks loop promote` because `peaks asset crystallize` already
29
+ * enforces the brief-section + pre-run gates and creates the
30
+ * initial `candidate` row. The integration test for AC-26 verifies
31
+ * that a freshly imported bundle has no `evolution_evaluation`
32
+ * row and that promotion therefore cannot proceed.
33
+ */
34
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, rmSync, } from "node:fs";
35
+ import { join } from "node:path";
36
+ import { ZodError } from "zod";
37
+ import { runTar } from "../skillhub/tar-runtime.js";
38
+ import { PEAKS_BUNDLE_FORMAT_VERSION_MAJOR, PEAKS_BUNDLE_SCHEMA_VERSIONS, SHARE_BUNDLE_ERROR_CODES, BundleManifestSchema, } from "./bundle-types.js";
39
+ /* ---------------------------------------------------------------------- */
40
+ /* Reader errors */
41
+ /* ---------------------------------------------------------------------- */
42
+ /**
43
+ * Thrown when `format_version_major !== 1`. This is the explicit
44
+ * HARD block per spec §7A.2 (major mismatch).
45
+ */
46
+ export class BundleMajorVersionMismatchError extends Error {
47
+ code;
48
+ receivedMajor;
49
+ constructor(receivedMajor) {
50
+ super(`bundle declares format_version_major=${receivedMajor}; the only supported major is ${PEAKS_BUNDLE_FORMAT_VERSION_MAJOR} (spec §7A.2)`);
51
+ this.name = "BundleMajorVersionMismatchError";
52
+ this.code = SHARE_BUNDLE_ERROR_CODES.MAJOR_VERSION_MISMATCH;
53
+ this.receivedMajor = receivedMajor;
54
+ }
55
+ }
56
+ /**
57
+ * Thrown when the schema-versions mapping is missing or carries
58
+ * non-canonical literals.
59
+ */
60
+ export class BundleSchemaVersionsMismatchError extends Error {
61
+ code;
62
+ received;
63
+ constructor(received) {
64
+ super(`bundle schema_versions mapping is missing or carries non-canonical literals (spec §7A.2). expected=${JSON.stringify(PEAKS_BUNDLE_SCHEMA_VERSIONS)} received=${JSON.stringify(received)}`);
65
+ this.name = "BundleSchemaVersionsMismatchError";
66
+ this.code = SHARE_BUNDLE_ERROR_CODES.SCHEMA_VERSIONS_MISMATCH;
67
+ this.received = received;
68
+ }
69
+ }
70
+ /**
71
+ * Thrown when the caller asks to land as anything other than
72
+ * `candidate` (the only allowed target status).
73
+ */
74
+ export class BundleImportToStableForbiddenError extends Error {
75
+ code;
76
+ constructor(receivedStatus) {
77
+ super(`bundles cannot land as '${receivedStatus}'; the only allowed target is 'candidate' (spec §7A.2). promotion to stable requires an evolution_evaluation row with an independent_scorer_verdict`);
78
+ this.name = "BundleImportToStableForbiddenError";
79
+ this.code = SHARE_BUNDLE_ERROR_CODES.IMPORT_TO_STABLE_FORBIDDEN;
80
+ }
81
+ }
82
+ /**
83
+ * Thrown when the bundle tarball is malformed (no manifest.json,
84
+ * bad JSON, etc.). Distinct from a ZodError so the CLI can map
85
+ * to a single BUNDLE_MALFORMED code.
86
+ */
87
+ export class BundleMalformedError extends Error {
88
+ code;
89
+ constructor(message) {
90
+ super(`bundle is malformed: ${message}`);
91
+ this.name = "BundleMalformedError";
92
+ this.code = SHARE_BUNDLE_ERROR_CODES.BUNDLE_MALFORMED;
93
+ }
94
+ }
95
+ /* ---------------------------------------------------------------------- */
96
+ /* Main entrypoint */
97
+ /* ---------------------------------------------------------------------- */
98
+ export function readBundle(args) {
99
+ const { db, blobsDir, inPath } = args;
100
+ if (!existsSync(inPath)) {
101
+ throw new BundleMalformedError(`bundle file not found at '${inPath}'`);
102
+ }
103
+ const stageDir = inPath + ".extract";
104
+ if (existsSync(stageDir))
105
+ rmSync(stageDir, { recursive: true, force: true });
106
+ mkdirSync(stageDir, { recursive: true });
107
+ let extractOk = false;
108
+ try {
109
+ runTar(["-xzf", inPath, "-C", stageDir]);
110
+ extractOk = true;
111
+ }
112
+ finally {
113
+ if (!extractOk)
114
+ rmSync(stageDir, { recursive: true, force: true });
115
+ }
116
+ try {
117
+ const manifestPath = join(stageDir, "manifest.json");
118
+ if (!existsSync(manifestPath)) {
119
+ throw new BundleMalformedError("bundle is missing manifest.json (writer is required to emit it)");
120
+ }
121
+ let rawManifest;
122
+ try {
123
+ rawManifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
124
+ }
125
+ catch (err) {
126
+ throw new BundleMalformedError(`manifest.json could not be parsed: ${err instanceof Error ? err.message : String(err)}`);
127
+ }
128
+ const manifest = parseManifest(rawManifest);
129
+ // Re-materialise content-addressed blobs regardless of kind —
130
+ // the layout (`blobs/<sha256>`) is identical for both.
131
+ const blobsStageDir = join(stageDir, "blobs");
132
+ if (existsSync(blobsStageDir)) {
133
+ mkdirSync(blobsDir, { recursive: true });
134
+ const blobsOut = blobsDir;
135
+ for (const fname of readDirEntries(blobsStageDir)) {
136
+ const src = join(blobsStageDir, fname);
137
+ if (!fname.match(/^[0-9a-f]{64}$/))
138
+ continue;
139
+ const destDir = join(blobsOut, fname.slice(0, 2));
140
+ mkdirSync(destDir, { recursive: true });
141
+ const destPath = join(destDir, fname);
142
+ if (!existsSync(destPath)) {
143
+ writeFileSync(destPath, readFileSync(src));
144
+ }
145
+ }
146
+ }
147
+ if (manifest.kind === "loop") {
148
+ const assetId = importLoopBundle(db, manifest, args.asName);
149
+ return {
150
+ assetId,
151
+ kind: "loop",
152
+ importedAs: "candidate",
153
+ warnings: manifest.format_version_minor === 0 ? [] : [
154
+ `minor-version=${manifest.format_version_minor}; supported but flagging for awareness`,
155
+ ],
156
+ evidenceBriefCount: manifest.evidence_briefs.length,
157
+ };
158
+ }
159
+ const beeId = importBeeBundle(db, manifest, blobsDir, args.asName);
160
+ return {
161
+ assetId: beeId,
162
+ kind: "bee",
163
+ importedAs: "candidate",
164
+ warnings: manifest.format_version_minor === 0 ? [] : [
165
+ `minor-version=${manifest.format_version_minor}; supported but flagging for awareness`,
166
+ ],
167
+ evidenceBriefCount: manifest.evidence_briefs.length,
168
+ };
169
+ }
170
+ finally {
171
+ if (existsSync(stageDir))
172
+ rmSync(stageDir, { recursive: true, force: true });
173
+ }
174
+ }
175
+ /* ---------------------------------------------------------------------- */
176
+ /* Manifest parsing — enforces the major-version hard block */
177
+ /* ---------------------------------------------------------------------- */
178
+ function parseManifest(raw) {
179
+ if (!raw ||
180
+ typeof raw !== "object" ||
181
+ Array.isArray(raw)) {
182
+ throw new BundleMalformedError("manifest must be a JSON object");
183
+ }
184
+ // Defense in depth: even if Zod would accept, we layer an early
185
+ // major-mismatch guard so the CLI can surface a specific error
186
+ // code (SHARE_BUNDLE_MAJOR_VERSION_MISMATCH) before the Zod
187
+ // schema-version mapping fatal-error path fires.
188
+ const candidate = raw;
189
+ if (typeof candidate.format_version_major === "number" &&
190
+ candidate.format_version_major !== PEAKS_BUNDLE_FORMAT_VERSION_MAJOR) {
191
+ throw new BundleMajorVersionMismatchError(candidate.format_version_major);
192
+ }
193
+ if (typeof candidate.format_constant !== "string") {
194
+ throw new BundleMalformedError("manifest is missing format_constant");
195
+ }
196
+ if (candidate.format_constant !== "peaks.bundle/1") {
197
+ throw new BundleMalformedError(`format_constant must be "peaks.bundle/1"; received '${candidate.format_constant}'`);
198
+ }
199
+ if (!("schema_versions" in candidate) ||
200
+ typeof candidate.schema_versions !== "object" ||
201
+ candidate.schema_versions === null ||
202
+ Array.isArray(candidate.schema_versions)) {
203
+ throw new BundleSchemaVersionsMismatchError(undefined);
204
+ }
205
+ try {
206
+ return BundleManifestSchema.parse(raw);
207
+ }
208
+ catch (err) {
209
+ if (err instanceof ZodError) {
210
+ const schemaIssue = err.issues.find((i) => i.path[0] === "schema_versions");
211
+ if (schemaIssue) {
212
+ throw new BundleSchemaVersionsMismatchError(candidate.schema_versions);
213
+ }
214
+ throw new BundleMalformedError(`manifest failed schema validation: ${err.issues
215
+ .map((i) => `${i.path.join(".")}:${i.message}`)
216
+ .join("; ")}`);
217
+ }
218
+ throw err;
219
+ }
220
+ }
221
+ /* ---------------------------------------------------------------------- */
222
+ /* Loop import */
223
+ /* ---------------------------------------------------------------------- */
224
+ function importLoopBundle(db, manifest, asName) {
225
+ const srcLoop = manifest.loop_release;
226
+ const srcId = String(srcLoop.id ?? "");
227
+ if (!srcId)
228
+ throw new BundleMalformedError("loop_release is missing id");
229
+ const targetId = asName ?? srcId;
230
+ // Hard-block per spec §7A.2 / AC-25: bundles MAY ONLY land as
231
+ // candidate. A non-candidate source lifecycle is REFUSED; we
232
+ // never silently coerce away a stable / retired source.
233
+ const srcStatus = typeof srcLoop.lifecycle_status === "string"
234
+ ? srcLoop.lifecycle_status
235
+ : "candidate";
236
+ if (srcStatus !== "candidate") {
237
+ throw new BundleImportToStableForbiddenError(srcStatus);
238
+ }
239
+ // The reader always lands as candidate — the source's lifecycle
240
+ // status is silently overridden (spec §7A.2 hard rule).
241
+ const loopRow = {
242
+ id: targetId,
243
+ name: srcLoop.name,
244
+ scenario: srcLoop.scenario,
245
+ trigger_policy: srcLoop.trigger_policy,
246
+ success_criteria_json: JSON.stringify(srcLoop.success_criteria ?? []),
247
+ interaction_policy: srcLoop.interaction_policy,
248
+ feedback_policy: srcLoop.feedback_policy,
249
+ evolution_policy: srcLoop.evolution_policy,
250
+ evaluator_policy_json: JSON.stringify(srcLoop.evaluator_policy ?? []),
251
+ linked_bees_json: JSON.stringify(srcLoop.linked_bees ?? []),
252
+ run_history_json: JSON.stringify(srcLoop.run_history ?? []),
253
+ crystallization_evidence_json: JSON.stringify(srcLoop.crystallization_evidence ?? []),
254
+ lifecycle_status: "candidate",
255
+ version: srcLoop.version,
256
+ schema_version: srcLoop.schema_version ?? PEAKS_BUNDLE_SCHEMA_VERSIONS.loop,
257
+ archived_at: typeof srcLoop.archived_at === "string"
258
+ ? srcLoop.archived_at
259
+ : new Date().toISOString(),
260
+ shareable: srcLoop.shareable === undefined ? 1 : srcLoop.shareable ? 1 : 0,
261
+ share_excluded_paths: JSON.stringify(srcLoop.share_excluded_paths ?? []),
262
+ desktop_visible: srcLoop.desktop_visible === undefined
263
+ ? 1
264
+ : srcLoop.desktop_visible
265
+ ? 1
266
+ : 0,
267
+ export_bundle_format: typeof srcLoop.export_bundle_format === "string"
268
+ ? srcLoop.export_bundle_format
269
+ : "peaks.bundle/1",
270
+ };
271
+ // Defense-in-depth: refuse if lifecycle was anything other than
272
+ // candidate. (The schema parse above already pinned the constant,
273
+ // but we layer this so any future schema relaxation cannot
274
+ // bypass the import-to-candidate rule.)
275
+ if (loopRow.lifecycle_status !== "candidate") {
276
+ throw new BundleImportToStableForbiddenError(loopRow.lifecycle_status);
277
+ }
278
+ // First materialise related bee_release rows. The bundle's
279
+ // loop_bee_relations reference these bee ids; re-stamping the
280
+ // relations before the bee rows land would FK-constraint fail
281
+ // on the receiver.
282
+ const beeIdMap = materialiseRelatedBees(db, manifest.related_bee_releases);
283
+ const tx = db.transaction(() => {
284
+ db.prepare(`INSERT OR REPLACE INTO loop_release (
285
+ id, name, scenario, trigger_policy,
286
+ success_criteria_json, interaction_policy, feedback_policy, evolution_policy,
287
+ evaluator_policy_json, linked_bees_json, run_history_json, crystallization_evidence_json,
288
+ lifecycle_status, version, schema_version, archived_at,
289
+ shareable, share_excluded_paths, desktop_visible, export_bundle_format
290
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(loopRow.id, loopRow.name, loopRow.scenario, loopRow.trigger_policy, loopRow.success_criteria_json, loopRow.interaction_policy, loopRow.feedback_policy, loopRow.evolution_policy, loopRow.evaluator_policy_json, loopRow.linked_bees_json, loopRow.run_history_json, loopRow.crystallization_evidence_json, loopRow.lifecycle_status, loopRow.version, loopRow.schema_version, loopRow.archived_at, loopRow.shareable, loopRow.share_excluded_paths, loopRow.desktop_visible, loopRow.export_bundle_format);
291
+ // Re-stamp relations (preserving source row content, but the
292
+ // loop_id is rewritten if `asName` was supplied).
293
+ for (const rel of manifest.loop_bee_relations) {
294
+ const r = rel;
295
+ const originalBeeId = Number(r.bee_release_id);
296
+ const newBeeId = beeIdMap.get(originalBeeId) ?? originalBeeId;
297
+ db.prepare(`INSERT OR REPLACE INTO loop_bee_relation (
298
+ loop_release_id, bee_release_id, role, reason, schema_version, created_at
299
+ ) VALUES (?, ?, ?, ?, ?, ?)`).run(asName ?? String(r.loop_release_id), newBeeId, String(r.role), String(r.reason ?? ""), String(r.schema_version ?? PEAKS_BUNDLE_SCHEMA_VERSIONS.loop_bee_relation), typeof r.created_at === "string"
300
+ ? r.created_at
301
+ : new Date().toISOString());
302
+ }
303
+ });
304
+ tx();
305
+ return targetId;
306
+ }
307
+ /* ---------------------------------------------------------------------- */
308
+ /* Related-bees materialisation */
309
+ /* ---------------------------------------------------------------------- */
310
+ /**
311
+ * Materialise the related_bee_releases rows in `db`. Returns a map
312
+ * from source bee_release.id → new bee_release.id so the loop /
313
+ * relations re-write can redirect the foreign keys.
314
+ *
315
+ * On a hash collision (a bee_name already exists on the receiver),
316
+ * we preserve the source row content but keep the existing
317
+ * `id`; the relations then skip the redirect for that bee. This
318
+ * is the safest cross-user behaviour: the receiver's existing
319
+ * bee_name wins, and we never overwrite user data.
320
+ */
321
+ function materialiseRelatedBees(db, relatedBees) {
322
+ const map = new Map();
323
+ if (relatedBees.length === 0)
324
+ return map;
325
+ for (const raw of relatedBees) {
326
+ const obj = raw;
327
+ const release = obj.bee_release ?? {};
328
+ const srcId = Number(release.id);
329
+ if (!Number.isInteger(srcId) || srcId <= 0)
330
+ continue;
331
+ const beeName = String(release.bee_name ?? "");
332
+ if (!beeName)
333
+ continue;
334
+ // Honour the same non-candidate hard rule at the bee row.
335
+ const srcStatus = typeof release.lifecycle_status === "string"
336
+ ? release.lifecycle_status
337
+ : "candidate";
338
+ if (srcStatus !== "candidate") {
339
+ throw new BundleImportToStableForbiddenError(srcStatus);
340
+ }
341
+ // Pre-existing receiver-side bee with the same name → keep its id.
342
+ const existing = db
343
+ .prepare("SELECT id FROM bee_release WHERE bee_name = ?")
344
+ .get(beeName);
345
+ let newId;
346
+ if (existing) {
347
+ newId = existing.id;
348
+ }
349
+ else {
350
+ const ins = db.prepare(`INSERT INTO bee_release (
351
+ bee_name, version, source, archived_at, archived_by, user_intent_raw,
352
+ description, parent_version, changelog, shareable, desktop_visible
353
+ ) VALUES (?, ?, 'user', ?, 'user', ?, ?, ?, ?, ?, ?)`);
354
+ const info = ins.run(beeName, String(release.version ?? "0.0.0"), new Date().toISOString(), release.user_intent_raw ?? null, release.description ?? null, release.parent_version ?? null, release.changelog ?? null, release.shareable === false ? 0 : 1, release.desktop_visible === false ? 0 : 1);
355
+ newId = Number(info.lastInsertRowid);
356
+ if (obj.manifest) {
357
+ const m = obj.manifest;
358
+ db.prepare(`INSERT INTO bee_manifest (
359
+ release_id, schema_version, description, segments_json,
360
+ entrypoint_preamble, promotion, min_cycles,
361
+ requires_human, requires_smoke, retire_on_misses
362
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(newId, String(m.schema_version ?? "peaks.bee/1"), String(m.description ?? ""), JSON.stringify(m.segments_json ?? []), m.entrypoint_preamble ?? null, String(m.promotion ?? "manual"), m.min_cycles ?? null, m.requires_human === undefined ? 1 : Number(m.requires_human), m.requires_smoke === undefined ? 1 : Number(m.requires_smoke), m.retire_on_misses ?? null);
363
+ }
364
+ }
365
+ map.set(srcId, newId);
366
+ }
367
+ return map;
368
+ }
369
+ /* ---------------------------------------------------------------------- */
370
+ /* Bee import */
371
+ /* ---------------------------------------------------------------------- */
372
+ function importBeeBundle(db, manifest, _blobsDir, asName) {
373
+ if (!manifest.bee_release) {
374
+ throw new BundleMalformedError("bee_release payload missing from bee bundle");
375
+ }
376
+ const beeObj = manifest.bee_release;
377
+ const release = beeObj.bee_release;
378
+ const newBeeName = asName ?? String(release.bee_name ?? "");
379
+ if (!newBeeName)
380
+ throw new BundleMalformedError("bee_release.bee_name missing");
381
+ // Same hard rule: any non-candidate import is refused. The reader
382
+ // does not honor --as-stable switches.
383
+ const srcStatus = typeof release.lifecycle_status === "string"
384
+ ? release.lifecycle_status
385
+ : "candidate";
386
+ if (srcStatus !== "candidate") {
387
+ throw new BundleImportToStableForbiddenError(srcStatus);
388
+ }
389
+ let newId = -1;
390
+ const tx = db.transaction(() => {
391
+ const ins = db.prepare(`INSERT INTO bee_release (
392
+ bee_name, version, source, archived_at, archived_by, user_intent_raw,
393
+ description, parent_version, changelog, shareable, desktop_visible
394
+ ) VALUES (?, ?, 'user', ?, 'user', ?, ?, ?, ?, ?, ?)`);
395
+ const info = ins.run(newBeeName, String(release.version ?? "0.0.0"), new Date().toISOString(), release.user_intent_raw ?? null, release.description ?? null, release.parent_version ?? null, release.changelog ?? null, release.shareable === false ? 0 : 1, release.desktop_visible === false ? 0 : 1);
396
+ newId = Number(info.lastInsertRowid);
397
+ if (beeObj.manifest) {
398
+ const m = beeObj.manifest;
399
+ db.prepare(`INSERT INTO bee_manifest (
400
+ release_id, schema_version, description, segments_json,
401
+ entrypoint_preamble, promotion, min_cycles,
402
+ requires_human, requires_smoke, retire_on_misses
403
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(newId, String(m.schema_version ?? "peaks.bee/1"), String(m.description ?? ""), JSON.stringify(m.segments_json ?? []), m.entrypoint_preamble ?? null, String(m.promotion ?? "manual"), m.min_cycles ?? null, m.requires_human === undefined ? 1 : Number(m.requires_human), m.requires_smoke === undefined ? 1 : Number(m.requires_smoke), m.retire_on_misses ?? null);
404
+ }
405
+ for (const s of beeObj.segments ?? []) {
406
+ db.prepare(`INSERT INTO bee_segment_ref (
407
+ release_id, segment_name, inputs_json, outputs_json, side_effects
408
+ ) VALUES (?, ?, ?, ?, ?)`).run(newId, String(s.segment_name ?? ""), s.inputs_json ?? null, s.outputs_json ?? null, s.side_effects ?? null);
409
+ }
410
+ for (const f of beeObj.files ?? []) {
411
+ db.prepare(`INSERT INTO bee_file (
412
+ release_id, owner_kind, owner_name, path, kind, size_bytes, sha256, blob_path
413
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(newId, String(f.owner_kind ?? "bee"), newBeeName, String(f.path ?? ""), String(f.kind ?? "other"), Number(f.size_bytes ?? 0), String(f.sha256 ?? ""), String(f.blob_path ?? ""));
414
+ }
415
+ for (const c of beeObj.changes ?? []) {
416
+ db.prepare(`INSERT INTO bee_change (
417
+ release_id, change_kind, target_kind, target_name, detail
418
+ ) VALUES (?, ?, ?, ?, ?)`).run(newId, String(c.change_kind ?? ""), String(c.target_kind ?? ""), String(c.target_name ?? ""), c.detail ?? null);
419
+ }
420
+ });
421
+ tx();
422
+ return newId;
423
+ }
424
+ /* ---------------------------------------------------------------------- */
425
+ /* Utility */
426
+ /* ---------------------------------------------------------------------- */
427
+ function readDirEntries(dir) {
428
+ try {
429
+ return readdirSync(dir);
430
+ }
431
+ catch {
432
+ return [];
433
+ }
434
+ }