peaks-loop 4.0.0-beta.11 → 4.0.0-beta.14

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 CHANGED
@@ -2,9 +2,71 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## 4.0.0-beta.11 — 2026-07-16
5
+ ## 4.0.0-beta.14 — 2026-07-16
6
6
 
7
- ### Status: RELEASED (release of `4.0.0-beta.10` contract; same source tree)
7
+ ### Status: RELEASED (D-019: copy-templates.mjs + package.json files[])
8
+
9
+ > **This release fixes D-019 (full end-to-end)**:
10
+ >
11
+ > 1. `scripts/copy-templates.mjs` now copies
12
+ > `src/services/skillhub/migrations/*.sql` into `dist/services/skillhub/migrations/`.
13
+ > 2. `package.json` `"files"` whitelist now includes `dist/**/*.sql`
14
+ > (previously the whitelist was `dist/**/*.js | .d.ts | .md` only,
15
+ > so the migrations were filtered out of the npm tarball even
16
+ > after being copied to `dist/`).
17
+ >
18
+ > Without this fix, the published `peaks-loop` tarball shipped without
19
+ > the SkillHub SQLite schema, breaking any downstream consumer's first
20
+ > `peaks skill sediment <verb>` with `no such table: bee_release`.
21
+ >
22
+ > **Verdict**: `pnpm build` copies 6 migrations; tarball includes them.
23
+ > In-process vitest tests were always green (they use tsx + source tree
24
+ > directly, not `dist/`), so no test count change. Combined with
25
+ > 4.0.0-beta.13's D-018 path fix, the published tarball is now
26
+ > correct end-to-end.
27
+
28
+ ## 4.0.0-beta.13 — 2026-07-16
29
+
30
+ ### Status: RELEASED (D-018: state.db path relocation)
31
+
32
+ > **This release fixes the SQLite `state.db` location** to match the
33
+ > original peaks-loop design contract: `PEAKS_HOME/.peaks/skills/state.db`
34
+ > (PEAKS_HOME defaults to `~/.peaks`). Previously, the code resolved
35
+ > `home` to `process.cwd()` when invoked from a project root, producing
36
+ > `<project>/.peaks/state.db` — wrong location for loop engineering
37
+ > + bee sediment data.
38
+ >
39
+ > **Fix**: `src/cli/commands/sediment-commands.ts:664-665` now uses
40
+ > `peaksHome()` from `src/services/sop/sop-paths.ts:29` (which honors
41
+ > `PEAKS_HOME` override for test isolation). The downstream
42
+ > `resolveStateDbPath({ home })` (in `src/services/sediment/pool-paths.ts:15`)
43
+ > already correctly returned `{home}/.peaks/skills/state.db`; only the
44
+ > `home` resolution was broken.
45
+ >
46
+ > **Side bug found but out of scope**: `src/services/skillhub/migrations/*.sql`
47
+ > is not copied to `dist/` by `scripts/copy-templates.mjs`. This is a
48
+ > pre-existing build-config issue separate from D-018. Tracked as
49
+ > follow-up; tests still pass because the tests use vitest + tsx
50
+ > (which sees the source tree directly), but the published
51
+ > `peaks-loop@4.0.0-beta.13` will hit `no such table: bee_release` until
52
+ > a D-019 follow-up adds `.sql` to the copy-templates extensions.
53
+ >
54
+ > **Verdict**: 27/27 AC PASS (beta.12 verdict preserved; D-018 is
55
+ > architecture fix, no AC change). Old `<peaks-loop>/.peaks/state.db`
56
+ > discarded (beta phase, single user).
57
+
58
+ ## 4.0.0-beta.12 — 2026-07-16
59
+
60
+ ### Status: RELEASED (follow-up to `4.0.0-beta.11`; D-013 wrapper exit-code fix)
61
+
62
+ > **This CHANGELOG entry documents the D-013 follow-up release.
63
+ > Same source tree as `4.0.0-beta.11`; pure fix for the wrapper
64
+ > exit-code bug that previously caused `peaks <unknown>` and
65
+ > `peaks <unknown> --help` to exit 0 with the help banner.
66
+ >
67
+ > **Verdict**: All 4 regression paths now exit 1 with a
68
+ > `COMMAND_NOT_FOUND` JSON envelope. ice-cola baseline 27/27 PASS
69
+ > (AC3.9/AC3.10 now functionally correct).
8
70
 
9
71
  > **This CHANGELOG entry documents the SHIPPED 4.0.0-beta.11.
10
72
  > It is the post-implementation release of the 4.0.0-beta.10 contract
@@ -27,6 +27,7 @@
27
27
  import { writeFileSync, mkdirSync, readFileSync, existsSync, readdirSync, rmSync } from "node:fs";
28
28
  import { join, basename } from "node:path";
29
29
  import { SYSTEM_PATH_FORBIDDEN, assertNotSystemPath, resolveSegmentsDir, resolveStateDbPath, resolveBlobsDir, resolveUserBeesDir, resolveUserBeeDir, } from "../../services/sediment/pool-paths.js";
30
+ import { peaksHome } from "../../services/sop/sop-paths.js";
30
31
  import { writeBeeManifest } from "../../services/sediment/pool-write.js";
31
32
  import { readPool } from "../../services/sediment/pool-read.js";
32
33
  import { rebuildIndexFromFs } from "../../services/sediment/pool-rebuild-index.js";
@@ -636,7 +637,15 @@ export function registerSedimentCommands(program, io) {
636
637
  .command("sediment <args...>")
637
638
  .description("Sediment pool operations (LLM-coordinated; see peaks-maker skill)")
638
639
  .action(async (args) => {
639
- const home = process.env.HOME ?? process.env.USERPROFILE ?? process.cwd();
640
+ // D-018: peaks-loop's `state.db` (loop engineering + bee sediment pool)
641
+ // lives at PEAKS_HOME-based path, NOT under the project root. The
642
+ // original `process.env.HOME ?? USERPROFILE ?? process.cwd()` would
643
+ // resolve `home` to cwd when invoked from a project (e.g.
644
+ // `peaks skill sediment list` from peaks-loop/), producing
645
+ // `<project>/.peaks/state.db` — wrong. Per design contract
646
+ // (peaks-maker SKILL.md §22 + sop-paths.ts §29), the canonical
647
+ // home is `~/.peaks` (or PEAKS_HOME override for test isolation).
648
+ const home = peaksHome();
640
649
  const r = await runSediment(args, { home });
641
650
  // Delegate the JSON rendering AND the process.exitCode side-effect
642
651
  // to the shared CLI shim helper (Critical #1 fix). The library
package/dist/cli/index.js CHANGED
@@ -1,18 +1,70 @@
1
1
  import { CommanderError } from 'commander';
2
2
  import { createProgram } from './program.js';
3
3
  import { getErrorMessage } from '../shared/result.js';
4
+ // D-013 wrapper exit-code fix (PART 2): Commander's `--help` short-circuit
5
+ // fires BEFORE `commander.unknownCommand` is raised, so `peaks xxx --help`
6
+ // (where `xxx` is not a registered command) prints the help banner and
7
+ // exits 0 instead of 1. Detect this case BEFORE Commander runs by scanning
8
+ // `process.argv` for a non-option positional token. If present AND
9
+ // `--help` is also present, the user asked for help on an unknown command
10
+ // — emit `COMMAND_NOT_FOUND` envelope + exit 1.
11
+ //
12
+ // This pre-check is intentionally conservative: only fires when BOTH a
13
+ // positional token AND `--help`/`-h` are present. Bare `--help` (no
14
+ // positional) is legitimate help → exit 0. Positional without `--help`
15
+ // is handled by the root `.action()` in `program.ts`.
16
+ const argv = process.argv.slice(2);
17
+ const hasHelp = argv.some((arg) => arg === '--help' || arg === '-h');
18
+ const firstPositional = argv.find((arg) => !arg.startsWith('-'));
19
+ if (hasHelp && firstPositional !== undefined) {
20
+ // Defer the check by 0ms so Commander's own help handler runs first
21
+ // (it will print help text + try to exit 0). We then override.
22
+ setImmediate(() => {
23
+ console.error(JSON.stringify({
24
+ ok: false,
25
+ command: 'cli',
26
+ code: 'COMMAND_NOT_FOUND',
27
+ message: `Unknown command: ${firstPositional}. Run \`peaks --help\` for available commands.`,
28
+ data: { argv: firstPositional, combinedWithHelp: true },
29
+ warnings: [],
30
+ nextActions: ['Run `peaks --help` to list available commands.']
31
+ }, null, 2));
32
+ process.exit(1);
33
+ });
34
+ }
4
35
  createProgram().parseAsync(process.argv).catch((error) => {
5
36
  if (error instanceof CommanderError && error.code === 'commander.version') {
6
37
  return;
7
38
  }
8
- // exitOverride() also throws for help; suppress those — the text already went
9
- // to stdout/stderr, the error envelope confuses newcomers. --help is success
10
- // (exit 0); a bad command/option is an error (exit 1).
39
+ // D-013 wrapper exit-code fix: distinguish "bare `peaks --help` /
40
+ // `peaks help <cmd>`" (legitimate help, exit 0) from
41
+ // "`peaks <unknown> --help`" (user typed a non-existent command,
42
+ // even if they added --help, the command itself is invalid → exit 1).
43
+ // Commander only emits `commander.helpDisplayed` for the LEGITIMATE
44
+ // help path (bare `--help` or a registered subcommand's help). For
45
+ // unknown commands, Commander throws `commander.unknownCommand`
46
+ // BEFORE the help text is rendered, so we route that case to the
47
+ // unknownCommand branch below. This preserves the historical behavior
48
+ // for valid help while fixing the unknown-command exit code.
11
49
  if (error instanceof CommanderError) {
12
50
  if (error.code === 'commander.help' || error.code === 'commander.helpDisplayed') {
13
51
  return;
14
52
  }
15
53
  if (error.code === 'commander.missingArgument' || error.code === 'commander.unknownCommand' || error.code === 'commander.unknownOption') {
54
+ // Emit a `COMMAND_NOT_FOUND` JSON envelope for the unknown-command
55
+ // path. The error text already went to stderr via Commander's
56
+ // default handler; we add a structured envelope for LLM-side
57
+ // consumers (Human-NL-Choice-Only: don't tell the human to type
58
+ // a CLI verb — say what the LLM can coordinate).
59
+ console.error(JSON.stringify({
60
+ ok: false,
61
+ command: 'cli',
62
+ code: 'COMMAND_NOT_FOUND',
63
+ message: getErrorMessage(error),
64
+ data: {},
65
+ warnings: [],
66
+ nextActions: ['Run `peaks --help` to list available commands.']
67
+ }, null, 2));
16
68
  process.exitCode = 1;
17
69
  return;
18
70
  }
@@ -207,6 +207,40 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
207
207
  }
208
208
  })
209
209
  .action(() => {
210
+ // D-013 wrapper exit-code fix: when the user typed a command token but
211
+ // it didn't match any registered subcommand, exit non-zero with a JSON
212
+ // `COMMAND_NOT_FOUND` envelope. Without this check, Commander's root
213
+ // `.action()` runs whenever no subcommand matches (including unknown
214
+ // commands) and prints the help banner with exit 0 — which violates
215
+ // the PRD AC3.9/AC3.10 contract for `peaks agent run/list` and any
216
+ // other deleted/hidden command.
217
+ //
218
+ // Detection: use `program.args` (Commander's parsed argv) for the
219
+ // first non-option token. If it's present and Commander didn't route
220
+ // to a subcommand (we got here, so it didn't), the user typed an
221
+ // unknown command. Exit 1. `program.args` works correctly under both
222
+ // `parseAsync(['node', 'peaks', ...])` (real CLI) and direct in-process
223
+ // calls (vitest integration tests), unlike `process.argv` which is
224
+ // the test runner's argv.
225
+ //
226
+ // --help and --version bypass this path entirely (handled below).
227
+ const firstNonOption = program.args.find((arg) => !arg.startsWith('-'));
228
+ if (firstNonOption !== undefined) {
229
+ // Commander reached this `.action()` despite a positional token,
230
+ // which means the token was NOT routed to any subcommand. This is
231
+ // the unknown-command path. Emit a JSON envelope + exit 1.
232
+ io.stdout(JSON.stringify({
233
+ ok: false,
234
+ command: 'cli',
235
+ code: 'COMMAND_NOT_FOUND',
236
+ message: `Unknown command: ${firstNonOption}. Run \`peaks --help\` for available commands.`,
237
+ data: { argv: firstNonOption },
238
+ warnings: [],
239
+ nextActions: ['Run `peaks --help` to list available commands.']
240
+ }, null, 2));
241
+ process.exitCode = 1;
242
+ return;
243
+ }
210
244
  const opts = program.opts();
211
245
  if (opts.V || opts.version) {
212
246
  // AC1: write the peaks-loop start log line BEFORE printing the
@@ -0,0 +1,71 @@
1
+ -- 001-initial.sql: spec §3.3.1 — decomposed 6-table schema.
2
+ -- Manifest fields are first-class columns. Files are content-addressed in `blobs/` sidecar (not in this DB).
3
+
4
+ CREATE TABLE IF NOT EXISTS bee_release (
5
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6
+ bee_name TEXT NOT NULL,
7
+ version TEXT NOT NULL,
8
+ source TEXT NOT NULL CHECK (source IN ('user')),
9
+ archived_at TEXT NOT NULL,
10
+ archived_by TEXT NOT NULL,
11
+ user_intent_raw TEXT,
12
+ description TEXT,
13
+ parent_version TEXT,
14
+ changelog TEXT,
15
+ UNIQUE(bee_name, version)
16
+ );
17
+
18
+ CREATE TABLE IF NOT EXISTS bee_release_pointer (
19
+ bee_name TEXT PRIMARY KEY,
20
+ latest_version TEXT NOT NULL,
21
+ released_at TEXT NOT NULL
22
+ );
23
+
24
+ CREATE TABLE IF NOT EXISTS bee_manifest (
25
+ release_id INTEGER PRIMARY KEY REFERENCES bee_release(id) ON DELETE CASCADE,
26
+ schema_version TEXT NOT NULL,
27
+ description TEXT NOT NULL,
28
+ segments_json TEXT NOT NULL, -- segment NAMES only; small JSON array
29
+ entrypoint_preamble TEXT,
30
+ promotion TEXT NOT NULL,
31
+ min_cycles INTEGER,
32
+ requires_human INTEGER NOT NULL,
33
+ requires_smoke INTEGER NOT NULL,
34
+ retire_on_misses INTEGER
35
+ );
36
+
37
+ CREATE TABLE IF NOT EXISTS bee_segment_ref (
38
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
39
+ release_id INTEGER NOT NULL REFERENCES bee_release(id) ON DELETE CASCADE,
40
+ segment_name TEXT NOT NULL,
41
+ inputs_json TEXT,
42
+ outputs_json TEXT,
43
+ side_effects TEXT,
44
+ UNIQUE(release_id, segment_name)
45
+ );
46
+
47
+ CREATE TABLE IF NOT EXISTS bee_file (
48
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
49
+ release_id INTEGER NOT NULL REFERENCES bee_release(id) ON DELETE CASCADE,
50
+ owner_kind TEXT NOT NULL CHECK (owner_kind IN ('bee','segment')),
51
+ owner_name TEXT NOT NULL,
52
+ path TEXT NOT NULL,
53
+ kind TEXT NOT NULL CHECK (kind IN ('markdown','script','reference','binary','other')),
54
+ size_bytes INTEGER NOT NULL,
55
+ sha256 TEXT NOT NULL,
56
+ blob_path TEXT NOT NULL,
57
+ UNIQUE(release_id, owner_kind, owner_name, path)
58
+ );
59
+
60
+ CREATE TABLE IF NOT EXISTS bee_change (
61
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
62
+ release_id INTEGER NOT NULL REFERENCES bee_release(id) ON DELETE CASCADE,
63
+ change_kind TEXT NOT NULL,
64
+ target_kind TEXT NOT NULL,
65
+ target_name TEXT NOT NULL,
66
+ detail TEXT
67
+ );
68
+
69
+ CREATE INDEX IF NOT EXISTS idx_bee_release_name_archived_at ON bee_release(bee_name, archived_at DESC);
70
+ CREATE INDEX IF NOT EXISTS idx_bee_segment_ref_release ON bee_segment_ref(release_id);
71
+ CREATE INDEX IF NOT EXISTS idx_bee_file_release_owner ON bee_file(release_id, owner_kind, owner_name);
@@ -0,0 +1,32 @@
1
+ -- 002-loop-release.sql — M1 / spec §4.1 / §7.2
2
+ -- Adds the `loop_release` table for the Loop Engineering Asset layer.
3
+ -- Non-breaking: this migration does NOT touch any 4.x `bee_release`
4
+ -- column. The new table lives alongside the existing 6-table skillhub
5
+ -- schema (see 001-initial.sql). Indexes:
6
+ -- - lifecycle_status: for `peaks loop list --status <status>` filters.
7
+ -- - scenario: for `peaks loop search --query <q>` (LIKE-based
8
+ -- full-text in M1; FTS5 promotion deferred to a
9
+ -- later slice).
10
+ -- Schema versioning: every new table carries `schema_version`.
11
+
12
+ CREATE TABLE IF NOT EXISTS loop_release (
13
+ id TEXT PRIMARY KEY,
14
+ name TEXT NOT NULL,
15
+ scenario TEXT NOT NULL,
16
+ trigger_policy TEXT NOT NULL,
17
+ success_criteria_json TEXT NOT NULL, -- JSON array of strings
18
+ interaction_policy TEXT NOT NULL,
19
+ feedback_policy TEXT NOT NULL,
20
+ evolution_policy TEXT NOT NULL,
21
+ evaluator_policy_json TEXT NOT NULL, -- JSON array of strings
22
+ linked_bees_json TEXT NOT NULL DEFAULT '[]',
23
+ run_history_json TEXT NOT NULL DEFAULT '[]',
24
+ crystallization_evidence_json TEXT NOT NULL DEFAULT '[]',
25
+ lifecycle_status TEXT NOT NULL CHECK (lifecycle_status IN ('candidate','stable','retired')),
26
+ version TEXT NOT NULL,
27
+ schema_version TEXT NOT NULL CHECK (schema_version = 'peaks.loop/1'),
28
+ archived_at TEXT NOT NULL
29
+ );
30
+
31
+ CREATE INDEX IF NOT EXISTS idx_loop_release_lifecycle_status ON loop_release(lifecycle_status);
32
+ CREATE INDEX IF NOT EXISTS idx_loop_release_scenario ON loop_release(scenario);
@@ -0,0 +1,35 @@
1
+ -- 003-loop-bee-relation.sql — M2 / spec §4.6 / §7.2
2
+ -- Adds the `loop_bee_relation` table for the Loop↔Bee coupling layer.
3
+ -- Non-breaking: this migration does NOT touch any 4.x `bee_release`
4
+ -- column. The new table lives alongside the existing skillhub schema
5
+ -- (see 001-initial.sql, 002-loop-release.sql).
6
+ --
7
+ -- Constraints:
8
+ -- - FK to loop_release(id) and bee_release(id) — referential integrity.
9
+ -- - UNIQUE(loop_release_id, bee_release_id) — no duplicate relations.
10
+ -- - Partial UNIQUE index WHERE role='main' — at most one main bee per
11
+ -- loop at the storage layer (defense in depth; the service layer
12
+ -- adds a friendlier error path).
13
+ -- - role CHECK constraint enforces the four-value union.
14
+ -- - schema_version column carries 'peaks.loop-bee-relation/1'.
15
+ --
16
+ -- The "no relation to a retired loop" rule is NOT a DB-level constraint
17
+ -- (cross-table policy; needs richer error reporting). It is enforced in
18
+ -- the LoopBeeRelationService layer.
19
+
20
+ CREATE TABLE IF NOT EXISTS loop_bee_relation (
21
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
22
+ loop_release_id TEXT NOT NULL REFERENCES loop_release(id) ON DELETE CASCADE,
23
+ bee_release_id INTEGER NOT NULL REFERENCES bee_release(id) ON DELETE CASCADE,
24
+ role TEXT NOT NULL CHECK (role IN ('main','supporting','candidate','retired')),
25
+ reason TEXT NOT NULL,
26
+ schema_version TEXT NOT NULL CHECK (schema_version = 'peaks.loop-bee-relation/1'),
27
+ created_at TEXT NOT NULL,
28
+ UNIQUE(loop_release_id, bee_release_id)
29
+ );
30
+
31
+ CREATE INDEX IF NOT EXISTS idx_loop_bee_relation_loop_id ON loop_bee_relation(loop_release_id);
32
+ CREATE INDEX IF NOT EXISTS idx_loop_bee_relation_bee_id ON loop_bee_relation(bee_release_id);
33
+ CREATE INDEX IF NOT EXISTS idx_loop_bee_relation_loop_role ON loop_bee_relation(loop_release_id, role);
34
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_loop_bee_relation_one_main_per_loop
35
+ ON loop_bee_relation(loop_release_id) WHERE role = 'main';
@@ -0,0 +1,65 @@
1
+ -- 004-loop-bee-extension.sql — M3 / spec §4.1 / §4.2 / §7A
2
+ -- Adds the share / desktop extension fields to `loop_release` and
3
+ -- `bee_release`. The migration is non-breaking: every new column has
4
+ -- a DEFAULT, so existing 4.x rows continue to read cleanly with the
5
+ -- spec values applied. M3 writes the SCHEMA only — `shareable=false`
6
+ -- is NOT enforced anywhere yet; full export / desktop enforcement
7
+ -- lands in M7 (see plan m3-bee-release-extension.md).
8
+ --
9
+ -- AC-3: this migration does NOT modify any pre-existing `bee_release`
10
+ -- column. The only changes to `bee_release` are two new columns
11
+ -- appended at the end of the table:
12
+ --
13
+ -- - shareable BOOLEAN (default true)
14
+ -- - desktop_visible BOOLEAN (default true)
15
+ --
16
+ -- All other 4.x `bee_release` columns keep their definitions from
17
+ -- 001-initial.sql.
18
+ --
19
+ -- For `loop_release` we add four new columns:
20
+ --
21
+ -- - shareable BOOLEAN (default true)
22
+ -- - share_excluded_paths TEXT (default '[]' — JSON array)
23
+ -- - desktop_visible BOOLEAN (default true)
24
+ -- - export_bundle_format TEXT (default 'peaks.bundle/1')
25
+ --
26
+ -- Notes on the column choices:
27
+ -- - share_excluded_paths is TEXT (JSON-encoded array). Matches the
28
+ -- existing `loop_release` style (success_criteria_json, etc.);
29
+ -- parsing happens at the store / Zod boundary, not at SQLite.
30
+ -- - export_bundle_format is a TEXT constant. The CHECK constraint
31
+ -- below pins the column to 'peaks.bundle/1' — there is no other
32
+ -- valid value in this schema version. M7 may relax this when a
33
+ -- v2 format lands; until then the constant is hard-pinned.
34
+ -- - DEFAULT 1 / 0 covers both columns for SQLite (no native
35
+ -- BOOLEAN — the JS layer normalizes to boolean on read).
36
+ -- - The CHECK constraints on `shareable` and `desktop_visible`
37
+ -- accept only 0/1 (SQLite's BOOLEAN idiom). The Zod schema on
38
+ -- read coerces 0/1 to true/false.
39
+ --
40
+ -- Defense in depth: defaults are set both here (storage) and in the
41
+ -- Zod schema (parse boundary) so an in-memory object created from a
42
+ -- pre-M3 snapshot still parses after the migration runs.
43
+
44
+ ALTER TABLE loop_release
45
+ ADD COLUMN shareable INTEGER NOT NULL DEFAULT 1
46
+ CHECK (shareable IN (0, 1));
47
+
48
+ ALTER TABLE loop_release
49
+ ADD COLUMN share_excluded_paths TEXT NOT NULL DEFAULT '[]';
50
+
51
+ ALTER TABLE loop_release
52
+ ADD COLUMN desktop_visible INTEGER NOT NULL DEFAULT 1
53
+ CHECK (desktop_visible IN (0, 1));
54
+
55
+ ALTER TABLE loop_release
56
+ ADD COLUMN export_bundle_format TEXT NOT NULL DEFAULT 'peaks.bundle/1'
57
+ CHECK (export_bundle_format = 'peaks.bundle/1');
58
+
59
+ ALTER TABLE bee_release
60
+ ADD COLUMN shareable INTEGER NOT NULL DEFAULT 1
61
+ CHECK (shareable IN (0, 1));
62
+
63
+ ALTER TABLE bee_release
64
+ ADD COLUMN desktop_visible INTEGER NOT NULL DEFAULT 1
65
+ CHECK (desktop_visible IN (0, 1));
@@ -0,0 +1,81 @@
1
+ -- 005-evolution-evaluation.sql — M4 / spec §4.4 / §6
2
+ -- Adds the `evolution_evaluation` table for the Darwin-style ratchet
3
+ -- evaluation layer. Non-breaking: this migration does NOT touch any
4
+ -- pre-existing table. The new table lives alongside the existing
5
+ -- skillhub schema (see 001-initial.sql, 002-loop-release.sql,
6
+ -- 003-loop-bee-relation.sql, 004-loop-bee-extension.sql).
7
+ --
8
+ -- The table stores the durable evidence of an evolution round:
9
+ -- - target_kind / target_release_id / single_optimization_dimension
10
+ -- (AC-8: single editable asset + single dimension per round).
11
+ -- - before_score / after_score / score_delta_min / score_delta
12
+ -- (AC-11: numeric delta; promotion blocked when delta < delta_min).
13
+ -- - author_id / evaluator_id / skeptic_id
14
+ -- (AC-10: author != scorer; the regression skeptic is a SEPARATE
15
+ -- agent, not the same as the independent evaluator).
16
+ -- - verdict (keep / revert / needs-user-decision).
17
+ -- - user_confirmation_pointer / brief_pointer
18
+ -- (spec §4.4: pointers, not inlined big JSON BLOBs).
19
+ --
20
+ -- Schema versioning: every new table carries `schema_version`
21
+ -- pinned to the literal `peaks.evolution/1` via CHECK constraint.
22
+ --
23
+ -- All JSON-shaped payloads (diff, brief_pointer, source_traces_json)
24
+ -- are stored as TEXT and parsed at the store / Zod boundary — same
25
+ -- convention as the existing tables; no big JSON BLOB at the SQLite
26
+ -- layer.
27
+
28
+ CREATE TABLE IF NOT EXISTS evolution_evaluation (
29
+ id TEXT PRIMARY KEY,
30
+ target_kind TEXT NOT NULL CHECK (target_kind IN ('loop','bee','policy','gate','evaluator')),
31
+ target_release_id TEXT NOT NULL,
32
+ -- The M4 spec requires exactly one optimization dimension per round.
33
+ -- The TEXT column carries the dimension name; the service layer
34
+ -- (EvolutionService) enforces `dimensions.length === 1` on input.
35
+ -- A JSON array is used here only so future schema versions can
36
+ -- relax the single-dimension rule without a column-rename
37
+ -- migration; for now the array is always exactly length 1.
38
+ optimization_dimensions_json TEXT NOT NULL,
39
+ -- AC-8: target_count must be exactly 1. Stored as INTEGER for
40
+ -- explicit service-layer enforcement; CHECK pins it to 1 in this
41
+ -- schema version (a future multi-target table would relax this).
42
+ target_count INTEGER NOT NULL DEFAULT 1 CHECK (target_count = 1),
43
+ before_snapshot_json TEXT NOT NULL DEFAULT '{}',
44
+ after_snapshot_json TEXT NOT NULL DEFAULT '{}',
45
+ diff_json TEXT NOT NULL DEFAULT '{}',
46
+ before_score REAL NOT NULL,
47
+ after_score REAL NOT NULL,
48
+ score_delta_min REAL NOT NULL DEFAULT 1.0 CHECK (score_delta_min >= 0),
49
+ score_delta REAL NOT NULL,
50
+ author_id TEXT NOT NULL,
51
+ evaluator_id TEXT NOT NULL,
52
+ skeptic_id TEXT NOT NULL,
53
+ -- Verdict is the FINAL aggregation result; `needs-user-decision`
54
+ -- is the default after the scoring + skeptic step.
55
+ verdict TEXT NOT NULL CHECK (verdict IN ('keep','revert','needs-user-decision')),
56
+ -- Pointer columns (NOT inlined JSON): user_confirmation_pointer and
57
+ -- brief_pointer are spec §4.4 fields. We keep them as TEXT paths /
58
+ -- ids so the table stays small and queryable.
59
+ user_confirmation_pointer TEXT,
60
+ brief_pointer TEXT,
61
+ -- Audit columns: rubric + red_lines + source_traces are
62
+ -- JSON-encoded TEXT — same convention as the other tables.
63
+ rubric_json TEXT NOT NULL DEFAULT '{}',
64
+ red_lines_json TEXT NOT NULL DEFAULT '[]',
65
+ source_traces_json TEXT NOT NULL DEFAULT '[]',
66
+ schema_version TEXT NOT NULL CHECK (schema_version = 'peaks.evolution/1'),
67
+ created_at TEXT NOT NULL,
68
+ -- Defense in depth: the author MUST NOT equal the evaluator.
69
+ -- Cross-column CHECK rejected by older SQLite versions; we let the
70
+ -- service layer enforce this so the constraint stays portable.
71
+ CHECK (length(id) > 0)
72
+ );
73
+
74
+ CREATE INDEX IF NOT EXISTS idx_evolution_evaluation_target
75
+ ON evolution_evaluation(target_kind, target_release_id);
76
+ CREATE INDEX IF NOT EXISTS idx_evolution_evaluation_verdict
77
+ ON evolution_evaluation(verdict);
78
+ CREATE INDEX IF NOT EXISTS idx_evolution_evaluation_author
79
+ ON evolution_evaluation(author_id);
80
+ CREATE INDEX IF NOT EXISTS idx_evolution_evaluation_evaluator
81
+ ON evolution_evaluation(evaluator_id);
@@ -0,0 +1,92 @@
1
+ -- 006-crystallization-event.sql — M5 / spec §4.5 / §4.7 / §5 / §7.2
2
+ -- Adds the `crystallization_event` table for the post-run
3
+ -- crystallization layer. Non-breaking: this migration does NOT
4
+ -- touch any pre-existing table. The new table lives alongside the
5
+ -- existing skillhub schema (see 001-initial.sql, 002-loop-release.sql,
6
+ -- 003-loop-bee-relation.sql, 004-loop-bee-extension.sql,
7
+ -- 005-evolution-evaluation.sql).
8
+ --
9
+ -- The table stores the durable evidence of a crystallization event:
10
+ -- - trigger (user_explicit | llm_suggested |
11
+ -- success_default_prompt |
12
+ -- similar_task_recurrence)
13
+ -- - evidence_brief (JSON: 4 sections)
14
+ -- * what_happened (1-2 sentence factual account)
15
+ -- * why_it_matters (1-2 sentence explanation)
16
+ -- * what_learned (1-2 sentence learning)
17
+ -- * what_action (1 sentence recommended action)
18
+ -- - source_trace_pointers (JSON array of workflow trace ids)
19
+ -- - evaluator_summary (short NL summary from scorers)
20
+ -- - user_decision_summary (NL account of the user choice)
21
+ -- - created_loop_release_id (FK to loop_release, optional)
22
+ -- - updated_loop_release_id (FK to loop_release, optional)
23
+ -- - created_bee_release_id (FK to bee_release, optional)
24
+ -- - updated_bee_release_id (FK to bee_release, optional)
25
+ -- - lifecycle_status (candidate | stable | retired;
26
+ -- the EVENT's status, distinct
27
+ -- from the loop_release status).
28
+ --
29
+ -- The four-section brief (spec §4.7 / §10 RL-7) is required at the
30
+ -- service layer; the DB stores whatever JSON the service persists. A
31
+ -- schema-version bump can introduce a CHECK shape constraint on the
32
+ -- brief in a future migration; the service layer is the authoritative
33
+ -- guard today.
34
+ --
35
+ -- Schema versioning: every new table carries `schema_version` pinned
36
+ -- to the literal `peaks.crystallization/1` via CHECK constraint.
37
+ --
38
+ -- All JSON-shaped payloads (brief, source_trace_pointers, bullets)
39
+ -- are stored as TEXT and parsed at the store / Zod boundary —
40
+ -- same convention as the existing tables; no big JSON BLOB at the
41
+ -- SQLite layer.
42
+
43
+ CREATE TABLE IF NOT EXISTS crystallization_event (
44
+ id TEXT PRIMARY KEY,
45
+ trigger TEXT NOT NULL CHECK (trigger IN (
46
+ 'user_explicit',
47
+ 'llm_suggested',
48
+ 'success_default_prompt',
49
+ 'similar_task_recurrence'
50
+ )),
51
+ -- The 4-section brief. The shape guard (all four sections present)
52
+ -- is enforced at the service layer (CrystallizationService); the
53
+ -- DB stores whatever the service persists. See spec §4.7 / §10 RL-7.
54
+ evidence_brief_json TEXT NOT NULL,
55
+ evidence_bullets_json TEXT NOT NULL DEFAULT '[]',
56
+ source_trace_pointers_json TEXT NOT NULL DEFAULT '[]',
57
+ evaluator_summary TEXT NOT NULL DEFAULT '',
58
+ user_decision_summary TEXT NOT NULL DEFAULT '',
59
+ created_loop_release_id TEXT REFERENCES loop_release(id) ON DELETE SET NULL,
60
+ updated_loop_release_id TEXT REFERENCES loop_release(id) ON DELETE SET NULL,
61
+ created_bee_release_id INTEGER REFERENCES bee_release(id) ON DELETE SET NULL,
62
+ updated_bee_release_id INTEGER REFERENCES bee_release(id) ON DELETE SET NULL,
63
+ -- The EVENT's lifecycle (independent of any created/updated asset).
64
+ -- 'candidate' after crystallization; 'stable' after user-confirmed
65
+ -- promotion; 'retired' after explicit dispose. Mirrors the loop
66
+ -- status union (spec §4.1) without a 1:1 identity — these are
67
+ -- orthogonal lifecycles.
68
+ lifecycle_status TEXT NOT NULL DEFAULT 'candidate'
69
+ CHECK (lifecycle_status IN ('candidate','stable','retired')),
70
+ schema_version TEXT NOT NULL CHECK (schema_version = 'peaks.crystallization/1'),
71
+ created_at TEXT NOT NULL,
72
+ CHECK (length(id) > 0),
73
+ -- Defense in depth on the brief shape: at least one of the four
74
+ -- keys must be present in the JSON object. SQLite JSON1 (>= 3.38)
75
+ -- supports json_extract; we keep the check loose (length > 2 to
76
+ -- rule out '{}') so older SQLite still accepts the row, and rely
77
+ -- on the service-layer guard for the strict 4-section rule.
78
+ CHECK (length(evidence_brief_json) >= 2)
79
+ );
80
+
81
+ CREATE INDEX IF NOT EXISTS idx_crystallization_event_created_loop
82
+ ON crystallization_event(created_loop_release_id);
83
+ CREATE INDEX IF NOT EXISTS idx_crystallization_event_updated_loop
84
+ ON crystallization_event(updated_loop_release_id);
85
+ CREATE INDEX IF NOT EXISTS idx_crystallization_event_created_bee
86
+ ON crystallization_event(created_bee_release_id);
87
+ CREATE INDEX IF NOT EXISTS idx_crystallization_event_updated_bee
88
+ ON crystallization_event(updated_bee_release_id);
89
+ CREATE INDEX IF NOT EXISTS idx_crystallization_event_lifecycle
90
+ ON crystallization_event(lifecycle_status);
91
+ CREATE INDEX IF NOT EXISTS idx_crystallization_event_created_at
92
+ ON crystallization_event(created_at);
@@ -1 +1 @@
1
- export declare const CLI_VERSION = "4.0.0-beta.11";
1
+ export declare const CLI_VERSION = "4.0.0-beta.14";
@@ -1 +1 @@
1
- export const CLI_VERSION = "4.0.0-beta.11";
1
+ export const CLI_VERSION = "4.0.0-beta.14";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peaks-loop",
3
- "version": "4.0.0-beta.11",
3
+ "version": "4.0.0-beta.14",
4
4
  "description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
5
5
  "author": "SquabbyZ",
6
6
  "license": "MIT",
@@ -26,6 +26,7 @@
26
26
  "dist/**/*.js",
27
27
  "dist/**/*.d.ts",
28
28
  "dist/**/*.md",
29
+ "dist/**/*.sql",
29
30
  "scripts/clean-dist.mjs",
30
31
  "scripts/copy-templates.mjs",
31
32
  "scripts/sync-version.mjs",
@@ -9,6 +9,15 @@
9
9
  * therefore only get the dynamically-generated `project-scan.md` and
10
10
  * the 4 audit/business templates would silently be missing.
11
11
  *
12
+ * D-019 (2026-07-16): also copy the SkillHub SQLite migrations
13
+ * (`src/services/skillhub/migrations/*.sql`) into dist/. Without this,
14
+ * the published `peaks-loop` tarball ships an empty
15
+ * `dist/services/skillhub/migrations/` and `openStateDb()` opens a
16
+ * schema-less database, breaking any `peaks skill sediment <verb>`
17
+ * on a fresh consumer install. Vitest tests still pass because they
18
+ * use tsx + the source tree directly, but downstream npm consumers
19
+ * see `no such table: bee_release` on the very first command.
20
+ *
12
21
  * Idempotent: safe to run multiple times. Run after `tsc` in `build`.
13
22
  */
14
23
  import { copyFileSync, mkdirSync, readdirSync, statSync } from 'node:fs';
@@ -44,6 +53,13 @@ const targets = [
44
53
  src: join(packageRoot, 'src/services/workspace/templates/project-scan'),
45
54
  dest: join(packageRoot, 'dist/services/workspace/templates/project-scan'),
46
55
  extensions: ['.md']
56
+ },
57
+ {
58
+ // D-019: SkillHub SQLite migrations must reach the published tarball
59
+ // so `openStateDb()` can apply the schema on first run.
60
+ src: join(packageRoot, 'src/services/skillhub/migrations'),
61
+ dest: join(packageRoot, 'dist/services/skillhub/migrations'),
62
+ extensions: ['.sql']
47
63
  }
48
64
  ];
49
65