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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## 4.0.0-beta.4 — 2026-07-08
|
|
6
|
+
|
|
7
|
+
Loop Engineering crystallization is now the product surface. The 4.0.0-beta.3 line shipped the framework; this release ships the post-run crystallization engine, the Darwin-style ratchet, the bundle share / desktop extension surface, and the karpathy-engineered red-line set that locks them together. Every durable-change entry is gated on a real, completed run; every evolution round is gated on an independent-context evaluation.
|
|
8
|
+
|
|
9
|
+
### Added — Loop Engineering Asset layer (§4.1, AC-1/AC-2/AC-3)
|
|
10
|
+
|
|
11
|
+
- **`loop_release` table** — `src/services/skillhub/migrations/002-loop-release.sql`. Schema version pinned to `peaks.loop/1` via CHECK constraint. Indexes on `lifecycle_status` and `scenario`. Non-breaking: 4.x `bee_release` rows continue to read.
|
|
12
|
+
- **`loop_bee_relation` table** — `src/services/skillhub/migrations/003-loop-bee-relation.sql`. Schema version `peaks.loop-bee-relation/1`. Roles: `main` / `supporting` / `candidate` / `retired`. Partial UNIQUE index `WHERE role='main'` enforces at most one main bee per loop at the storage layer (defense in depth; the service layer also enforces it).
|
|
13
|
+
- **Schema for the four-loop + dual-asset model** — Zod schemas in `src/services/loop/loop-release-types.ts` and `src/services/loop/loop-bee-relation-types.ts` mirror the §4.1 / §4.6 row shapes and refuse to parse rows whose `schema_version` is not the fixed literal.
|
|
14
|
+
|
|
15
|
+
### Added — Post-run crystallization flow (§5, AC-4 / AC-5 / AC-6 / AC-7)
|
|
16
|
+
|
|
17
|
+
- **`crystallization_event` table** — `src/services/skillhub/migrations/006-crystallization-event.sql`. Schema version `peaks.crystallization/1`. Carries the 4-section evidence brief inline; FKs to optional created/updated loop_release / bee_release.
|
|
18
|
+
- **`CrystallizationService`** — `src/services/crystallization/crystallization-service.ts`. Pre-run gate: `task_status='completed'` AND `gates_passed=true` AND `evidence_collected=true` (Zod literal enforcement + service-layer re-assertion). Atomic single-transaction write of loop_release + bee_release header + bee_manifest + loop_bee_relation(main) + crystallization_event(brief inline).
|
|
19
|
+
- **`peaks asset crystallize`** CLI — `src/cli/commands/asset-commands.ts`. Required options: `--brief-what-happened`, `--brief-why-it-matters`, `--brief-what-learned`, `--brief-what-action`. A partial brief is rejected with `MISSING_BRIEF_SECTION` and exit 1 (RL-7).
|
|
20
|
+
- **`peaks asset dispose` / `peaks asset status`** — cross-asset dispose (trace_only / retain / destroy) and lifecycle status dashboard.
|
|
21
|
+
|
|
22
|
+
### Added — Darwin-style ratchet (§6, AC-8 / AC-9 / AC-10 / AC-11)
|
|
23
|
+
|
|
24
|
+
- **`evolution_evaluation` table** — `src/services/skillhub/migrations/005-evolution-evaluation.sql`. Schema version `peaks.evolution/1`. Carries `target_kind`, `target_release_id`, `optimization_dimensions_json` (length-1 enforced at service layer), `target_count=1` (CHECK), `author_id`, `evaluator_id`, `skeptic_id` (three independent agents), `verdict` ∈ {`keep`, `revert`, `needs-user-decision`}, `user_confirmation_pointer`, `brief_pointer`, and the four-section brief projection.
|
|
25
|
+
- **`EvolutionService`** — `src/services/evolution/evolution-service.ts`. Hard rules: single object (AC-8) + single optimization dimension (AC-8) + author ≠ evaluator ≠ skeptic (AC-10/AC-12/AC-14) + score_delta >= score_delta_min for `keep` (AC-11, default 1.0) + user_confirmation_pointer required for `keep` (AC-15).
|
|
26
|
+
- **`peaks evolution propose / evaluate / revert / mark-keep / status`** CLI — `src/cli/commands/evolution-commands.ts`. Each error path emits a stable wire-format code (`EVOLUTION_MULTI_OBJECT` / `EVOLUTION_MULTI_DIMENSION` / `EVOLUTION_SELF_SCORE` / `EVOLUTION_DELTA_BELOW_THRESHOLD` / `EVOLUTION_MISSING_USER_CONFIRMATION`).
|
|
27
|
+
- **Independent-evaluator runner** — `src/services/evolution/independent-evaluator-runner.ts`. Frozen `EvaluationPackage` containing only `target_kind / target_release_id / optimization_dimension / before_snapshot / after_snapshot / diff / rubric / red_lines / source_traces` — no author session, no author reasoning, no recommendation framing (AC-12 / AC-13).
|
|
28
|
+
- **Regression-skeptic runner** — `src/services/evolution/regression-skeptic-runner.ts`. A SEPARATE agent that emits `driftRisks / overfitRisks / safetyRegressionRisks` plus an optional `blocker` (which forces `verdict='revert'`).
|
|
29
|
+
|
|
30
|
+
### Added — Karpathy-engineered red-line set (AC-21 / AC-22 / AC-23)
|
|
31
|
+
|
|
32
|
+
- **`.peaks/standards/loop-engineering-guidelines.md`** — the single source of truth for the 10 red lines RL-0..RL-9, each in the four-section karpathy form (Failure modes / Rewrite / Self-check / Out-of-scope). Co-equal karpathy × darwin layers (RL-0).
|
|
33
|
+
- **`peaks standards lint --category loop-engineering`** — `src/services/standards/loop-engineering-lint.ts`. Parses the guideline file and asserts every red line has all four sections, fails closed otherwise.
|
|
34
|
+
- **`peaks skill lint --category loop-engineering-readiness --path <skill-dir>`** — `src/services/standards/loop-engineering-readiness-lint.ts` + `src/cli/commands/skill-loop-engineering-readiness-commands.ts`. Asserts a peaks-* SKILL.md (a) references `.peaks/standards/loop-engineering-guidelines.md`, (b) does not introduce a CLI verb the user is meant to type, (c) does not introduce a JSON / manifest hand-authoring surface. Alias verb: `peaks skill ready --category loop-engineering-readiness --path <skill-dir>`.
|
|
35
|
+
- **Unit guard** — `tests/unit/standards/loop-engineering-guidelines.test.ts` enforces the four-section shape for every red line in the file.
|
|
36
|
+
- **peaks-code domain boundary (RL-8)** — `skills/peaks-code/SKILL.md` self-declares as the code-domain long-task loop engineering orchestrator and is NOT a general-purpose orchestrator. Cross-domain peaks-* skills (peaks-content, peaks-issue-fix-orchestrator) import the shared guideline file and pass the readiness lint.
|
|
37
|
+
|
|
38
|
+
### Added — Bundle share + desktop extension surface (§7A, AC-24 / AC-25 / AC-26)
|
|
39
|
+
|
|
40
|
+
- **Share / desktop fields** — `src/services/skillhub/migrations/004-loop-bee-extension.sql`. Adds `shareable / share_excluded_paths / desktop_visible / export_bundle_format` to `loop_release`, and `shareable / desktop_visible` to `bee_release`. `export_bundle_format` is pinned to `peaks.bundle/1` via CHECK constraint. Non-breaking: every new column has a DEFAULT.
|
|
41
|
+
- **Bundle writer** — `src/services/share/bundle-writer.ts`. Writes a `peaks.bundle/1` tar.gz with `manifest.json`, `relations.json`, `evidence_briefs/*.json`, content-addressed `blobs/<sha256>`, and `EVALUATION_REQUIRED.md`. Hard block: `shareable=false` throws `BundleNotShareableError(SHARE_BUNDLE_ERROR_CODES.NOT_SHAREABLE)` BEFORE any tarball work, for both loop and bee kinds.
|
|
42
|
+
- **Bundle reader** — `src/services/share/bundle-reader.ts`. Hard blocks: `format_version_major != 1` is rejected (AC-25); `schema_versions` mismatch is rejected; the imported release ALWAYS lands as `candidate` (AC-25 / §10 RL-9). The bundle carries an `EVALUATION_REQUIRED.md` marker telling the receiver to run an independent evaluation before any durable change.
|
|
43
|
+
- **`peaks loop export` / `peaks loop import`** — `src/cli/commands/loop-commands.ts`. Existing 14.x `peaks loop *` subcommands are preserved; M7 only ADDS export / import.
|
|
44
|
+
- **`peaks bee export` / `peaks bee import`** — `src/cli/commands/share-commands.ts`. Same hard-block semantics.
|
|
45
|
+
- **Receiver-side integration test** — `tests/integration/share-bundle-roundtrip.test.ts` asserts the round-trip lands as `candidate` (AC-25), that `shareable=false` blocks the export at the CLI layer, and that without an `evolution_evaluation` row the receiver has no path to `stable` (AC-26).
|
|
46
|
+
|
|
47
|
+
### Added — SkillHub expansion (5 new tables / non-breaking)
|
|
48
|
+
|
|
49
|
+
The 6+ relation table layout is preserved. 5 new tables added under `.peaks/_runtime/<sessionId>/`:
|
|
50
|
+
- `loop_release` (002)
|
|
51
|
+
- `loop_bee_relation` (003)
|
|
52
|
+
- loop↔bee share / desktop extension columns (004)
|
|
53
|
+
- `evolution_evaluation` (005)
|
|
54
|
+
- `crystallization_event` (006)
|
|
55
|
+
|
|
56
|
+
Migration is non-breaking: every new column has a DEFAULT and the new tables sit alongside the existing 4.x schema.
|
|
57
|
+
|
|
58
|
+
### Added — New CLI verbs (spec §7.4)
|
|
59
|
+
|
|
60
|
+
- `peaks loop init / list / show / search / recent / export / import`
|
|
61
|
+
- `peaks asset crystallize / dispose / status`
|
|
62
|
+
- `peaks evolution propose / evaluate / revert / mark-keep / status`
|
|
63
|
+
- `peaks skill lint --category loop-engineering-readiness --path <skill-dir>`
|
|
64
|
+
- `peaks skill ready --category loop-engineering-readiness --path <skill-dir>` (alias)
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **`peaks workflow *`** is reframed as the execution trace surface (per spec §7.6 demotion); it remains functional. The user-facing verb is "replay this run", not "create a new asset".
|
|
69
|
+
- **peaks-maker skill** is narratively repositioned to "Loop crystallizer + Bee creator + Evolution gatekeeper" (per spec §7.5); only the SKILL.md and memory references are re-narrated, the `id` is preserved.
|
|
70
|
+
|
|
71
|
+
### Verification (4.0.0-beta.4)
|
|
72
|
+
|
|
73
|
+
- `tsc --noEmit` zero errors
|
|
74
|
+
- `peaks standards lint --category loop-engineering` parses all 10 red lines + 4 sections each (lint passes)
|
|
75
|
+
- `pnpm vitest run tests/unit/{standards,crystallization,evolution,share}/` — 13 files / 154 unit tests pass
|
|
76
|
+
- `pnpm vitest run tests/integration/{share-bundle-roundtrip,dogfood-loop-engineering-crystallization,asset-crystallize-cli,evolution-cli,skill-loop-engineering-readiness-cli}.test.ts` — 5 files / 26 integration tests pass
|
|
77
|
+
- M8 dogfood (`tests/integration/dogfood-loop-engineering-crystallization.test.ts`) crystallizes the M0..M7 work into a real loop + bee + relation + event in 644ms (defense in depth: the release crystallizes itself)
|
|
78
|
+
|
|
79
|
+
### Migration
|
|
80
|
+
|
|
81
|
+
- 4.x `bee_release` rows continue to read. No data migration required.
|
|
82
|
+
- The 5 new tables are added via non-destructive `CREATE TABLE IF NOT EXISTS` migrations.
|
|
83
|
+
- The `bundle-writer / bundle-reader` pair is additive; existing `peaks skill sediment export / import` remains as an alias for one release cycle, then deprecates.
|
|
84
|
+
- The peaks-code SKILL.md is unchanged from 4.0.0-beta.3 — no `peaks-solo` migration needed for users already on beta.2 or later.
|
|
85
|
+
|
|
5
86
|
## 4.0.0-beta.2 — 2026-07-07
|
|
6
87
|
|
|
7
88
|
### Renamed
|
package/README-en.md
CHANGED
|
@@ -7,16 +7,29 @@
|
|
|
7
7
|
[](https://github.com/SquabbyZ/peaks-loop/stargazers)
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<
|
|
10
|
+
<a href="https://raw.githubusercontent.com/SquabbyZ/peaks-loop/main/examples/video-demo/preview/peaks-loop-demo-en.mp4">
|
|
11
|
+
<img src="https://raw.githubusercontent.com/SquabbyZ/peaks-loop/main/examples/video-demo/out/en-closing-960.png" alt="peaks-loop demo (click to play)" width="100%" style="border-radius: 12px; box-shadow: 0 12px 40px rgba(0,0,0,0.55);">
|
|
12
|
+
</a>
|
|
13
|
+
<br>
|
|
14
|
+
<sub>👆 click the image to play the 60s demo</sub>
|
|
11
15
|
</p>
|
|
12
16
|
|
|
13
17
|
## What it is
|
|
14
18
|
|
|
15
19
|
### Loop engineering, engineered
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
**peaks-loop is your Loop Engineering crystallization system — not a workflow tool.** It crystallizes Loop Engineering assets out of real, completed work, and only evolves them through verified improvements. You operate the system through natural language and choices; every structured action is performed by the LLM on your behalf.
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
The product is governed by a four-layer asset model and a karpathy × darwin dual-discipline, both of which are non-negotiable:
|
|
24
|
+
|
|
25
|
+
- **Four-layer asset model**: Loop Engineering Asset (the method system, first-class) + Bee Asset (the executable body, first-class) + Workflow Trace (execution trace — evidence only, never the durable asset) + Evolution Evaluation (anti-drift gate, mandatory for any change).
|
|
26
|
+
- **karpathy × darwin dual discipline**: karpathy engineers every rule (failure modes + imperative→declarative rewrite + self-check + out-of-scope); darwin verifies every improvement (single object + single dimension + independent-context evaluator + ratchet). The two layers are co-equal partners — neither is a subset of the other. See `.peaks/standards/loop-engineering-guidelines.md` and the upstream references `multica-ai/andrej-karpathy-skills` and `alchaincyf/darwin-skill`.
|
|
27
|
+
- **Post-run crystallization**: `loop_release` and `bee_release` are only written after a real task completes; pre-run is always scratch. Four triggers are supported, with `user_explicit` ranked highest.
|
|
28
|
+
- **Human-NL-Choice-Only**: you speak or pick; the LLM runs the CLI. Hand-authoring JSON / manifests / form fields is forbidden.
|
|
29
|
+
|
|
30
|
+
### peaks-code is the code-domain entry — and only the code-domain entry
|
|
31
|
+
|
|
32
|
+
`peaks-code` is the code-domain long-task loop engineering orchestrator — it runs the long chain (PRD, RD, QA, UI, SC, TXT) and is the role you'll talk to most. It is **not a general-purpose orchestrator** — research, content, product, and other domains are independent `peaks-*` skills that reuse the same Loop Engineering guidelines. See the Loop Engineering design `docs/superpowers/specs/2026-07-07-peaks-loop-loop-engineering-crystallization-design.md` §0.4 + RL-8, and the sediment design `docs/superpowers/specs/2026-07-04-peaks-maker-dynamic-skill-sediment-design.md`.
|
|
20
33
|
|
|
21
34
|
Summon them, they handle the work. Stand them down when it's done. **No skipped steps. No half-finished hand-offs.** No new AI CLI to learn — it sits on top of Claude Code, Codex, or Copilot you already run.
|
|
22
35
|
|
package/README.md
CHANGED
|
@@ -7,16 +7,29 @@
|
|
|
7
7
|
[](https://github.com/SquabbyZ/peaks-loop/stargazers)
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<
|
|
10
|
+
<a href="https://raw.githubusercontent.com/SquabbyZ/peaks-loop/main/examples/video-demo/preview/peaks-loop-demo.mp4">
|
|
11
|
+
<img src="https://raw.githubusercontent.com/SquabbyZ/peaks-loop/main/examples/video-demo/out/zh-closing-960.png" alt="peaks-loop demo (点击播放)" width="100%" style="border-radius: 12px; box-shadow: 0 12px 40px rgba(0,0,0,0.55);">
|
|
12
|
+
</a>
|
|
13
|
+
<br>
|
|
14
|
+
<sub>👆 点击图片播放 60s 演示</sub>
|
|
11
15
|
</p>
|
|
12
16
|
|
|
13
17
|
## 它是什么
|
|
14
18
|
|
|
15
19
|
### loop engineering 的工程实现
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
**peaks-loop 是你的 Loop Engineering 结晶系统 —— 不是工作流工具。** 它从真实完成的工作里把 loop engineering 资产结晶出来,只通过被验证的改进让它们进化。你只用自然语言和选择操作系统,所有结构化操作由 LLM 代办。
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
产品遵循四层资产模型与 karpathy × darwin 双层纪律,两者缺一不可:
|
|
24
|
+
|
|
25
|
+
- **四层资产模型**:Loop Engineering 资产(方法体系,一等公民)+ Bee 资产(可执行体,一等公民)+ Workflow Trace(执行轨迹,只作证据,不作资产)+ Evolution Evaluation(反漂移闸门,变更必经)。
|
|
26
|
+
- **karpathy × darwin 双层纪律**:karpathy 工程化每一条规则(failure modes + imperative→declarative rewrite + self-check + out-of-scope);darwin 校验每一次改进(单对象 + 单维度 + 独立上下文评估 + ratchet 防回退)。两层是平行的伙伴,谁都不是谁的子集 —— 详见 `.peaks/standards/loop-engineering-guidelines.md` 与上游参考 `multica-ai/andrej-karpathy-skills`、`alchaincyf/darwin-skill`。
|
|
27
|
+
- **post-run crystallization**:`loop_release` 与 `bee_release` 只在一次真实任务跑完后才写入;pre-run 永远是 scratch。沉淀触发支持四种,user explicit 优先级最高。
|
|
28
|
+
- **Human-NL-Choice-Only**:你只说话或选择,LLM 替你跑 CLI;禁止手写 JSON / manifest / form field。
|
|
29
|
+
|
|
30
|
+
### peaks-code 是 code-domain 唯一的入口
|
|
31
|
+
|
|
32
|
+
`peaks-code` 是 loop engineering 在 **代码域** 的长任务编排器,管 PRD、RD、QA、UI、SC、TXT 这一长串工序的主调度,也是你日常跟 peaks-loop 说话时最常打交道的那个角色。它**不是通用编排器** —— 研究 / 内容 / 产品等其他域各自是独立的 `peaks-*` 技能,复用同一份 Loop Engineering 准则。详见 Loop Engineering 设计 `docs/superpowers/specs/2026-07-07-peaks-loop-loop-engineering-crystallization-design.md` §0.4 + RL-8,以及 sediment 设计 `docs/superpowers/specs/2026-07-04-peaks-maker-dynamic-skill-sediment-design.md`。
|
|
20
33
|
|
|
21
34
|
召之即来,事完收队,**不跳步,不半截扔给你**。它不是新发明一个 AI CLI,而是架在你已有的 Claude Code / Codex / Copilot 之上,把它们都调成同一支团队,只对你这一个开发者的口味。
|
|
22
35
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peaks asset * CLI — M5 / spec §5 / §7.4
|
|
3
|
+
*
|
|
4
|
+
* Adds the umbrella verbs for the post-run crystallization flow:
|
|
5
|
+
*
|
|
6
|
+
* peaks asset crystallize --from-task <id> --loop-name <name> \
|
|
7
|
+
* --loop-scenario <text> --bee-name <name> \
|
|
8
|
+
* --bee-version <semver> --bee-description <text> \
|
|
9
|
+
* --brief-what-happened <text> --brief-why-it-matters <text> \
|
|
10
|
+
* --brief-what-learned <text> --brief-what-action <text> \
|
|
11
|
+
* [--bee-relation-reason <text>] [--brief-bullet <bullet>]... \
|
|
12
|
+
* [--source-trace <id>]... [--evaluator-summary <text>] \
|
|
13
|
+
* [--user-decision-summary <text>] \
|
|
14
|
+
* [--trigger <user_explicit|llm_suggested|success_default_prompt|similar_task_recurrence>] \
|
|
15
|
+
* [--project <root>] [--json]
|
|
16
|
+
* peaks asset dispose --crystallization-event <id> --mode <trace_only|retain|destroy> \
|
|
17
|
+
* [--project <root>] [--json]
|
|
18
|
+
* peaks asset status [--loop <id>] [--bee <name>] [--project <root>] [--json]
|
|
19
|
+
*
|
|
20
|
+
* The crystallize verb prints the 4-section brief in the
|
|
21
|
+
* user-facing recommendation; refuses to proceed if any brief
|
|
22
|
+
* section is missing (EvidenceBriefSchema.refine guard +
|
|
23
|
+
* CrystallizationService pre-run gate).
|
|
24
|
+
*
|
|
25
|
+
* The dispose verb handles trace-only / retain / destroy. trace_only
|
|
26
|
+
* retires the crystallization event but keeps the source trace;
|
|
27
|
+
* retain is a no-op on the asset (the user wants to keep it as
|
|
28
|
+
* evidence); destroy hard-retires both the event and (if the
|
|
29
|
+
* user opts in) the created/updated assets. The CLI defaults to
|
|
30
|
+
* trace-only disposal for safety.
|
|
31
|
+
*
|
|
32
|
+
* The status verb lists loop + bee lifecycle state plus any
|
|
33
|
+
* crystallization events that reference them.
|
|
34
|
+
*
|
|
35
|
+
* Each verb prints a structured JSON envelope (`printResult` with
|
|
36
|
+
* `asJson=true`). The user never types a JSON manifest / form
|
|
37
|
+
* field; the LLM runs the CLI on the user's behalf.
|
|
38
|
+
*
|
|
39
|
+
* Defense in depth: the CrystallizationService enforces the
|
|
40
|
+
* pre-run gate and the brief-section guard; the CLI only
|
|
41
|
+
* translates flags into the service payload.
|
|
42
|
+
*/
|
|
43
|
+
import { Command } from "commander";
|
|
44
|
+
import { type ProgramIO } from "../cli-helpers.js";
|
|
45
|
+
export declare function registerAssetCommands(program: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peaks asset * CLI — M5 / spec §5 / §7.4
|
|
3
|
+
*
|
|
4
|
+
* Adds the umbrella verbs for the post-run crystallization flow:
|
|
5
|
+
*
|
|
6
|
+
* peaks asset crystallize --from-task <id> --loop-name <name> \
|
|
7
|
+
* --loop-scenario <text> --bee-name <name> \
|
|
8
|
+
* --bee-version <semver> --bee-description <text> \
|
|
9
|
+
* --brief-what-happened <text> --brief-why-it-matters <text> \
|
|
10
|
+
* --brief-what-learned <text> --brief-what-action <text> \
|
|
11
|
+
* [--bee-relation-reason <text>] [--brief-bullet <bullet>]... \
|
|
12
|
+
* [--source-trace <id>]... [--evaluator-summary <text>] \
|
|
13
|
+
* [--user-decision-summary <text>] \
|
|
14
|
+
* [--trigger <user_explicit|llm_suggested|success_default_prompt|similar_task_recurrence>] \
|
|
15
|
+
* [--project <root>] [--json]
|
|
16
|
+
* peaks asset dispose --crystallization-event <id> --mode <trace_only|retain|destroy> \
|
|
17
|
+
* [--project <root>] [--json]
|
|
18
|
+
* peaks asset status [--loop <id>] [--bee <name>] [--project <root>] [--json]
|
|
19
|
+
*
|
|
20
|
+
* The crystallize verb prints the 4-section brief in the
|
|
21
|
+
* user-facing recommendation; refuses to proceed if any brief
|
|
22
|
+
* section is missing (EvidenceBriefSchema.refine guard +
|
|
23
|
+
* CrystallizationService pre-run gate).
|
|
24
|
+
*
|
|
25
|
+
* The dispose verb handles trace-only / retain / destroy. trace_only
|
|
26
|
+
* retires the crystallization event but keeps the source trace;
|
|
27
|
+
* retain is a no-op on the asset (the user wants to keep it as
|
|
28
|
+
* evidence); destroy hard-retires both the event and (if the
|
|
29
|
+
* user opts in) the created/updated assets. The CLI defaults to
|
|
30
|
+
* trace-only disposal for safety.
|
|
31
|
+
*
|
|
32
|
+
* The status verb lists loop + bee lifecycle state plus any
|
|
33
|
+
* crystallization events that reference them.
|
|
34
|
+
*
|
|
35
|
+
* Each verb prints a structured JSON envelope (`printResult` with
|
|
36
|
+
* `asJson=true`). The user never types a JSON manifest / form
|
|
37
|
+
* field; the LLM runs the CLI on the user's behalf.
|
|
38
|
+
*
|
|
39
|
+
* Defense in depth: the CrystallizationService enforces the
|
|
40
|
+
* pre-run gate and the brief-section guard; the CLI only
|
|
41
|
+
* translates flags into the service payload.
|
|
42
|
+
*/
|
|
43
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
44
|
+
import { join } from "node:path";
|
|
45
|
+
import { openStateDb } from "../../services/skillhub/sqlite-store.js";
|
|
46
|
+
import { CrystallizationService, CrystallizationIntegrityError, } from "../../services/crystallization/crystallization-service.js";
|
|
47
|
+
import { CRYSTALLIZATION_TRIGGERS, parseEvidenceBrief, } from "../../services/crystallization/crystallization-types.js";
|
|
48
|
+
import { BriefSectionError, safeRenderRecommendationPayload, } from "../../services/crystallization/evidence-brief-builder.js";
|
|
49
|
+
import { findProjectRoot } from "../../services/config/config-safety.js";
|
|
50
|
+
import { addJsonOption, getErrorMessage, printResult } from "../cli-helpers.js";
|
|
51
|
+
import { fail, ok } from "../../shared/result.js";
|
|
52
|
+
function collectRepeatable(value, previous) {
|
|
53
|
+
if (Array.isArray(previous))
|
|
54
|
+
return [...previous, value];
|
|
55
|
+
return [value];
|
|
56
|
+
}
|
|
57
|
+
const DISPOSE_MODES = ["trace_only", "retain", "destroy"];
|
|
58
|
+
function parseTriggerFlag(raw) {
|
|
59
|
+
return CRYSTALLIZATION_TRIGGERS.includes(raw)
|
|
60
|
+
? raw
|
|
61
|
+
: null;
|
|
62
|
+
}
|
|
63
|
+
export function registerAssetCommands(program, io) {
|
|
64
|
+
// Reuse the existing `asset` parent if one is registered; the
|
|
65
|
+
// add-a-new-subcommand-check-for-existing-top-level-first rule
|
|
66
|
+
// requires this guard.
|
|
67
|
+
const existing = program.commands.find((c) => c.name() === "asset");
|
|
68
|
+
const asset = existing ?? program.command("asset").description("M5: cross-asset crystallization surface (crystallize / dispose / status — spec §5 / §7.4)");
|
|
69
|
+
// ---------- peaks asset crystallize ----------
|
|
70
|
+
addJsonOption(asset
|
|
71
|
+
.command("crystallize")
|
|
72
|
+
.description("M5: persist a new loop_release + main_bee_release + loop_bee_relation + crystallization_event in a single transaction. Enforces the pre-run gate (task_status=completed AND gates_passed=true AND evidence_collected=true; AC-4 / RL-2) and the brief-section guard (all 4 sections required; AC-15 / RL-7).")
|
|
73
|
+
.requiredOption("--from-task <id>", "the candidate task id (must be 'completed' with gates_passed + evidence_collected)")
|
|
74
|
+
.requiredOption("--loop-id <id>", "kebab-case loop id, e.g. loop-onboarding-research")
|
|
75
|
+
.requiredOption("--loop-name <name>", "NL display name")
|
|
76
|
+
.requiredOption("--loop-scenario <text>", "long-form scenario text (what real problem the loop solves)")
|
|
77
|
+
.requiredOption("--loop-trigger-policy <text>", "trigger policy (NL intent match)")
|
|
78
|
+
.requiredOption("--loop-interaction-policy <text>", "interaction policy (Human-NL-Choice-Only is the default)")
|
|
79
|
+
.requiredOption("--loop-feedback-policy <text>", "feedback policy (what feedback enters long-term memory)")
|
|
80
|
+
.requiredOption("--loop-evolution-policy <text>", "evolution policy (Darwin-style ratchet rules)")
|
|
81
|
+
.requiredOption("--loop-success-criterion <text>", "declarative success criterion (repeatable)", collectRepeatable, [])
|
|
82
|
+
.requiredOption("--loop-evaluator-policy <text>", "evaluator policy line (repeatable)", collectRepeatable, [])
|
|
83
|
+
.requiredOption("--loop-version <semver>", "loop version (e.g. 0.1.0)")
|
|
84
|
+
.requiredOption("--bee-name <name>", "main bee name (kebab-case)")
|
|
85
|
+
.requiredOption("--bee-version <semver>", "bee version (e.g. 0.1.0)")
|
|
86
|
+
.requiredOption("--bee-description <text>", "main bee description (manifest-level)")
|
|
87
|
+
.requiredOption("--bee-relation-reason <text>", "NL reason for the main bee relation")
|
|
88
|
+
.requiredOption("--brief-what-happened <text>", "brief section: what_happened (1-2 sentence factual account)")
|
|
89
|
+
.requiredOption("--brief-why-it-matters <text>", "brief section: why_it_matters (1-2 sentence explanation)")
|
|
90
|
+
.requiredOption("--brief-what-learned <text>", "brief section: what_learned (1-2 sentence learning)")
|
|
91
|
+
.requiredOption("--brief-what-action <text>", "brief section: what_action (1 sentence recommended action)")
|
|
92
|
+
.option("--brief-bullet <bullet>", "structured bullet supporting the brief (repeatable)", collectRepeatable, [])
|
|
93
|
+
.option("--source-trace <id>", "workflow trace id backing the brief (repeatable)", collectRepeatable, [])
|
|
94
|
+
.option("--evaluator-summary <text>", "evaluator one-liner (independent scorers)", "")
|
|
95
|
+
.option("--user-decision-summary <text>", "user decision summary (NL)", "")
|
|
96
|
+
.option("--bee-intent-raw <text>", "optional bee user_intent_raw")
|
|
97
|
+
.option("--bee-parent-version <semver>", "optional parent_version")
|
|
98
|
+
.option("--bee-changelog <text>", "optional changelog")
|
|
99
|
+
.option("--trigger <name>", `crystallization trigger (one of: ${CRYSTALLIZATION_TRIGGERS.join("|")})`, "user_explicit")
|
|
100
|
+
.option("--project <path>", "project root (default: cwd)")).action((options) => {
|
|
101
|
+
try {
|
|
102
|
+
const trigger = parseTriggerFlag(options.trigger);
|
|
103
|
+
if (!trigger) {
|
|
104
|
+
printResult(io, fail("asset.crystallize", "ASSET_INVALID_TRIGGER", `--trigger must be one of: ${CRYSTALLIZATION_TRIGGERS.join("|")}`, { trigger: options.trigger }, ["Pass a valid --trigger value."]), options.json);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Build the brief from CLI flags and re-validate it through
|
|
109
|
+
// the canonical schema (so a partial brief is caught BEFORE
|
|
110
|
+
// any DB side effects fire).
|
|
111
|
+
const candidateBrief = {
|
|
112
|
+
what_happened: options.briefWhatHappened,
|
|
113
|
+
why_it_matters: options.briefWhyItMatters,
|
|
114
|
+
what_learned: options.briefWhatLearned,
|
|
115
|
+
what_action: options.briefWhatAction,
|
|
116
|
+
};
|
|
117
|
+
let brief;
|
|
118
|
+
try {
|
|
119
|
+
brief = parseEvidenceBrief(candidateBrief);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
if (err instanceof BriefSectionError) {
|
|
123
|
+
printResult(io, fail("asset.crystallize", "MISSING_BRIEF_SECTION", err.message, { findings: [...err.findings], flagsProvided: Object.keys(candidateBrief) }, [
|
|
124
|
+
"Pass ALL FOUR brief sections via --brief-what-happened / --brief-why-it-matters / --brief-what-learned / --brief-what-action.",
|
|
125
|
+
"The CLI refuses to render a recommendation without a complete 4-section brief (spec §4.7 / RL-7).",
|
|
126
|
+
]), options.json);
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
// Render the recommendation envelope up-front so the CLI can
|
|
133
|
+
// refuse to write if any brief section is missing. This is
|
|
134
|
+
// the same code path the crystallization service uses.
|
|
135
|
+
const recommendation = safeRenderRecommendationPayload({
|
|
136
|
+
brief,
|
|
137
|
+
bullets: options.briefBullet ?? [],
|
|
138
|
+
source_trace_pointers: options.sourceTrace ?? [],
|
|
139
|
+
evaluator_summary: {
|
|
140
|
+
one_liner: options.evaluatorSummary ?? "",
|
|
141
|
+
risk_tags: [],
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
if (!recommendation.ok) {
|
|
145
|
+
printResult(io, fail("asset.crystallize", recommendation.code ?? "MISSING_BRIEF_SECTION", "recommendation envelope rejected — brief is missing one of its 4 sections", { findings: recommendation.findings, briefProvided: candidateBrief }, [
|
|
146
|
+
"Pass ALL FOUR brief sections via the matching CLI flags.",
|
|
147
|
+
"Counts in --brief-bullet may support the brief; they do NOT replace it (spec §4.7 / RL-7).",
|
|
148
|
+
]), options.json);
|
|
149
|
+
process.exitCode = 1;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
153
|
+
if (!existsSync(join(projectRoot, ".peaks"))) {
|
|
154
|
+
mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
|
|
157
|
+
try {
|
|
158
|
+
const svc = new CrystallizationService(db);
|
|
159
|
+
const result = svc.crystallize({
|
|
160
|
+
task: {
|
|
161
|
+
task_id: options.fromTask,
|
|
162
|
+
task_status: "completed",
|
|
163
|
+
gates_passed: true,
|
|
164
|
+
evidence_collected: true,
|
|
165
|
+
},
|
|
166
|
+
loop_input: {
|
|
167
|
+
id: options.loopId,
|
|
168
|
+
name: options.loopName,
|
|
169
|
+
scenario: options.loopScenario,
|
|
170
|
+
trigger_policy: options.loopTriggerPolicy,
|
|
171
|
+
success_criteria: options.loopSuccessCriterion,
|
|
172
|
+
interaction_policy: options.loopInteractionPolicy,
|
|
173
|
+
feedback_policy: options.loopFeedbackPolicy,
|
|
174
|
+
evolution_policy: options.loopEvolutionPolicy,
|
|
175
|
+
evaluator_policy: options.loopEvaluatorPolicy,
|
|
176
|
+
linked_bees: [],
|
|
177
|
+
run_history: [],
|
|
178
|
+
crystallization_evidence: [],
|
|
179
|
+
lifecycle_status: "candidate",
|
|
180
|
+
version: options.loopVersion,
|
|
181
|
+
},
|
|
182
|
+
bee_input: {
|
|
183
|
+
bee_name: options.beeName,
|
|
184
|
+
version: options.beeVersion,
|
|
185
|
+
description: options.beeDescription,
|
|
186
|
+
...(options.beeIntentRaw !== undefined ? { user_intent_raw: options.beeIntentRaw } : {}),
|
|
187
|
+
...(options.beeParentVersion !== undefined ? { parent_version: options.beeParentVersion } : {}),
|
|
188
|
+
...(options.beeChangelog !== undefined ? { changelog: options.beeChangelog } : {}),
|
|
189
|
+
},
|
|
190
|
+
bee_relation_reason: options.beeRelationReason,
|
|
191
|
+
evidence_brief: brief,
|
|
192
|
+
evidence_bullets: options.briefBullet ?? [],
|
|
193
|
+
source_trace_pointers: options.sourceTrace ?? [],
|
|
194
|
+
...(options.evaluatorSummary !== undefined ? { evaluator_summary: options.evaluatorSummary } : {}),
|
|
195
|
+
...(options.userDecisionSummary !== undefined ? { user_decision_summary: options.userDecisionSummary } : {}),
|
|
196
|
+
trigger,
|
|
197
|
+
});
|
|
198
|
+
printResult(io, ok("asset.crystallize", {
|
|
199
|
+
recommendation: {
|
|
200
|
+
brief: recommendation.payload.brief,
|
|
201
|
+
bullets: recommendation.payload.bullets,
|
|
202
|
+
source_trace_pointers: recommendation.payload.source_trace_pointers,
|
|
203
|
+
evaluator_summary: recommendation.payload.evaluator_summary,
|
|
204
|
+
},
|
|
205
|
+
result,
|
|
206
|
+
nextActions: [
|
|
207
|
+
`Run \`peaks loop show --loop ${result.loop_release_id}\` to inspect the new loop_release.`,
|
|
208
|
+
`Run \`peaks asset dispose --crystallization-event ${result.crystallization_event_id} --mode trace_only\` to retire the event without touching the asset.`,
|
|
209
|
+
],
|
|
210
|
+
}, [], [
|
|
211
|
+
"The CLI has surfaced a complete 4-section brief; the LLM should now drive the user through AskUserQuestion picks (spec §5.3).",
|
|
212
|
+
]), options.json);
|
|
213
|
+
}
|
|
214
|
+
finally {
|
|
215
|
+
db.close();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
if (err instanceof CrystallizationIntegrityError) {
|
|
220
|
+
printResult(io, fail("asset.crystallize", err.code, err.message, { findings: [...err.findings] }, [
|
|
221
|
+
err.code === "CRYSTALLIZATION_PRE_RUN"
|
|
222
|
+
? "Re-shape the candidate task so task_status=completed AND gates_passed=true AND evidence_collected=true (spec §5 / RL-2)."
|
|
223
|
+
: err.code === "MISSING_BRIEF_SECTION"
|
|
224
|
+
? "Pass ALL FOUR brief sections (RL-7); the CLI refuses to render a recommendation without them."
|
|
225
|
+
: "Inspect the failure findings and re-shape the payload.",
|
|
226
|
+
]), options.json);
|
|
227
|
+
process.exitCode = 1;
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (err instanceof BriefSectionError) {
|
|
231
|
+
printResult(io, fail("asset.crystallize", "MISSING_BRIEF_SECTION", err.message, { findings: [...err.findings] }, ["Pass all 4 brief sections (RL-7)."]), options.json);
|
|
232
|
+
process.exitCode = 1;
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
printResult(io, fail("asset.crystallize", "ASSET_CRYSTALLIZE_FAILED", getErrorMessage(err), {}, [
|
|
236
|
+
"Verify the brief flags and the loop/bee identifiers are valid.",
|
|
237
|
+
]), options.json);
|
|
238
|
+
process.exitCode = 1;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
// ---------- peaks asset dispose ----------
|
|
242
|
+
addJsonOption(asset
|
|
243
|
+
.command("dispose")
|
|
244
|
+
.description("M5: dispose a crystallization event. mode=trace_only retires the event but keeps the trace; mode=retain keeps both; mode=destroy retires the event and marks the created/updated assets retired.")
|
|
245
|
+
.requiredOption("--crystallization-event <id>", "the crystallization event id (returned by `peaks asset crystallize`)")
|
|
246
|
+
.requiredOption(`--mode <mode>`, `dispose mode (one of: ${DISPOSE_MODES.join("|")}; default: trace_only)`, "trace_only")
|
|
247
|
+
.option("--project <path>", "project root (default: cwd)")).action((options) => {
|
|
248
|
+
try {
|
|
249
|
+
if (!DISPOSE_MODES.includes(options.mode)) {
|
|
250
|
+
printResult(io, fail("asset.dispose", "ASSET_INVALID_MODE", `--mode must be one of: ${DISPOSE_MODES.join("|")}`, { mode: options.mode }, ["Pass --mode trace_only / retain / destroy."]), options.json);
|
|
251
|
+
process.exitCode = 1;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const mode = options.mode;
|
|
255
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
256
|
+
if (!existsSync(join(projectRoot, ".peaks"))) {
|
|
257
|
+
mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
|
|
258
|
+
}
|
|
259
|
+
const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
|
|
260
|
+
try {
|
|
261
|
+
const svc = new CrystallizationService(db);
|
|
262
|
+
const existing = svc.read(options.crystallizationEvent);
|
|
263
|
+
if (!existing) {
|
|
264
|
+
printResult(io, fail("asset.dispose", "ASSET_EVENT_NOT_FOUND", `crystallization event '${options.crystallizationEvent}' not found`, { eventId: options.crystallizationEvent }, ["Verify the event id."]), options.json);
|
|
265
|
+
process.exitCode = 1;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
// trace_only → retire the event row; the source traces are
|
|
269
|
+
// untouched (workflow traces are immutable per spec §4.3).
|
|
270
|
+
// retain → no DB change; the user wants to keep the event
|
|
271
|
+
// as historical evidence.
|
|
272
|
+
// destroy → retire the event + retire the created/updated
|
|
273
|
+
// assets (no asset delete; retirement only).
|
|
274
|
+
if (mode === "retain") {
|
|
275
|
+
printResult(io, ok("asset.dispose", {
|
|
276
|
+
crystallization_event_id: existing.id,
|
|
277
|
+
mode,
|
|
278
|
+
lifecycle_status: existing.lifecycle_status,
|
|
279
|
+
note: "no DB change; event retained as-is",
|
|
280
|
+
}, [], [
|
|
281
|
+
"Run `peaks asset status --loop <id>` to inspect the lifecycle state of the linked assets.",
|
|
282
|
+
]), options.json);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const updated = svc.updateStatus(existing.id, "retired");
|
|
286
|
+
if (mode === "destroy" && updated) {
|
|
287
|
+
// Retire the created/updated loop_release rows as well.
|
|
288
|
+
// We do NOT delete rows — retirement is a status flip
|
|
289
|
+
// (spec §5.6).
|
|
290
|
+
const retireLoop = db.prepare("UPDATE loop_release SET lifecycle_status = 'retired' WHERE id IN (?, ?)");
|
|
291
|
+
const ids = [
|
|
292
|
+
updated.created_loop_release_id ?? null,
|
|
293
|
+
updated.updated_loop_release_id ?? null,
|
|
294
|
+
].filter((x) => x !== null);
|
|
295
|
+
for (const id of ids) {
|
|
296
|
+
retireLoop.run(id, id);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
printResult(io, ok("asset.dispose", {
|
|
300
|
+
crystallization_event_id: existing.id,
|
|
301
|
+
mode,
|
|
302
|
+
lifecycle_status: updated?.lifecycle_status ?? "retired",
|
|
303
|
+
retired_assets: mode === "destroy" ? "loop_release retired (no rows deleted)" : "none",
|
|
304
|
+
nextActions: [
|
|
305
|
+
"Run `peaks asset status` to confirm the lifecycle transition.",
|
|
306
|
+
],
|
|
307
|
+
}, [], []), options.json);
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
db.close();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch (err) {
|
|
314
|
+
printResult(io, fail("asset.dispose", "ASSET_DISPOSE_FAILED", getErrorMessage(err), { eventId: options.crystallizationEvent }, [
|
|
315
|
+
"Verify the event id and --mode flag.",
|
|
316
|
+
]), options.json);
|
|
317
|
+
process.exitCode = 1;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
// ---------- peaks asset status ----------
|
|
321
|
+
addJsonOption(asset
|
|
322
|
+
.command("status")
|
|
323
|
+
.description("M5: list loop + bee lifecycle state. With --loop, returns the loop + all linked bee_releases + all crystallization events referencing them.")
|
|
324
|
+
.option("--loop <id>", "filter by loop_release id")
|
|
325
|
+
.option("--bee <name>", "filter by bee_name")
|
|
326
|
+
.option("--project <path>", "project root (default: cwd)")).action((options) => {
|
|
327
|
+
try {
|
|
328
|
+
const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
|
|
329
|
+
if (!existsSync(join(projectRoot, ".peaks"))) {
|
|
330
|
+
mkdirSync(join(projectRoot, ".peaks"), { recursive: true });
|
|
331
|
+
}
|
|
332
|
+
const db = openStateDb(join(projectRoot, ".peaks", "state.db"));
|
|
333
|
+
try {
|
|
334
|
+
const svc = new CrystallizationService(db);
|
|
335
|
+
let events = [];
|
|
336
|
+
if (options.loop) {
|
|
337
|
+
events = [
|
|
338
|
+
...svc.list({ created_loop_release_id: options.loop }),
|
|
339
|
+
...svc.list({ updated_loop_release_id: options.loop }),
|
|
340
|
+
];
|
|
341
|
+
// Deduplicate by id.
|
|
342
|
+
const seen = new Set();
|
|
343
|
+
events = events.filter((e) => {
|
|
344
|
+
const key = e?.id ?? "";
|
|
345
|
+
if (!key || seen.has(key))
|
|
346
|
+
return false;
|
|
347
|
+
seen.add(key);
|
|
348
|
+
return true;
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
events = svc.list();
|
|
353
|
+
}
|
|
354
|
+
// Companion queries: loop lifecycle counts + bee lifecycle
|
|
355
|
+
// counts. We compute from the DB so the CLI is the single
|
|
356
|
+
// source of truth.
|
|
357
|
+
const loopCounts = db
|
|
358
|
+
.prepare("SELECT lifecycle_status, COUNT(*) AS n FROM loop_release GROUP BY lifecycle_status")
|
|
359
|
+
.all()
|
|
360
|
+
.reduce((acc, r) => {
|
|
361
|
+
acc[r.lifecycle_status] = r.n;
|
|
362
|
+
return acc;
|
|
363
|
+
}, {});
|
|
364
|
+
const beeCounts = db
|
|
365
|
+
.prepare("SELECT bee_name, COUNT(*) AS n FROM bee_release GROUP BY bee_name ORDER BY bee_name ASC")
|
|
366
|
+
.all()
|
|
367
|
+
.filter((r) => options.bee === undefined || r.bee_name === options.bee);
|
|
368
|
+
printResult(io, ok("asset.status", {
|
|
369
|
+
filters: {
|
|
370
|
+
...(options.loop !== undefined ? { loop: options.loop } : {}),
|
|
371
|
+
...(options.bee !== undefined ? { bee: options.bee } : {}),
|
|
372
|
+
},
|
|
373
|
+
crystallization_events: events.length,
|
|
374
|
+
events: events.map((e) => ({
|
|
375
|
+
id: e?.id,
|
|
376
|
+
trigger: e?.trigger,
|
|
377
|
+
lifecycle_status: e?.lifecycle_status,
|
|
378
|
+
created_loop_release_id: e?.created_loop_release_id,
|
|
379
|
+
created_bee_release_id: e?.created_bee_release_id,
|
|
380
|
+
updated_loop_release_id: e?.updated_loop_release_id,
|
|
381
|
+
updated_bee_release_id: e?.updated_bee_release_id,
|
|
382
|
+
created_at: e?.created_at,
|
|
383
|
+
})),
|
|
384
|
+
loop_release_counts_by_lifecycle: loopCounts,
|
|
385
|
+
bee_release_counts_by_name: beeCounts,
|
|
386
|
+
nextActions: [
|
|
387
|
+
options.loop
|
|
388
|
+
? `Run \`peaks loop show --loop ${options.loop}\` for the loop detail view.`
|
|
389
|
+
: "Pass --loop <id> to drill into a specific loop's crystallization history.",
|
|
390
|
+
],
|
|
391
|
+
}, [], []), options.json);
|
|
392
|
+
}
|
|
393
|
+
finally {
|
|
394
|
+
db.close();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
printResult(io, fail("asset.status", "ASSET_STATUS_FAILED", getErrorMessage(err), { loop: options.loop, bee: options.bee }, [
|
|
399
|
+
"Verify the loop / bee identifiers.",
|
|
400
|
+
]), options.json);
|
|
401
|
+
process.exitCode = 1;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* peaks bee * CLI — M7 (spec §7A.2 / §10 RL-9 / AC-24 / AC-25 / AC-26).
|
|
3
|
+
*
|
|
4
|
+
* Adds:
|
|
5
|
+
* peaks bee export --bee <id> --out <path.tar.gz>
|
|
6
|
+
* peaks bee import --in <path.tar.gz> [--as <bee-name>]
|
|
7
|
+
*
|
|
8
|
+
* The bee-side counterparts of the loop-side `peaks loop export /
|
|
9
|
+
* import`. Same hard rules:
|
|
10
|
+
*
|
|
11
|
+
* - source `shareable=false` blocks export at the CLI layer.
|
|
12
|
+
* - bundle lands as `candidate`; promotion to `stable` requires
|
|
13
|
+
* an `evolution_evaluation` row with an independent
|
|
14
|
+
* `evaluator_id` (see `peaks evolution evaluate`).
|
|
15
|
+
*
|
|
16
|
+
* The bee bundle captures the bee's inline manifest/segment/file/change
|
|
17
|
+
* rows so the receiver can re-materialize the full bee + any
|
|
18
|
+
* loop↔bee relations that referenced it + any evidence briefs that
|
|
19
|
+
* touched it.
|
|
20
|
+
*
|
|
21
|
+
* The `peaks skill sediment export / import` alias lives in
|
|
22
|
+
* `src/cli/commands/skill-sediment.ts` for one release cycle.
|
|
23
|
+
*/
|
|
24
|
+
import { Command } from 'commander';
|
|
25
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
26
|
+
export declare function registerBeeCommands(program: Command, io: ProgramIO): void;
|