peaks-loop 4.0.0-beta.2 → 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/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/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;
|