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,429 @@
1
+ /**
2
+ * bundle-writer.ts — writeBundle(loopOrBeeId, outPath) (spec §7A.2).
3
+ *
4
+ * M7 / spec §7A.2 / §10 RL-9.
5
+ *
6
+ * Writes a `peaks.bundle/1` tar.gz that captures:
7
+ *
8
+ * - One anchor asset: a loop_release (kind='loop') OR a single
9
+ * bee_release (kind='bee'). A loop bundle additionally carries
10
+ * related_bee_releases (all loop↔bee relations resolved) and
11
+ * loop_bee_relations rows.
12
+ * - All evidence_briefs (crystallization_event rows) that
13
+ * reference the anchor (created_loop_release_id /
14
+ * updated_loop_release_id / created_bee_release_id /
15
+ * updated_bee_release_id).
16
+ *
17
+ * Hard rules enforced at THIS layer:
18
+ *
19
+ * - The source release MUST have `shareable !== false`. A
20
+ * `shareable=false` source THROWS
21
+ * `SHARE_BUNDLE_NOT_SHAREABLE` BEFORE the tarball is written.
22
+ * The CLI layer is the canonical gate (per spec §7A.2 /
23
+ * §10 RL-9); the writer enforces the same gate at the
24
+ * service boundary for defense in depth.
25
+ * - The bundle NEVER captures private run_state, the personal
26
+ * `.peaks/memory/personal/` directory, or raw `state.db`
27
+ * rows. The exclusion is declared in the manifest's
28
+ * `exclusion_manifest` field; physically, the writer only
29
+ * pulls rows from the structured tables.
30
+ * - The bundle is a tarball whose root contains `manifest.json`,
31
+ * `relations.json`, `evidence_briefs/*.json`, and
32
+ * `blobs/<sha256>` content-addressed blobs (mirroring the
33
+ * SkillHub release pattern).
34
+ *
35
+ * The writer runs `tar -czf <outPath> -C <stageDir> .` exactly the
36
+ * same way `release-export.ts` does — same layout, same tar
37
+ * invocation, so the `bundle-reader.ts` symmetrical side can
38
+ * extract via `runTar(['-xzf', inPath, '-C', stageDir])` with no
39
+ * extra plumbing.
40
+ */
41
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, } from "node:fs";
42
+ import { join } from "node:path";
43
+ import { runTar } from "../skillhub/tar-runtime.js";
44
+ import { PEAKS_BUNDLE_DEFAULT_MINOR_VERSION, PEAKS_BUNDLE_FORMAT_CONSTANT, PEAKS_BUNDLE_FORMAT_VERSION_MAJOR, PEAKS_BUNDLE_SCHEMA_VERSIONS, SHARE_BUNDLE_ERROR_CODES, } from "./bundle-types.js";
45
+ /* ---------------------------------------------------------------------- */
46
+ /* Hard-block errors */
47
+ /* ---------------------------------------------------------------------- */
48
+ /**
49
+ * Thrown when the source release has `shareable === false`. The
50
+ * writer throws BEFORE any tarball work; the CLI layer surfaces
51
+ * `code = SHARE_BUNDLE_NOT_SHAREABLE`.
52
+ */
53
+ export class BundleNotShareableError extends Error {
54
+ code;
55
+ constructor(message) {
56
+ super(message);
57
+ this.name = "BundleNotShareableError";
58
+ this.code = SHARE_BUNDLE_ERROR_CODES.NOT_SHAREABLE;
59
+ }
60
+ }
61
+ /**
62
+ * Thrown when the writer cannot locate the source asset (loop id
63
+ * not present in `loop_release`, or bee_release.id not present).
64
+ * Includes both `LOOP_NOT_FOUND` and `BEE_NOT_FOUND` flavors —
65
+ * both fall under `BUNDLE_ASSET_NOT_FOUND` for the CLI envelope.
66
+ */
67
+ export class BundleAssetNotFoundError extends Error {
68
+ code;
69
+ assetKind;
70
+ assetId;
71
+ constructor(assetKind, assetId) {
72
+ super(`${assetKind} asset not found for id='${String(assetId)}'`);
73
+ this.name = "BundleAssetNotFoundError";
74
+ this.code = "BUNDLE_ASSET_NOT_FOUND";
75
+ this.assetKind = assetKind;
76
+ this.assetId = assetId;
77
+ }
78
+ }
79
+ /* ---------------------------------------------------------------------- */
80
+ /* Manifest builder */
81
+ /* ---------------------------------------------------------------------- */
82
+ /* eslint-disable @typescript-eslint/no-explicit-any */
83
+ /**
84
+ * Internal: extract a serialized LoopRelease row ready for the
85
+ * bundle manifest. The writer does NOT validate with
86
+ * `LoopReleaseSchema` at write time — the source row was already
87
+ * validated when it was written to the DB; the reader validates
88
+ * on the way back in.
89
+ */
90
+ function readLoopReleaseRow(db, id) {
91
+ const row = db.prepare("SELECT * FROM loop_release WHERE id = ?").get(id);
92
+ if (!row)
93
+ return undefined;
94
+ // Re-shape SQLite 0/1 INTEGER convention to JS boolean; keep
95
+ // JSON-shaped columns as their parsed-text representation so the
96
+ // JSON.stringify round-trip preserves fidelity.
97
+ return {
98
+ id: row.id,
99
+ name: row.name,
100
+ scenario: row.scenario,
101
+ trigger_policy: row.trigger_policy,
102
+ success_criteria: JSON.parse(String(row.success_criteria_json ?? "[]")),
103
+ interaction_policy: row.interaction_policy,
104
+ feedback_policy: row.feedback_policy,
105
+ evolution_policy: row.evolution_policy,
106
+ evaluator_policy: JSON.parse(String(row.evaluator_policy_json ?? "[]")),
107
+ linked_bees: JSON.parse(String(row.linked_bees_json ?? "[]")),
108
+ run_history: JSON.parse(String(row.run_history_json ?? "[]")),
109
+ crystallization_evidence: JSON.parse(String(row.crystallization_evidence_json ?? "[]")),
110
+ lifecycle_status: row.lifecycle_status,
111
+ version: row.version,
112
+ schema_version: row.schema_version,
113
+ archived_at: row.archived_at,
114
+ shareable: row.shareable === 1,
115
+ share_excluded_paths: JSON.parse(String(row.share_excluded_paths ?? "[]")),
116
+ desktop_visible: row.desktop_visible === 1,
117
+ export_bundle_format: row.export_bundle_format,
118
+ };
119
+ }
120
+ /**
121
+ * Internal: read a single `bee_release` row + its relations/segments/files
122
+ * for the bundle manifest. Mirrors the existing
123
+ * `src/services/skillhub/release-export.ts` shape — the writer
124
+ * includes `bee_release`, plus the inline manifest/segment/file/change
125
+ * rows so the receiver can re-materialize the full bee.
126
+ */
127
+ function readBeeReleaseBundle(db, id) {
128
+ const release = db
129
+ .prepare("SELECT * FROM bee_release WHERE id = ?")
130
+ .get(id);
131
+ if (!release)
132
+ return undefined;
133
+ const manifest = db
134
+ .prepare("SELECT * FROM bee_manifest WHERE release_id = ?")
135
+ .get(id);
136
+ const segments = db
137
+ .prepare("SELECT * FROM bee_segment_ref WHERE release_id = ?")
138
+ .all(id);
139
+ const files = db
140
+ .prepare("SELECT * FROM bee_file WHERE release_id = ?")
141
+ .all(id);
142
+ const changes = db
143
+ .prepare("SELECT * FROM bee_change WHERE release_id = ?")
144
+ .all(id);
145
+ return {
146
+ bee_release: release,
147
+ manifest,
148
+ segments,
149
+ files,
150
+ changes,
151
+ };
152
+ }
153
+ /**
154
+ * Internal: read loop_bee_relation rows for a given loop.
155
+ */
156
+ function readLoopBeeRelationsForLoop(db, loopId) {
157
+ return db
158
+ .prepare("SELECT * FROM loop_bee_relation WHERE loop_release_id = ? ORDER BY id ASC")
159
+ .all(loopId);
160
+ }
161
+ /**
162
+ * Internal: read loop_bee_relation rows for a given bee.
163
+ */
164
+ function readLoopBeeRelationsForBee(db, beeReleaseId) {
165
+ return db
166
+ .prepare("SELECT * FROM loop_bee_relation WHERE bee_release_id = ? ORDER BY id ASC")
167
+ .all(beeReleaseId);
168
+ }
169
+ /**
170
+ * Internal: read evidence briefs (crystallization_event rows) that
171
+ * reference a given loop / bee. Includes both created_* and
172
+ * updated_* FK pointers so any crystallization event that touched
173
+ * the asset is captured.
174
+ */
175
+ function readEvidenceBriefsForAsset(db, refs) {
176
+ const wheres = [];
177
+ const params = [];
178
+ if (refs.loopId !== undefined) {
179
+ wheres.push("created_loop_release_id = ?");
180
+ params.push(refs.loopId);
181
+ wheres.push("updated_loop_release_id = ?");
182
+ params.push(refs.loopId);
183
+ }
184
+ if (refs.beeReleaseId !== undefined) {
185
+ wheres.push("created_bee_release_id = ?");
186
+ params.push(refs.beeReleaseId);
187
+ wheres.push("updated_bee_release_id = ?");
188
+ params.push(refs.beeReleaseId);
189
+ }
190
+ if (wheres.length === 0)
191
+ return [];
192
+ const whereSql = wheres.join(" OR ");
193
+ const rows = db
194
+ .prepare(`SELECT * FROM crystallization_event WHERE ${whereSql} ORDER BY created_at DESC`)
195
+ .all(...params);
196
+ return rows.map((row) => ({
197
+ id: row.id,
198
+ trigger: row.trigger,
199
+ evidence_brief: JSON.parse(String(row.evidence_brief_json ?? "{}")),
200
+ evidence_bullets: JSON.parse(String(row.evidence_bullets_json ?? "[]")),
201
+ source_trace_pointers: JSON.parse(String(row.source_trace_pointers_json ?? "[]")),
202
+ evaluator_summary: row.evaluator_summary,
203
+ user_decision_summary: row.user_decision_summary,
204
+ created_loop_release_id: row.created_loop_release_id ?? undefined,
205
+ updated_loop_release_id: row.updated_loop_release_id ?? undefined,
206
+ created_bee_release_id: row.created_bee_release_id ?? undefined,
207
+ updated_bee_release_id: row.updated_bee_release_id ?? undefined,
208
+ lifecycle_status: row.lifecycle_status,
209
+ schema_version: row.schema_version,
210
+ created_at: row.created_at,
211
+ }));
212
+ }
213
+ /* eslint-enable @typescript-eslint/no-explicit-any */
214
+ /* ---------------------------------------------------------------------- */
215
+ /* Main entrypoint */
216
+ /* ---------------------------------------------------------------------- */
217
+ /**
218
+ * Write a `peaks.bundle/1` tar.gz to `outPath`. The bundle
219
+ * captures ONE anchor asset (`loop` or `bee`) plus its relations,
220
+ * supporting bees (loop kind only), and evidence briefs.
221
+ *
222
+ * Hard blocks raised at THIS layer:
223
+ *
224
+ * - `BundleNotShareableError`: the source has `shareable === false`.
225
+ * - `BundleAssetNotFoundError`: the source id was not present in the table.
226
+ *
227
+ * On success the call returns the resolved asset id and kind; the
228
+ * CLI layer uses this to render a one-line status.
229
+ */
230
+ export function writeBundle(args) {
231
+ const { db, blobsDir, outPath } = args;
232
+ if (args.kind === "loop") {
233
+ return writeLoopBundle({
234
+ db,
235
+ blobsDir,
236
+ loopId: String(args.id),
237
+ outPath,
238
+ });
239
+ }
240
+ return writeBeeBundle({
241
+ db,
242
+ blobsDir,
243
+ beeReleaseId: typeof args.id === "number" ? args.id : Number(args.id),
244
+ outPath,
245
+ });
246
+ }
247
+ /* ---------------------------------------------------------------------- */
248
+ /* Loop bundle writer */
249
+ /* ---------------------------------------------------------------------- */
250
+ function writeLoopBundle(args) {
251
+ const loopRow = args.db
252
+ .prepare("SELECT * FROM loop_release WHERE id = ?")
253
+ .get(args.loopId);
254
+ if (!loopRow)
255
+ throw new BundleAssetNotFoundError("loop", args.loopId);
256
+ // spec §7A.2 hard block: shareable=false blocks the export at the
257
+ // CLI layer. We re-enforce at the writer for defense in depth.
258
+ if (loopRow.shareable === 0) {
259
+ throw new BundleNotShareableError(`loop_release '${args.loopId}' has shareable=false; bundles cannot be exported for non-shareable assets (spec §7A.2)`);
260
+ }
261
+ const loop = readLoopReleaseRow(args.db, args.loopId);
262
+ if (!loop)
263
+ throw new BundleAssetNotFoundError("loop", args.loopId);
264
+ const relations = readLoopBeeRelationsForLoop(args.db, args.loopId);
265
+ const relatedBees = [];
266
+ const blobHashes = new Set();
267
+ for (const rel of relations) {
268
+ const beeId = Number(rel.bee_release_id);
269
+ const bee = readBeeReleaseBundle(args.db, beeId);
270
+ if (bee) {
271
+ relatedBees.push(bee);
272
+ // Pull every content-addressed sha256 across all bee_files rows.
273
+ for (const f of bee.files) {
274
+ const sha = String(f.sha256 ?? "");
275
+ if (sha)
276
+ blobHashes.add(sha);
277
+ }
278
+ }
279
+ }
280
+ const evidenceBriefs = readEvidenceBriefsForAsset(args.db, {
281
+ loopId: args.loopId,
282
+ });
283
+ const manifest = {
284
+ format_constant: PEAKS_BUNDLE_FORMAT_CONSTANT,
285
+ format_version_major: PEAKS_BUNDLE_FORMAT_VERSION_MAJOR,
286
+ format_version_minor: PEAKS_BUNDLE_DEFAULT_MINOR_VERSION,
287
+ schema_versions: {
288
+ loop: PEAKS_BUNDLE_SCHEMA_VERSIONS.loop,
289
+ bee: PEAKS_BUNDLE_SCHEMA_VERSIONS.bee,
290
+ loop_bee_relation: PEAKS_BUNDLE_SCHEMA_VERSIONS.loop_bee_relation,
291
+ crystallization: PEAKS_BUNDLE_SCHEMA_VERSIONS.crystallization,
292
+ },
293
+ kind: "loop",
294
+ loop_release: loop,
295
+ bee_release: undefined,
296
+ related_bee_releases: relatedBees,
297
+ loop_bee_relations: relations,
298
+ evidence_briefs: evidenceBriefs,
299
+ exclusion_manifest: {
300
+ private_run_state: "excluded",
301
+ personal_memory: "excluded",
302
+ state_db_rows: "excluded",
303
+ },
304
+ };
305
+ runStageTarball({
306
+ outPath: args.outPath,
307
+ blobsDir: args.blobsDir,
308
+ blobHashes,
309
+ manifest,
310
+ });
311
+ return { outPath: args.outPath, kind: "loop", assetId: args.loopId };
312
+ }
313
+ /* ---------------------------------------------------------------------- */
314
+ /* Bee bundle writer */
315
+ /* ---------------------------------------------------------------------- */
316
+ function writeBeeBundle(args) {
317
+ const releaseRow = args.db
318
+ .prepare("SELECT * FROM bee_release WHERE id = ?")
319
+ .get(args.beeReleaseId);
320
+ if (!releaseRow)
321
+ throw new BundleAssetNotFoundError("bee", args.beeReleaseId);
322
+ // spec §7A.2 hard block: same as loop kind.
323
+ if (releaseRow.shareable === 0) {
324
+ throw new BundleNotShareableError(`bee_release id='${args.beeReleaseId}' has shareable=false; bundles cannot be exported for non-shareable assets (spec §7A.2)`);
325
+ }
326
+ const bee = readBeeReleaseBundle(args.db, args.beeReleaseId);
327
+ if (!bee)
328
+ throw new BundleAssetNotFoundError("bee", args.beeReleaseId);
329
+ const relations = readLoopBeeRelationsForBee(args.db, args.beeReleaseId);
330
+ const evidenceBriefs = readEvidenceBriefsForAsset(args.db, {
331
+ beeReleaseId: args.beeReleaseId,
332
+ });
333
+ const blobHashes = new Set();
334
+ for (const f of bee.files) {
335
+ const sha = String(f.sha256 ?? "");
336
+ if (sha)
337
+ blobHashes.add(sha);
338
+ }
339
+ const manifest = {
340
+ format_constant: PEAKS_BUNDLE_FORMAT_CONSTANT,
341
+ format_version_major: PEAKS_BUNDLE_FORMAT_VERSION_MAJOR,
342
+ format_version_minor: PEAKS_BUNDLE_DEFAULT_MINOR_VERSION,
343
+ schema_versions: {
344
+ loop: PEAKS_BUNDLE_SCHEMA_VERSIONS.loop,
345
+ bee: PEAKS_BUNDLE_SCHEMA_VERSIONS.bee,
346
+ loop_bee_relation: PEAKS_BUNDLE_SCHEMA_VERSIONS.loop_bee_relation,
347
+ crystallization: PEAKS_BUNDLE_SCHEMA_VERSIONS.crystallization,
348
+ },
349
+ kind: "bee",
350
+ loop_release: undefined,
351
+ bee_release: bee,
352
+ related_bee_releases: [],
353
+ loop_bee_relations: relations,
354
+ evidence_briefs: evidenceBriefs,
355
+ exclusion_manifest: {
356
+ private_run_state: "excluded",
357
+ personal_memory: "excluded",
358
+ state_db_rows: "excluded",
359
+ },
360
+ };
361
+ runStageTarball({
362
+ outPath: args.outPath,
363
+ blobsDir: args.blobsDir,
364
+ blobHashes,
365
+ manifest,
366
+ });
367
+ return { outPath: args.outPath, kind: "bee", assetId: args.beeReleaseId };
368
+ }
369
+ /* ---------------------------------------------------------------------- */
370
+ /* Stage tarball — write the directory, then tar.gz it. */
371
+ /* ---------------------------------------------------------------------- */
372
+ function runStageTarball(args) {
373
+ const { outPath, blobsDir, blobHashes, manifest } = args;
374
+ const stageDir = outPath + ".stage";
375
+ if (existsSync(stageDir))
376
+ rmSync(stageDir, { recursive: true, force: true });
377
+ mkdirSync(stageDir, { recursive: true });
378
+ let tarOk = false;
379
+ try {
380
+ writeFileSync(join(stageDir, "manifest.json"), JSON.stringify(manifest, null, 2));
381
+ mkdirSync(join(stageDir, "evidence_briefs"), { recursive: true });
382
+ for (const brief of manifest.evidence_briefs) {
383
+ const id = String(brief.id ?? Math.random().toString(16).slice(2));
384
+ writeFileSync(join(stageDir, "evidence_briefs", `${id}.json`), JSON.stringify(brief, null, 2));
385
+ }
386
+ writeFileSync(join(stageDir, "relations.json"), JSON.stringify({
387
+ loop_bee_relations: manifest.loop_bee_relations,
388
+ related_bee_releases: manifest.related_bee_releases,
389
+ }, null, 2));
390
+ // Copy content-addressed blobs (only ones the bundle actually
391
+ // references). The reader's blob re-materialisation
392
+ // (see bundle-reader.ts) uses the same content-addressed
393
+ // `blobs/<sha256>` layout as the existing SkillHub hashing.
394
+ if (blobHashes.size > 0) {
395
+ mkdirSync(join(stageDir, "blobs"), { recursive: true });
396
+ for (const sha of blobHashes) {
397
+ const src = join(blobsDir, sha.slice(0, 2), sha);
398
+ if (!existsSync(src))
399
+ continue;
400
+ writeFileSync(join(stageDir, "blobs", sha), readFileSync(src));
401
+ }
402
+ }
403
+ // Write EVALUATION_REQUIRED.md — a marker that signals the
404
+ // receiver to run an independent evaluation before any durable
405
+ // change. Spec §7A.2 hard rules (5th bullet).
406
+ writeFileSync(join(stageDir, "EVALUATION_REQUIRED.md"), [
407
+ "# EVALUATION_REQUIRED",
408
+ "",
409
+ "The receiver MUST run an independent evaluation before",
410
+ "any durable change to this loop / bee. Bundles always",
411
+ "import as `candidate`; promotion to `stable` requires",
412
+ "an `evolution_evaluation` row with an",
413
+ "`independent_scorer_verdict` (spec §7A.2 / §10 RL-9).",
414
+ "",
415
+ ].join("\n"));
416
+ runTar(["-czf", outPath, "-C", stageDir, "."]);
417
+ tarOk = true;
418
+ }
419
+ finally {
420
+ if (existsSync(stageDir) && !tarOk) {
421
+ rmSync(stageDir, { recursive: true, force: true });
422
+ }
423
+ // Always remove the stage dir after tar succeeds too — the
424
+ // bundle lives entirely on disk at outPath.
425
+ if (tarOk && existsSync(stageDir)) {
426
+ rmSync(stageDir, { recursive: true, force: true });
427
+ }
428
+ }
429
+ }
@@ -0,0 +1,133 @@
1
+ /**
2
+ * run-state-contract.ts — locked read-only shape for the desktop
3
+ * client (spec §7A.3 / §10 RL-9).
4
+ *
5
+ * The desktop client observes an in-flight bee through a single
6
+ * JSON file. This module locks the shape so the desktop can render
7
+ * progress without holding a lock on the asset:
8
+ *
9
+ * {
10
+ * bee_id: string,
11
+ * status: "running" | "paused" | "done" | "failed" | "blocked",
12
+ * current_step: string,
13
+ * started_at: ISO8601 string,
14
+ * updated_at: ISO8601 string,
15
+ * last_evaluator_verdict: string | null,
16
+ * last_user_choice: string | null
17
+ * }
18
+ *
19
+ * Hard rules (spec §7A.3):
20
+ * - The contract is read-only for the desktop client. Only the
21
+ * running bee (and peaks CLI) may write it.
22
+ * - The shape is locked — adding a field is a schema-version
23
+ * bump; removing or renaming a field is a breaking change.
24
+ *
25
+ * This module exposes:
26
+ * - A Zod schema (`RunStateContractSchema`) that validates the
27
+ * shape with strict rules (no extra keys, exact enum for
28
+ * status).
29
+ * - A type alias (`RunStateContract`).
30
+ * - A constructor-like factory (for testing / fixtures).
31
+ *
32
+ * It does NOT expose any mutation helpers — there are no setters,
33
+ * because the contract is read-only at the desktop. The running
34
+ * bee writes the file via the existing dispatch flow; the desktop
35
+ * client reads via a separate channel.
36
+ */
37
+ import { z } from "zod";
38
+ /**
39
+ * The five status values an in-flight bee may report. The
40
+ * desktop-client UI renders the status badge; only the running bee
41
+ * transitions between them.
42
+ */
43
+ export declare const RUN_STATE_STATUSES: readonly ["running", "paused", "done", "failed", "blocked"];
44
+ export type RunStateStatus = (typeof RUN_STATE_STATUSES)[number];
45
+ /**
46
+ * RunStateContract — the locked read-only shape (spec §7A.3).
47
+ *
48
+ * Notes:
49
+ * - `bee_id`, `current_step`: non-empty NL strings.
50
+ * - `started_at`, `updated_at`: ISO8601; updated_at >= started_at
51
+ * is the caller's job (the schema does not check ordering
52
+ * because a clock skew between the dispatcher and the receiver
53
+ * is a transient runtime issue, not a shape invariant).
54
+ * - `last_evaluator_verdict`: optional NL string; null when no
55
+ * evaluator has fired yet.
56
+ * - `last_user_choice`: optional NL choice summary; null when
57
+ * the user has not made a pick yet.
58
+ *
59
+ * `.strict()` rejects unknown keys — adding a field is a
60
+ * schema-version bump, not a quiet extension.
61
+ */
62
+ export declare const RunStateContractSchema: z.ZodObject<{
63
+ /** Stable bee id (kebab-case). */
64
+ bee_id: z.ZodString;
65
+ /** Current run status (running / paused / done / failed / blocked). */
66
+ status: z.ZodEnum<["running", "paused", "done", "failed", "blocked"]>;
67
+ /** Current step name; NL short label. */
68
+ current_step: z.ZodString;
69
+ /** ISO8601 timestamp the run started. */
70
+ started_at: z.ZodString;
71
+ /** ISO8601 timestamp of the latest update. */
72
+ updated_at: z.ZodString;
73
+ /** NL summary of the latest evaluator verdict (or null when none). */
74
+ last_evaluator_verdict: z.ZodNullable<z.ZodString>;
75
+ /** NL summary of the latest user choice (or null when none). */
76
+ last_user_choice: z.ZodNullable<z.ZodString>;
77
+ }, "strict", z.ZodTypeAny, {
78
+ status: "done" | "failed" | "blocked" | "running" | "paused";
79
+ bee_id: string;
80
+ current_step: string;
81
+ started_at: string;
82
+ updated_at: string;
83
+ last_evaluator_verdict: string | null;
84
+ last_user_choice: string | null;
85
+ }, {
86
+ status: "done" | "failed" | "blocked" | "running" | "paused";
87
+ bee_id: string;
88
+ current_step: string;
89
+ started_at: string;
90
+ updated_at: string;
91
+ last_evaluator_verdict: string | null;
92
+ last_user_choice: string | null;
93
+ }>;
94
+ export type RunStateContract = z.infer<typeof RunStateContractSchema>;
95
+ /**
96
+ * Strict-parse an unknown value as a RunStateContract. Throws
97
+ * `ZodError` on failure. The desktop client uses this at the read
98
+ * boundary; the running bee writes the file via the existing
99
+ * dispatch flow (separate write path).
100
+ */
101
+ export declare function parseRunStateContract(input: unknown): RunStateContract;
102
+ /**
103
+ * Safe-parse twin — same Result-like envelope shape used elsewhere.
104
+ */
105
+ export declare function safeParseRunStateContract(input: unknown): {
106
+ ok: true;
107
+ contract: RunStateContract;
108
+ } | {
109
+ ok: false;
110
+ findings: Array<{
111
+ path: string;
112
+ message: string;
113
+ }>;
114
+ };
115
+ /**
116
+ * Server-side factory used by the dispatch flow to write the file.
117
+ * This helper is exported for testing purposes; the desktop client
118
+ * is read-only and SHOULD NOT call this.
119
+ *
120
+ * The function returns a brand-new `RunStateContract` value; it
121
+ * does NOT mutate any external state. Persistence is the caller's
122
+ * responsibility (the dispatch flow writes a JSON file at a
123
+ * well-known path under `.peaks/_runtime/<sid>/<role>/`).
124
+ */
125
+ export declare function buildRunState(args: {
126
+ bee_id: string;
127
+ status: RunStateStatus;
128
+ current_step: string;
129
+ started_at: string;
130
+ updated_at: string;
131
+ last_evaluator_verdict?: string | null;
132
+ last_user_choice?: string | null;
133
+ }): RunStateContract;