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.
- package/CHANGELOG.md +105 -87
- package/README-en.md +17 -4
- package/README.md +17 -4
- 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/{solo-commands.d.ts → code-commands.d.ts} +24 -24
- package/dist/src/cli/commands/{solo-commands.js → code-commands.js} +78 -78
- package/dist/src/cli/commands/core/skill-command.js +1 -1
- package/dist/src/cli/commands/dispatch-from-dag.js +1 -1
- 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/hook-handle.js +7 -7
- package/dist/src/cli/commands/job-commands.js +2 -2
- 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/session-auto-compact-hook-command.js +2 -2
- 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/commands/sub-agent-shared.d.ts +1 -1
- package/dist/src/cli/commands/user-touchpoint-commands.d.ts +4 -4
- package/dist/src/cli/commands/user-touchpoint-commands.js +14 -14
- package/dist/src/cli/commands/workflow-commands.js +22 -22
- package/dist/src/cli/program.js +23 -6
- package/dist/src/services/artifacts/repair-cycle-service.js +1 -1
- package/dist/src/services/audit/enforcers/active-skill-resolver.d.ts +1 -1
- package/dist/src/services/audit/enforcers/active-skill-resolver.js +1 -1
- package/dist/src/services/audit/enforcers/{solo-code-ban.d.ts → code-ban.d.ts} +6 -6
- package/dist/src/services/audit/enforcers/{solo-code-ban.js → code-ban.js} +6 -6
- package/dist/src/services/audit/red-line-catalog.d.ts +2 -2
- package/dist/src/services/audit/red-line-catalog.js +5 -5
- package/dist/src/services/audit/types.d.ts +2 -2
- package/dist/src/services/{solo → code}/auto-compact-orchestrator.d.ts +1 -1
- package/dist/src/services/{solo → code}/auto-compact-orchestrator.js +3 -3
- package/dist/src/services/{solo → code}/dag-orchestrator.d.ts +1 -1
- package/dist/src/services/{solo → code}/dag-orchestrator.js +1 -1
- package/dist/src/services/{solo → code}/emit-handoff.d.ts +1 -1
- package/dist/src/services/{solo → code}/emit-handoff.js +1 -1
- package/dist/src/services/{solo → code}/job-shape-decision.d.ts +1 -1
- package/dist/src/services/{solo → code}/job-shape-decision.js +5 -5
- package/dist/src/services/{solo → code}/mode-gate.d.ts +9 -9
- package/dist/src/services/{solo → code}/mode-gate.js +5 -5
- package/dist/src/services/{solo → code}/post-compact-detector.d.ts +3 -3
- package/dist/src/services/{solo → code}/post-compact-detector.js +4 -4
- package/dist/src/services/{solo → code}/step-08-gate.d.ts +4 -4
- package/dist/src/services/{solo → code}/step-08-gate.js +4 -4
- package/dist/src/services/{solo → code}/user-touchpoint-classifier.d.ts +1 -1
- package/dist/src/services/{solo → code}/user-touchpoint-classifier.js +1 -1
- package/dist/src/services/context/auto-compact-dispatcher.d.ts +2 -2
- package/dist/src/services/context/auto-compact-dispatcher.js +2 -2
- package/dist/src/services/context/auto-compact-types.d.ts +1 -1
- 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/dispatch/slice-dag.d.ts +2 -2
- package/dist/src/services/dispatch/test-tool-detection.d.ts +1 -1
- package/dist/src/services/dispatch/test-tool-detection.js +1 -1
- 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/feedback/feedback-promotion-service.js +2 -2
- package/dist/src/services/job/job-progress-store.d.ts +1 -1
- package/dist/src/services/job/job-progress-store.js +1 -1
- 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/preferences/preferences-service.d.ts +1 -1
- package/dist/src/services/preferences/preferences-types.d.ts +1 -1
- package/dist/src/services/rd/standards-diagnostic.d.ts +1 -1
- package/dist/src/services/rd/standards-diagnostic.js +2 -2
- package/dist/src/services/recommendations/capability-seed-items.js +1 -1
- package/dist/src/services/recommendations/capability-seed-mappings.js +1 -1
- package/dist/src/services/recommendations/recommendation-service.js +1 -1
- package/dist/src/services/refactor/refactor-service.d.ts +1 -1
- 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/signal/cancel-handler.js +3 -3
- package/dist/src/services/skill/resume-detector.js +1 -1
- 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/skills/skill-presence-service.d.ts +1 -1
- 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/services/upgrade/1x-detector-service.js +1 -1
- package/dist/src/services/workflow/pipeline-verify-service.d.ts +1 -1
- package/dist/src/services/workflow/pipeline-verify-service.js +3 -3
- package/dist/src/services/workflow/workflow-autonomous-service.d.ts +3 -3
- package/dist/src/services/workflow/workflow-autonomous-service.js +2 -2
- package/dist/src/services/workflow/workflow-router-service.d.ts +6 -6
- package/dist/src/services/workflow/workflow-router-service.js +33 -33
- package/dist/src/services/workspace/claude-settings-template.d.ts +1 -1
- package/dist/src/services/workspace/claude-settings-template.js +5 -5
- 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/output-styles/peaks-skill-swarm.md +3 -3
- package/package.json +6 -2
- package/skills/bee/peaks-qa/SKILL.md +1 -1
- package/skills/bee/peaks-qa/references/artifact-per-request.md +3 -3
- package/skills/bee/peaks-qa/references/qa-sub-agent-dispatch.md +6 -6
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +1 -1
- package/skills/bee/peaks-rd/references/rd-runbook.md +1 -1
- package/skills/bee/peaks-rd/references/rd-sub-agent-dispatch.md +11 -11
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.md +3 -3
- package/skills/bee/peaks-ui/SKILL.md +8 -8
- package/skills/peaks-code/SKILL.md +34 -21
- package/skills/peaks-code/references/anchoring-and-session-info.md +2 -2
- package/skills/peaks-code/references/artifact-contracts.md +1 -1
- package/skills/peaks-code/references/boundaries.md +3 -3
- package/skills/peaks-code/references/codegraph-orchestration.md +1 -1
- package/skills/peaks-code/references/dag-orchestrator.md +7 -7
- package/skills/peaks-code/references/external-references.md +2 -2
- package/skills/peaks-code/references/fanout-mandatory.md +3 -3
- package/skills/peaks-code/references/fast-mode.md +2 -2
- package/skills/peaks-code/references/frontend-only-mode.md +9 -9
- package/skills/peaks-code/references/job-loop.md +1 -1
- package/skills/peaks-code/references/local-artifact-workspace.md +2 -2
- package/skills/peaks-code/references/micro-cycle.md +14 -14
- package/skills/peaks-code/references/mode-selection-with-stale-presence.md +5 -5
- package/skills/peaks-code/references/mode-selection.md +3 -3
- package/skills/peaks-code/references/openspec-workflow.md +16 -16
- package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
- package/skills/peaks-code/references/quality-gate-cheatsheet.md +1 -1
- package/skills/peaks-code/references/refactor-mode.md +1 -1
- package/skills/peaks-code/references/runbook.md +15 -15
- package/skills/peaks-code/references/step-0-55-1x-detection.md +1 -1
- package/skills/peaks-code/references/step-0-8-gate.md +11 -11
- package/skills/peaks-code/references/sub-agent-dispatch.md +8 -8
- package/skills/peaks-code/references/swarm-dispatch-contract.md +17 -17
- package/skills/peaks-code/references/workflow-gates-and-types.md +2 -2
- package/skills/peaks-code/references/workflow.md +5 -5
- package/skills/peaks-content/SKILL.md +272 -0
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +307 -0
- package/skills/peaks-resume/SKILL.md +2 -2
- package/skills/peaks-status/SKILL.md +2 -2
- package/skills/peaks-test/SKILL.md +2 -2
- /package/dist/src/services/{solo → code}/batch-heartbeat-poller.d.ts +0 -0
- /package/dist/src/services/{solo → code}/batch-heartbeat-poller.js +0 -0
- /package/dist/src/services/{solo → code}/status-line-renderer.d.ts +0 -0
- /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
|
+
}
|