peaks-loop 4.0.0-beta.2 → 4.0.0-beta.4
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.
- package/CHANGELOG.md +81 -0
- package/README-en.md +16 -3
- package/README.md +16 -3
- package/dist/src/cli/commands/asset-commands.d.ts +45 -0
- package/dist/src/cli/commands/asset-commands.js +404 -0
- package/dist/src/cli/commands/bee-commands.d.ts +26 -0
- package/dist/src/cli/commands/bee-commands.js +169 -0
- package/dist/src/cli/commands/evolution-commands.d.ts +27 -0
- package/dist/src/cli/commands/evolution-commands.js +393 -0
- package/dist/src/cli/commands/loop-commands.d.ts +19 -13
- package/dist/src/cli/commands/loop-commands.js +147 -13
- package/dist/src/cli/commands/sediment-commands.js +9 -0
- package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.d.ts +67 -0
- package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.js +173 -0
- package/dist/src/cli/program.js +17 -0
- package/dist/src/services/crystallization/crystallization-service.d.ts +239 -0
- package/dist/src/services/crystallization/crystallization-service.js +397 -0
- package/dist/src/services/crystallization/crystallization-store.d.ts +75 -0
- package/dist/src/services/crystallization/crystallization-store.js +193 -0
- package/dist/src/services/crystallization/crystallization-types.d.ts +313 -0
- package/dist/src/services/crystallization/crystallization-types.js +299 -0
- package/dist/src/services/crystallization/evidence-brief-builder.d.ts +237 -0
- package/dist/src/services/crystallization/evidence-brief-builder.js +240 -0
- package/dist/src/services/evolution/evolution-service.d.ts +137 -0
- package/dist/src/services/evolution/evolution-service.js +281 -0
- package/dist/src/services/evolution/evolution-store.d.ts +57 -0
- package/dist/src/services/evolution/evolution-store.js +232 -0
- package/dist/src/services/evolution/evolution-types.d.ts +706 -0
- package/dist/src/services/evolution/evolution-types.js +286 -0
- package/dist/src/services/evolution/independent-evaluator-runner.d.ts +90 -0
- package/dist/src/services/evolution/independent-evaluator-runner.js +106 -0
- package/dist/src/services/evolution/regression-skeptic-runner.d.ts +64 -0
- package/dist/src/services/evolution/regression-skeptic-runner.js +113 -0
- package/dist/src/services/loop/loop-bee-relation-service.d.ts +107 -0
- package/dist/src/services/loop/loop-bee-relation-service.js +211 -0
- package/dist/src/services/loop/loop-bee-relation-store.d.ts +53 -0
- package/dist/src/services/loop/loop-bee-relation-store.js +130 -0
- package/dist/src/services/loop/loop-bee-relation-types.d.ts +118 -0
- package/dist/src/services/loop/loop-bee-relation-types.js +114 -0
- package/dist/src/services/loop/loop-release-service.d.ts +46 -0
- package/dist/src/services/loop/loop-release-service.js +66 -0
- package/dist/src/services/loop/loop-release-store.d.ts +55 -0
- package/dist/src/services/loop/loop-release-store.js +134 -0
- package/dist/src/services/loop/loop-release-types.d.ts +250 -0
- package/dist/src/services/loop/loop-release-types.js +162 -0
- package/dist/src/services/share/bundle-reader.d.ts +100 -0
- package/dist/src/services/share/bundle-reader.js +434 -0
- package/dist/src/services/share/bundle-types.d.ts +392 -0
- package/dist/src/services/share/bundle-types.js +318 -0
- package/dist/src/services/share/bundle-writer.d.ts +97 -0
- package/dist/src/services/share/bundle-writer.js +429 -0
- package/dist/src/services/share/run-state-contract.d.ts +133 -0
- package/dist/src/services/share/run-state-contract.js +138 -0
- package/dist/src/services/skillhub/release-import.js +7 -2
- package/dist/src/services/skillhub/release-retain.js +6 -1
- package/dist/src/services/skillhub/sqlite-store.js +17 -1
- package/dist/src/services/skillhub/types.d.ts +10 -0
- package/dist/src/services/standards/loop-engineering-lint.d.ts +38 -0
- package/dist/src/services/standards/loop-engineering-lint.js +101 -0
- package/dist/src/services/standards/loop-engineering-readiness-lint.d.ts +111 -0
- package/dist/src/services/standards/loop-engineering-readiness-lint.js +248 -0
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/examples/video-demo/README.md +57 -0
- package/examples/video-demo/out/en-closing-960.png +0 -0
- package/examples/video-demo/out/zh-closing-960.png +0 -0
- package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
- package/examples/video-demo/preview/peaks-loop-demo.mp4 +0 -0
- package/package.json +6 -2
- package/skills/peaks-code/SKILL.md +13 -0
- package/skills/peaks-content/SKILL.md +272 -0
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +307 -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;
|