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.
Files changed (71) hide show
  1. package/README-en.md +16 -3
  2. package/README.md +16 -3
  3. package/dist/src/cli/commands/asset-commands.d.ts +45 -0
  4. package/dist/src/cli/commands/asset-commands.js +404 -0
  5. package/dist/src/cli/commands/bee-commands.d.ts +26 -0
  6. package/dist/src/cli/commands/bee-commands.js +169 -0
  7. package/dist/src/cli/commands/evolution-commands.d.ts +27 -0
  8. package/dist/src/cli/commands/evolution-commands.js +393 -0
  9. package/dist/src/cli/commands/loop-commands.d.ts +19 -13
  10. package/dist/src/cli/commands/loop-commands.js +147 -13
  11. package/dist/src/cli/commands/sediment-commands.js +9 -0
  12. package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.d.ts +67 -0
  13. package/dist/src/cli/commands/skill-loop-engineering-readiness-commands.js +173 -0
  14. package/dist/src/cli/program.js +17 -0
  15. package/dist/src/services/crystallization/crystallization-service.d.ts +239 -0
  16. package/dist/src/services/crystallization/crystallization-service.js +397 -0
  17. package/dist/src/services/crystallization/crystallization-store.d.ts +75 -0
  18. package/dist/src/services/crystallization/crystallization-store.js +193 -0
  19. package/dist/src/services/crystallization/crystallization-types.d.ts +313 -0
  20. package/dist/src/services/crystallization/crystallization-types.js +299 -0
  21. package/dist/src/services/crystallization/evidence-brief-builder.d.ts +237 -0
  22. package/dist/src/services/crystallization/evidence-brief-builder.js +240 -0
  23. package/dist/src/services/evolution/evolution-service.d.ts +137 -0
  24. package/dist/src/services/evolution/evolution-service.js +281 -0
  25. package/dist/src/services/evolution/evolution-store.d.ts +57 -0
  26. package/dist/src/services/evolution/evolution-store.js +232 -0
  27. package/dist/src/services/evolution/evolution-types.d.ts +706 -0
  28. package/dist/src/services/evolution/evolution-types.js +286 -0
  29. package/dist/src/services/evolution/independent-evaluator-runner.d.ts +90 -0
  30. package/dist/src/services/evolution/independent-evaluator-runner.js +106 -0
  31. package/dist/src/services/evolution/regression-skeptic-runner.d.ts +64 -0
  32. package/dist/src/services/evolution/regression-skeptic-runner.js +113 -0
  33. package/dist/src/services/loop/loop-bee-relation-service.d.ts +107 -0
  34. package/dist/src/services/loop/loop-bee-relation-service.js +211 -0
  35. package/dist/src/services/loop/loop-bee-relation-store.d.ts +53 -0
  36. package/dist/src/services/loop/loop-bee-relation-store.js +130 -0
  37. package/dist/src/services/loop/loop-bee-relation-types.d.ts +118 -0
  38. package/dist/src/services/loop/loop-bee-relation-types.js +114 -0
  39. package/dist/src/services/loop/loop-release-service.d.ts +46 -0
  40. package/dist/src/services/loop/loop-release-service.js +66 -0
  41. package/dist/src/services/loop/loop-release-store.d.ts +55 -0
  42. package/dist/src/services/loop/loop-release-store.js +134 -0
  43. package/dist/src/services/loop/loop-release-types.d.ts +250 -0
  44. package/dist/src/services/loop/loop-release-types.js +162 -0
  45. package/dist/src/services/share/bundle-reader.d.ts +100 -0
  46. package/dist/src/services/share/bundle-reader.js +434 -0
  47. package/dist/src/services/share/bundle-types.d.ts +392 -0
  48. package/dist/src/services/share/bundle-types.js +318 -0
  49. package/dist/src/services/share/bundle-writer.d.ts +97 -0
  50. package/dist/src/services/share/bundle-writer.js +429 -0
  51. package/dist/src/services/share/run-state-contract.d.ts +133 -0
  52. package/dist/src/services/share/run-state-contract.js +138 -0
  53. package/dist/src/services/skillhub/release-import.js +7 -2
  54. package/dist/src/services/skillhub/release-retain.js +6 -1
  55. package/dist/src/services/skillhub/sqlite-store.js +17 -1
  56. package/dist/src/services/skillhub/types.d.ts +10 -0
  57. package/dist/src/services/standards/loop-engineering-lint.d.ts +38 -0
  58. package/dist/src/services/standards/loop-engineering-lint.js +101 -0
  59. package/dist/src/services/standards/loop-engineering-readiness-lint.d.ts +111 -0
  60. package/dist/src/services/standards/loop-engineering-readiness-lint.js +248 -0
  61. package/dist/src/shared/version.d.ts +1 -1
  62. package/dist/src/shared/version.js +1 -1
  63. package/examples/video-demo/README.md +57 -0
  64. package/examples/video-demo/out/en-closing-960.png +0 -0
  65. package/examples/video-demo/out/zh-closing-960.png +0 -0
  66. package/examples/video-demo/preview/peaks-loop-demo-en.mp4 +0 -0
  67. package/examples/video-demo/preview/peaks-loop-demo.mp4 +0 -0
  68. package/package.json +6 -2
  69. package/skills/peaks-code/SKILL.md +13 -0
  70. package/skills/peaks-content/SKILL.md +272 -0
  71. package/skills/peaks-issue-fix-orchestrator/SKILL.md +307 -0
@@ -1,23 +1,35 @@
1
1
  /**
2
- * peaks loop * CLI (Slice #14) — L4 Agent Loop Integration.
2
+ * peaks loop * CLI (Slice #14 + M7 add).
3
3
  *
4
- * Per docs/superpowers/specs/2026-06-11-peaks-loop-l1-l2-l3-redesign.md §5.4
5
- * Slice #14, ships 5 sub-features:
4
+ * Slices:
5
+ * #14 sub-features (L4 Agent Loop integration): distill / preflight /
6
+ * detect-pattern / check-consistency / peaks goal compose.
6
7
  *
7
- * 14.1 peaks loop distill — extract patterns from past sessions
8
- * (delegates to peaks memory extract)
9
- * 14.2 peaks loop preflight — pre-run sanity checks (placeholder)
10
- * 14.3 peaks loop detect-pattern — find repeating patterns (placeholder)
11
- * 14.4 peaks loop check-consistency — verify state consistency (placeholder)
12
- * 14.5 peaks goal compose — autonomous goal composition (placeholder;
13
- * requires IDE adapter goalCommand capability
14
- * per Slice #0.7)
8
+ * M7 (2026-07-07 spec §7A.2):
9
+ * peaks loop export --loop <id> --out <path.tar.gz>
10
+ * peaks loop import --in <path.tar.gz> [--as <loop-id>]
15
11
  *
16
- * The 4 placeholders emit a clear nextActions list; the LLM-side UX
17
- * layer composes the actual runtime today.
12
+ * `peaks loop export` reads the source loop_release + relations +
13
+ * related bees + evidence briefs and emits a `peaks.bundle/1`
14
+ * tar.gz. Hard-blocks when the source has `shareable=false`.
15
+ *
16
+ * `peaks loop import` extracts a bundle, validates
17
+ * format_version_major=1, and lands the release as `candidate`
18
+ * (mandatory landing status per spec §7A.2). The receiver must run
19
+ * an independent evaluation before promoting to `stable`.
20
+ *
21
+ * The existing 14.x commands are kept under the same `loop` parent;
22
+ * M7 only ADDs export / import — it does not modify the existing
23
+ * surface.
18
24
  */
25
+ import { existsSync, mkdirSync } from 'node:fs';
26
+ import { join } from 'node:path';
19
27
  import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
20
28
  import { fail, ok } from '../../shared/result.js';
29
+ import { findProjectRoot } from '../../services/config/config-safety.js';
30
+ import { openStateDb } from '../../services/skillhub/sqlite-store.js';
31
+ import { writeBundle, BundleNotShareableError, BundleAssetNotFoundError, } from '../../services/share/bundle-writer.js';
32
+ import { readBundle, BundleMajorVersionMismatchError, BundleSchemaVersionsMismatchError, BundleImportToStableForbiddenError, BundleMalformedError, } from '../../services/share/bundle-reader.js';
21
33
  export function registerLoopCommands(program, io) {
22
34
  // 14.5 peaks goal compose — registered as a TOP-LEVEL command (not under
23
35
  // `peaks loop`) because IDE adapters expose it as `goalCommand`.
@@ -125,4 +137,126 @@ export function registerLoopCommands(program, io) {
125
137
  'loop.check-consistency is a thin facade; the LLM-side UX layer composes the drift scan.',
126
138
  ]), options.json);
127
139
  });
140
+ // ---------- M7: peaks loop export / import (spec §7A.2) ----------
141
+ const LOOP_EXPORT_IMPORT_OPTS = (cmd) => cmd
142
+ .option('--project <path>', 'target project root (defaults to cwd)')
143
+ .option('--json', 'print machine-readable JSON envelope');
144
+ LOOP_EXPORT_IMPORT_OPTS(loop
145
+ .command('export')
146
+ .description("M7: export a loop_release as a peaks.bundle/1 tarball (spec §7A.2 / §10 RL-9). Refuses to export when shareable=false.")
147
+ .requiredOption('--loop <id>', 'loop_release id (kebab-case)')
148
+ .requiredOption('--out <path>', 'output .tar.gz path')).action((options) => {
149
+ const asJson = options.json === true;
150
+ try {
151
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
152
+ if (!existsSync(join(projectRoot, '.peaks'))) {
153
+ mkdirSync(join(projectRoot, '.peaks'), { recursive: true });
154
+ }
155
+ const db = openStateDb(join(projectRoot, '.peaks', 'state.db'));
156
+ try {
157
+ const result = writeBundle({
158
+ db,
159
+ blobsDir: join(projectRoot, '.peaks', 'blobs'),
160
+ kind: 'loop',
161
+ id: options.loop,
162
+ outPath: options.out,
163
+ });
164
+ printResult(io, ok('loop.export', {
165
+ outPath: result.outPath,
166
+ kind: result.kind,
167
+ assetId: result.assetId,
168
+ importedAs: 'candidate',
169
+ }, [], [
170
+ `Receiver must run \`peaks loop import --in ${result.outPath}\` to land this bundle, then run an independent evolution_evaluation before any promote.`,
171
+ ]), asJson);
172
+ }
173
+ finally {
174
+ db.close();
175
+ }
176
+ }
177
+ catch (error) {
178
+ if (error instanceof BundleNotShareableError) {
179
+ printResult(io, fail('loop.export', error.code, error.message, { ok: false, loop: options.loop }, [
180
+ 'Set shareable=true on the loop_release row, or share via desktop_visible=false.',
181
+ ]), asJson);
182
+ process.exitCode = 1;
183
+ return;
184
+ }
185
+ if (error instanceof BundleAssetNotFoundError) {
186
+ printResult(io, fail('loop.export', error.code, error.message, { ok: false, loop: options.loop }, [
187
+ 'Verify the loop id with `peaks asset status --loop <id>`.',
188
+ ]), asJson);
189
+ process.exitCode = 1;
190
+ return;
191
+ }
192
+ printResult(io, fail('loop.export', 'LOOP_EXPORT_FAILED', getErrorMessage(error), { ok: false, loop: options.loop }, ['Verify --loop id and --out path.']), asJson);
193
+ process.exitCode = 1;
194
+ }
195
+ });
196
+ LOOP_EXPORT_IMPORT_OPTS(loop
197
+ .command('import')
198
+ .description("M7: import a peaks.bundle/1 tarball. Lands as candidate only — promotion requires an evolution_evaluation row (spec §7A.2 / §10 RL-9 / AC-25 / AC-26).")
199
+ .requiredOption('--in <path>', 'input .tar.gz path')
200
+ .option('--as <id>', 'optional rename for the loop id when landing')).action((options) => {
201
+ const asJson = options.json === true;
202
+ try {
203
+ const projectRoot = options.project ?? findProjectRoot(process.cwd()) ?? process.cwd();
204
+ if (!existsSync(join(projectRoot, '.peaks'))) {
205
+ mkdirSync(join(projectRoot, '.peaks'), { recursive: true });
206
+ }
207
+ const db = openStateDb(join(projectRoot, '.peaks', 'state.db'));
208
+ try {
209
+ const result = readBundle({
210
+ db,
211
+ blobsDir: join(projectRoot, '.peaks', 'blobs'),
212
+ inPath: options.in,
213
+ ...(options.as !== undefined ? { asName: options.as } : {}),
214
+ });
215
+ printResult(io, ok('loop.import', {
216
+ assetId: result.assetId,
217
+ kind: result.kind,
218
+ importedAs: result.importedAs,
219
+ warnings: result.warnings,
220
+ evidenceBriefCount: result.evidenceBriefCount,
221
+ }, result.warnings, [
222
+ `Run an independent evaluation against this release before promoting; peaks loop promote refuses without an evolution_evaluation row.`,
223
+ ]), asJson);
224
+ }
225
+ finally {
226
+ db.close();
227
+ }
228
+ }
229
+ catch (error) {
230
+ if (error instanceof BundleMajorVersionMismatchError) {
231
+ printResult(io, fail('loop.import', error.code, error.message, { ok: false, receivedMajor: error.receivedMajor }, [
232
+ 'Use the matching peaks.bundle/<major> reader; this peaks build only supports major=1.',
233
+ ]), asJson);
234
+ process.exitCode = 1;
235
+ return;
236
+ }
237
+ if (error instanceof BundleSchemaVersionsMismatchError) {
238
+ printResult(io, fail('loop.import', error.code, error.message, { ok: false }, [
239
+ 'Source bundle did not declare the canonical schema versions; refuse the bundle.',
240
+ ]), asJson);
241
+ process.exitCode = 1;
242
+ return;
243
+ }
244
+ if (error instanceof BundleImportToStableForbiddenError) {
245
+ printResult(io, fail('loop.import', error.code, error.message, { ok: false }, [
246
+ 'Bundles always land as candidate; promotion to stable requires an evolution_evaluation row (AC-26).',
247
+ ]), asJson);
248
+ process.exitCode = 1;
249
+ return;
250
+ }
251
+ if (error instanceof BundleMalformedError) {
252
+ printResult(io, fail('loop.import', error.code, error.message, { ok: false, inPath: options.in }, [
253
+ 'Re-export from the source via `peaks loop export`.',
254
+ ]), asJson);
255
+ process.exitCode = 1;
256
+ return;
257
+ }
258
+ printResult(io, fail('loop.import', 'LOOP_IMPORT_FAILED', getErrorMessage(error), { ok: false, inPath: options.in }, ['Verify the bundle path and integrity.']), asJson);
259
+ process.exitCode = 1;
260
+ }
261
+ });
128
262
  }
@@ -450,6 +450,11 @@ export async function runSediment(argv, { home }) {
450
450
  finally {
451
451
  db.close();
452
452
  }
453
+ // M7 (spec §7A.2 / §10 RL-9): `peaks skill sediment export` is
454
+ // an ALIAS of `peaks loop export` for one release cycle. Emit
455
+ // the deprecation warning to stderr so downstream tooling can
456
+ // upgrade. Future slice removes the alias.
457
+ process.stderr.write("warning: 'peaks skill sediment export' is deprecated; use 'peaks loop export' or 'peaks bee export' (peaks.bundle/1, spec §7A.2)\n");
453
458
  return { ok: true, data: { outPath } };
454
459
  }
455
460
  case "import": {
@@ -479,6 +484,10 @@ export async function runSediment(argv, { home }) {
479
484
  finally {
480
485
  db.close();
481
486
  }
487
+ // M7 (spec §7A.2): `peaks skill sediment import` is an ALIAS of
488
+ // `peaks loop import` for one release cycle. Emit the
489
+ // deprecation warning so downstream tooling can upgrade.
490
+ process.stderr.write("warning: 'peaks skill sediment import' is deprecated; use 'peaks loop import' or 'peaks bee import' (peaks.bundle/1, spec §7A.2)\n");
482
491
  return { ok: true, data: { asName: asName ?? basename(bundlePath) } };
483
492
  }
484
493
  case "gc-blobs": {
@@ -0,0 +1,67 @@
1
+ /**
2
+ * `peaks skill lint --category loop-engineering-readiness` — M6 / spec §7.5 + §8.4
3
+ *
4
+ * Loop Engineering readiness gate for any new peaks-* skill that
5
+ * participates in Loop Engineering (crystallization or evolution).
6
+ *
7
+ * Usage:
8
+ * peaks skill lint --category loop-engineering-readiness --path <skill-dir>
9
+ * peaks skill lint --category loop-engineering-readiness --path <skill-dir> --json
10
+ *
11
+ * The command:
12
+ * 1. Resolves <skill-dir>/SKILL.md (default if the path is a file).
13
+ * 2. Calls lintSkillLoopEngineeringReadiness() with the file contents.
14
+ * 3. Prints a structured JSON envelope via printResult (or a
15
+ * human-readable summary when --json is omitted).
16
+ * 4. Sets process.exitCode = 1 on a non-ok result, so the LLM
17
+ * gate can short-circuit a future peaks-* skill that fails the
18
+ * lint.
19
+ *
20
+ * The lint asserts three structural properties:
21
+ * 1. the SKILL.md references
22
+ * `.peaks/standards/loop-engineering-guidelines.md`;
23
+ * 2. the SKILL.md does not introduce a CLI verb the user is meant
24
+ * to type (only LLM-coordinated verbs from the sediment /
25
+ * asset / evolution surface are allowed; RL-1);
26
+ * 3. the SKILL.md does not introduce a JSON / manifest
27
+ * hand-authoring surface (RL-1).
28
+ *
29
+ * The user never types `peaks skill lint …` — the LLM runs it on
30
+ * the user's behalf. This command is also reachable as
31
+ * `peaks skill ready --category loop-engineering-readiness
32
+ * --path <skill-dir>`
33
+ * via a small alias added at the bottom of this file.
34
+ */
35
+ import type { Command } from 'commander';
36
+ import { type ProgramIO } from '../cli-helpers.js';
37
+ export declare const LOOP_ENGINEERING_READINESS_CATEGORY = "loop-engineering-readiness";
38
+ /**
39
+ * Resolve the SKILL.md file path from a `--path <skill-dir>` value.
40
+ *
41
+ * Accepts either:
42
+ * - a directory containing a SKILL.md (the common case for a
43
+ * peaks-* skill: `src/skills/<id>/`, `skills/<id>/`, etc.);
44
+ * - a direct file path to a SKILL.md.
45
+ *
46
+ * Returns the absolute path to the SKILL.md, or null if no SKILL.md
47
+ * is found.
48
+ */
49
+ export declare function resolveSkillMdPath(rawPath: string): string | null;
50
+ /**
51
+ * Pure helper: run the readiness lint against the SKILL.md text and
52
+ * return a CLI-shaped envelope. Kept separate from the Commander
53
+ * action so unit + integration tests can drive it without spawning
54
+ * a child process.
55
+ */
56
+ export declare function runReadinessLint(rawPath: string): {
57
+ ok: boolean;
58
+ code: string;
59
+ message: string;
60
+ data: {
61
+ path: string;
62
+ category: string;
63
+ findings: string[];
64
+ };
65
+ nextActions: string[];
66
+ };
67
+ export declare function registerSkillLoopEngineeringReadinessCommands(program: Command, io: ProgramIO): void;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * `peaks skill lint --category loop-engineering-readiness` — M6 / spec §7.5 + §8.4
3
+ *
4
+ * Loop Engineering readiness gate for any new peaks-* skill that
5
+ * participates in Loop Engineering (crystallization or evolution).
6
+ *
7
+ * Usage:
8
+ * peaks skill lint --category loop-engineering-readiness --path <skill-dir>
9
+ * peaks skill lint --category loop-engineering-readiness --path <skill-dir> --json
10
+ *
11
+ * The command:
12
+ * 1. Resolves <skill-dir>/SKILL.md (default if the path is a file).
13
+ * 2. Calls lintSkillLoopEngineeringReadiness() with the file contents.
14
+ * 3. Prints a structured JSON envelope via printResult (or a
15
+ * human-readable summary when --json is omitted).
16
+ * 4. Sets process.exitCode = 1 on a non-ok result, so the LLM
17
+ * gate can short-circuit a future peaks-* skill that fails the
18
+ * lint.
19
+ *
20
+ * The lint asserts three structural properties:
21
+ * 1. the SKILL.md references
22
+ * `.peaks/standards/loop-engineering-guidelines.md`;
23
+ * 2. the SKILL.md does not introduce a CLI verb the user is meant
24
+ * to type (only LLM-coordinated verbs from the sediment /
25
+ * asset / evolution surface are allowed; RL-1);
26
+ * 3. the SKILL.md does not introduce a JSON / manifest
27
+ * hand-authoring surface (RL-1).
28
+ *
29
+ * The user never types `peaks skill lint …` — the LLM runs it on
30
+ * the user's behalf. This command is also reachable as
31
+ * `peaks skill ready --category loop-engineering-readiness
32
+ * --path <skill-dir>`
33
+ * via a small alias added at the bottom of this file.
34
+ */
35
+ import { readFileSync, existsSync, statSync } from 'node:fs';
36
+ import { join, resolve } from 'node:path';
37
+ import { lintSkillLoopEngineeringReadiness, } from '../../services/standards/loop-engineering-readiness-lint.js';
38
+ import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
39
+ import { fail, ok } from '../../shared/result.js';
40
+ export const LOOP_ENGINEERING_READINESS_CATEGORY = 'loop-engineering-readiness';
41
+ /**
42
+ * Resolve the SKILL.md file path from a `--path <skill-dir>` value.
43
+ *
44
+ * Accepts either:
45
+ * - a directory containing a SKILL.md (the common case for a
46
+ * peaks-* skill: `src/skills/<id>/`, `skills/<id>/`, etc.);
47
+ * - a direct file path to a SKILL.md.
48
+ *
49
+ * Returns the absolute path to the SKILL.md, or null if no SKILL.md
50
+ * is found.
51
+ */
52
+ export function resolveSkillMdPath(rawPath) {
53
+ const abs = resolve(rawPath);
54
+ if (!existsSync(abs))
55
+ return null;
56
+ const st = statSync(abs);
57
+ if (st.isFile()) {
58
+ return abs.toLowerCase().endsWith('skill.md') ? abs : null;
59
+ }
60
+ if (st.isDirectory()) {
61
+ const candidate = join(abs, 'SKILL.md');
62
+ return existsSync(candidate) ? candidate : null;
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * Pure helper: run the readiness lint against the SKILL.md text and
68
+ * return a CLI-shaped envelope. Kept separate from the Commander
69
+ * action so unit + integration tests can drive it without spawning
70
+ * a child process.
71
+ */
72
+ export function runReadinessLint(rawPath) {
73
+ const skillMdPath = resolveSkillMdPath(rawPath);
74
+ if (!skillMdPath) {
75
+ return {
76
+ ok: false,
77
+ code: 'SKILL_FILE_NOT_FOUND',
78
+ message: `could not resolve SKILL.md from --path ${rawPath}`,
79
+ data: { path: rawPath, category: LOOP_ENGINEERING_READINESS_CATEGORY, findings: [] },
80
+ nextActions: [
81
+ 'Pass a directory containing SKILL.md or a direct path to a SKILL.md file.',
82
+ ],
83
+ };
84
+ }
85
+ const text = readFileSync(skillMdPath, 'utf-8');
86
+ const result = lintSkillLoopEngineeringReadiness(text);
87
+ if (result.ok) {
88
+ return {
89
+ ok: true,
90
+ code: 'SKILL_READINESS_OK',
91
+ message: 'SKILL.md passes the Loop Engineering readiness lint',
92
+ data: {
93
+ path: skillMdPath,
94
+ category: LOOP_ENGINEERING_READINESS_CATEGORY,
95
+ findings: [],
96
+ },
97
+ nextActions: [
98
+ 'The skill is allowed to participate in Loop Engineering (crystallization / evolution).',
99
+ ],
100
+ };
101
+ }
102
+ return {
103
+ ok: false,
104
+ code: 'SKILL_READINESS_FAILED',
105
+ message: `SKILL.md failed the Loop Engineering readiness lint (${result.findings.length} finding(s))`,
106
+ data: {
107
+ path: skillMdPath,
108
+ category: LOOP_ENGINEERING_READINESS_CATEGORY,
109
+ findings: result.findings,
110
+ },
111
+ nextActions: [
112
+ 'Address every finding listed in data.findings, then re-run the lint.',
113
+ 'See src/services/standards/loop-engineering-readiness-lint.ts for the rule definitions.',
114
+ 'See .peaks/standards/loop-engineering-guidelines.md for the red lines the lint enforces.',
115
+ ],
116
+ };
117
+ }
118
+ function lintAction(options, io) {
119
+ if (options.category !== LOOP_ENGINEERING_READINESS_CATEGORY) {
120
+ printResult(io, fail('skill.lint', 'UNKNOWN_LINT_CATEGORY', `only --category ${LOOP_ENGINEERING_READINESS_CATEGORY} is implemented in M6`, { category: options.category ?? null }, [
121
+ `Pass --category ${LOOP_ENGINEERING_READINESS_CATEGORY}.`,
122
+ ]), options.json);
123
+ process.exitCode = 1;
124
+ return;
125
+ }
126
+ if (!options.path) {
127
+ printResult(io, fail('skill.lint', 'MISSING_PATH', '--path <skill-dir> is required', { category: options.category }, ['Pass --path pointing at a peaks-* skill directory containing SKILL.md.']), options.json);
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+ try {
132
+ const envelope = runReadinessLint(options.path);
133
+ if (envelope.ok) {
134
+ printResult(io, ok('skill.lint', envelope.data, [], envelope.nextActions), options.json);
135
+ return;
136
+ }
137
+ printResult(io, fail('skill.lint', envelope.code, envelope.message, envelope.data, envelope.nextActions), options.json);
138
+ process.exitCode = 1;
139
+ }
140
+ catch (error) {
141
+ printResult(io, fail('skill.lint', 'SKILL_READINESS_ERROR', getErrorMessage(error), { path: options.path }, ['Verify the path is readable.']), options.json);
142
+ process.exitCode = 1;
143
+ }
144
+ }
145
+ export function registerSkillLoopEngineeringReadinessCommands(program, io) {
146
+ // `peaks skill lint --category loop-engineering-readiness --path <skill-dir>`
147
+ // — primary surface, named to match the spec §7.5 / §8.4 wording.
148
+ // We reuse the existing `skill` parent if one is registered; per
149
+ // the add-a-new-subcommand-check-for-existing-top-level-first rule,
150
+ // we never re-create `skill` when it's already there.
151
+ const existingSkill = program.commands.find((c) => c.name() === 'skill');
152
+ const skill = existingSkill ?? program
153
+ .command('skill')
154
+ .description('skill operations (M6: lint --category loop-engineering-readiness)');
155
+ addJsonOption(skill
156
+ .command('lint')
157
+ .description(`M6: lint a peaks-* SKILL.md against a category. Currently supports --category ${LOOP_ENGINEERING_READINESS_CATEGORY} (spec §7.5 / §8.4 / RL-8).`)
158
+ .option('--category <name>', `lint category; the M6 implementation only supports ${LOOP_ENGINEERING_READINESS_CATEGORY}`, LOOP_ENGINEERING_READINESS_CATEGORY)
159
+ .requiredOption('--path <skill-dir>', 'path to a peaks-* skill directory (containing SKILL.md) or directly to a SKILL.md file')).action((options) => lintAction(options, io));
160
+ // `peaks skill ready --category loop-engineering-readiness --path <skill-dir>`
161
+ // — alias verb chosen by M6 for human-facing callouts. It points at
162
+ // the same handler; both verbs are first-class so users (LLM
163
+ // proxies) can pick whichever phrasing fits the conversation.
164
+ // Per the spec, the user never types either verb; the LLM runs the
165
+ // CLI on the user's behalf.
166
+ skill
167
+ .command('ready')
168
+ .description(`M6 alias for \`peaks skill lint --category ${LOOP_ENGINEERING_READINESS_CATEGORY}\`; same flags, same exit codes.`)
169
+ .option('--category <name>', `lint category; the M6 implementation only supports ${LOOP_ENGINEERING_READINESS_CATEGORY}`, LOOP_ENGINEERING_READINESS_CATEGORY)
170
+ .requiredOption('--path <skill-dir>', 'path to a peaks-* skill directory or SKILL.md file')
171
+ .option('--json', 'print machine-readable JSON envelope')
172
+ .action((options) => lintAction(options, io));
173
+ }
@@ -52,8 +52,12 @@ import { registerClassifyCommands } from './commands/classify-classify-commands.
52
52
  import { registerContextCommands } from './commands/context-commands.js';
53
53
  import { registerContractCommands } from './commands/contract-commands.js';
54
54
  import { registerSkillConformanceCommands } from './commands/skill-conformance-commands.js';
55
+ import { registerSkillLoopEngineeringReadinessCommands } from './commands/skill-loop-engineering-readiness-commands.js';
55
56
  import { registerLoopCommands } from './commands/loop-commands.js';
56
57
  import { registerWorkflowEvalCommands } from './commands/loop-eval-commands.js';
58
+ import { registerEvolutionCommands } from './commands/evolution-commands.js';
59
+ import { registerAssetCommands } from './commands/asset-commands.js';
60
+ import { registerBeeCommands } from './commands/bee-commands.js';
57
61
  import { registerAgentCommands } from './commands/agent-commands.js';
58
62
  import { registerUpgradeCommands } from './commands/upgrade-commands.js';
59
63
  import { registerCodeReviewCommands } from './commands/code-review-commands.js';
@@ -289,6 +293,10 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
289
293
  registerContextCommands(program, io);
290
294
  // Slice #12: peaks skills:audit-conformance — skill family alignment pass.
291
295
  registerSkillConformanceCommands(program, io);
296
+ // M6 (2026-07-07 spec §7.5 / §8.4): peaks skill lint --category
297
+ // loop-engineering-readiness — readiness gate for any new peaks-*
298
+ // skill that participates in Loop Engineering.
299
+ registerSkillLoopEngineeringReadinessCommands(program, io);
292
300
  // Slice #13: peaks swarm * — additional subcommands (pipeline /
293
301
  // dispatch / verify / loop) are added inline in workflow-commands.ts
294
302
  // alongside the existing swarm.plan. This avoids the duplicate top-level
@@ -299,6 +307,15 @@ Run peaks (no arguments) for a quickstart. You likely want one of:
299
307
  // + peaks loop eval. Wraps the existing workflow + loop groups so the
300
308
  // add-a-new-subcommand-check-for-existing-top-level-first rule is honoured.
301
309
  registerWorkflowEvalCommands(program, io);
310
+ // M4 (2026-07-07 spec): `peaks evolution propose|evaluate|revert|status|mark-keep`
311
+ // — Darwin-style ratchet CLI surface.
312
+ registerEvolutionCommands(program, io);
313
+ // M5 (2026-07-07 spec): `peaks asset crystallize|dispose|status` —
314
+ // umbrella cross-asset crystallization surface.
315
+ registerAssetCommands(program, io);
316
+ // M7 (2026-07-07 spec §7A.2 / §10 RL-9): `peaks bee export|import`
317
+ // — cross-user share surface for bee_release rows.
318
+ registerBeeCommands(program, io);
302
319
  // Slice: ECC 64 agents soft-optional (per spec §7.2 line 818).
303
320
  registerAgentCommands(program, io);
304
321
  // Slice: 1.x → 2.0 umbrella (per "one-key completion" + "minimal-user-operation" tenets).