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
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* peaks loop * CLI (Slice #14
|
|
2
|
+
* peaks loop * CLI (Slice #14 + M7 add).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Slices:
|
|
5
|
+
* #14 sub-features (L4 Agent Loop integration): distill / preflight /
|
|
6
|
+
* detect-pattern / check-consistency / peaks goal compose.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
17
|
-
*
|
|
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
|
+
}
|
package/dist/src/cli/program.js
CHANGED
|
@@ -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).
|