picopilot 0.0.0 → 0.1.0
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/LICENSE +661 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +4 -0
- package/dist/bin.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +59 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/audio.d.ts +17 -0
- package/dist/commands/audio.d.ts.map +1 -0
- package/dist/commands/audio.js +315 -0
- package/dist/commands/audio.js.map +1 -0
- package/dist/commands/export.d.ts +36 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +257 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/gfx.d.ts +28 -0
- package/dist/commands/gfx.d.ts.map +1 -0
- package/dist/commands/gfx.js +521 -0
- package/dist/commands/gfx.js.map +1 -0
- package/dist/commands/init.d.ts +34 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +151 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +20 -0
- package/dist/commands/lint.d.ts.map +1 -0
- package/dist/commands/lint.js +90 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/minify.d.ts +20 -0
- package/dist/commands/minify.d.ts.map +1 -0
- package/dist/commands/minify.js +129 -0
- package/dist/commands/minify.js.map +1 -0
- package/dist/commands/music.d.ts +13 -0
- package/dist/commands/music.d.ts.map +1 -0
- package/dist/commands/music.js +241 -0
- package/dist/commands/music.js.map +1 -0
- package/dist/commands/playtest.d.ts +29 -0
- package/dist/commands/playtest.d.ts.map +1 -0
- package/dist/commands/playtest.js +631 -0
- package/dist/commands/playtest.js.map +1 -0
- package/dist/commands/run.d.ts +30 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +202 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/serve.d.ts +50 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +213 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/commands/sfx.d.ts +13 -0
- package/dist/commands/sfx.d.ts.map +1 -0
- package/dist/commands/sfx.js +254 -0
- package/dist/commands/sfx.js.map +1 -0
- package/dist/commands/tokens.d.ts +36 -0
- package/dist/commands/tokens.d.ts.map +1 -0
- package/dist/commands/tokens.js +129 -0
- package/dist/commands/tokens.js.map +1 -0
- package/dist/commands/verify.d.ts +57 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +213 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +24 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/engine/audio/index.d.ts +14 -0
- package/dist/engine/audio/index.d.ts.map +1 -0
- package/dist/engine/audio/index.js +14 -0
- package/dist/engine/audio/index.js.map +1 -0
- package/dist/engine/audio/music.d.ts +116 -0
- package/dist/engine/audio/music.d.ts.map +1 -0
- package/dist/engine/audio/music.js +138 -0
- package/dist/engine/audio/music.js.map +1 -0
- package/dist/engine/audio/section.d.ts +34 -0
- package/dist/engine/audio/section.d.ts.map +1 -0
- package/dist/engine/audio/section.js +67 -0
- package/dist/engine/audio/section.js.map +1 -0
- package/dist/engine/audio/sfx.d.ts +141 -0
- package/dist/engine/audio/sfx.d.ts.map +1 -0
- package/dist/engine/audio/sfx.js +506 -0
- package/dist/engine/audio/sfx.js.map +1 -0
- package/dist/engine/cart/cart.d.ts +75 -0
- package/dist/engine/cart/cart.d.ts.map +1 -0
- package/dist/engine/cart/cart.js +153 -0
- package/dist/engine/cart/cart.js.map +1 -0
- package/dist/engine/cart/errors.d.ts +29 -0
- package/dist/engine/cart/errors.d.ts.map +1 -0
- package/dist/engine/cart/errors.js +26 -0
- package/dist/engine/cart/errors.js.map +1 -0
- package/dist/engine/cart/gfx.d.ts +79 -0
- package/dist/engine/cart/gfx.d.ts.map +1 -0
- package/dist/engine/cart/gfx.js +171 -0
- package/dist/engine/cart/gfx.js.map +1 -0
- package/dist/engine/cart/index.d.ts +15 -0
- package/dist/engine/cart/index.d.ts.map +1 -0
- package/dist/engine/cart/index.js +15 -0
- package/dist/engine/cart/index.js.map +1 -0
- package/dist/engine/cart/map.d.ts +46 -0
- package/dist/engine/cart/map.d.ts.map +1 -0
- package/dist/engine/cart/map.js +118 -0
- package/dist/engine/cart/map.js.map +1 -0
- package/dist/engine/config/config.d.ts +74 -0
- package/dist/engine/config/config.d.ts.map +1 -0
- package/dist/engine/config/config.js +103 -0
- package/dist/engine/config/config.js.map +1 -0
- package/dist/engine/config/index.d.ts +7 -0
- package/dist/engine/config/index.d.ts.map +1 -0
- package/dist/engine/config/index.js +7 -0
- package/dist/engine/config/index.js.map +1 -0
- package/dist/engine/gfx/grid.d.ts +51 -0
- package/dist/engine/gfx/grid.d.ts.map +1 -0
- package/dist/engine/gfx/grid.js +116 -0
- package/dist/engine/gfx/grid.js.map +1 -0
- package/dist/engine/gfx/index.d.ts +17 -0
- package/dist/engine/gfx/index.d.ts.map +1 -0
- package/dist/engine/gfx/index.js +17 -0
- package/dist/engine/gfx/index.js.map +1 -0
- package/dist/engine/gfx/label.d.ts +34 -0
- package/dist/engine/gfx/label.d.ts.map +1 -0
- package/dist/engine/gfx/label.js +190 -0
- package/dist/engine/gfx/label.js.map +1 -0
- package/dist/engine/gfx/overlap.d.ts +42 -0
- package/dist/engine/gfx/overlap.d.ts.map +1 -0
- package/dist/engine/gfx/overlap.js +57 -0
- package/dist/engine/gfx/overlap.js.map +1 -0
- package/dist/engine/gfx/palette.d.ts +29 -0
- package/dist/engine/gfx/palette.d.ts.map +1 -0
- package/dist/engine/gfx/palette.js +46 -0
- package/dist/engine/gfx/palette.js.map +1 -0
- package/dist/engine/gfx/png.d.ts +31 -0
- package/dist/engine/gfx/png.d.ts.map +1 -0
- package/dist/engine/gfx/png.js +165 -0
- package/dist/engine/gfx/png.js.map +1 -0
- package/dist/engine/gfx/render.d.ts +42 -0
- package/dist/engine/gfx/render.d.ts.map +1 -0
- package/dist/engine/gfx/render.js +72 -0
- package/dist/engine/gfx/render.js.map +1 -0
- package/dist/engine/index.d.ts +22 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +22 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/pico8/adapter.d.ts +267 -0
- package/dist/engine/pico8/adapter.d.ts.map +1 -0
- package/dist/engine/pico8/adapter.js +37 -0
- package/dist/engine/pico8/adapter.js.map +1 -0
- package/dist/engine/pico8/drive.d.ts +308 -0
- package/dist/engine/pico8/drive.d.ts.map +1 -0
- package/dist/engine/pico8/drive.js +581 -0
- package/dist/engine/pico8/drive.js.map +1 -0
- package/dist/engine/pico8/harness.d.ts +146 -0
- package/dist/engine/pico8/harness.d.ts.map +1 -0
- package/dist/engine/pico8/harness.js +251 -0
- package/dist/engine/pico8/harness.js.map +1 -0
- package/dist/engine/pico8/index.d.ts +21 -0
- package/dist/engine/pico8/index.d.ts.map +1 -0
- package/dist/engine/pico8/index.js +21 -0
- package/dist/engine/pico8/index.js.map +1 -0
- package/dist/engine/pico8/sentinel.d.ts +63 -0
- package/dist/engine/pico8/sentinel.d.ts.map +1 -0
- package/dist/engine/pico8/sentinel.js +86 -0
- package/dist/engine/pico8/sentinel.js.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts +10 -0
- package/dist/engine/pico8/session-daemon-entry.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-entry.js +11 -0
- package/dist/engine/pico8/session-daemon-entry.js.map +1 -0
- package/dist/engine/pico8/session-daemon-main.d.ts +44 -0
- package/dist/engine/pico8/session-daemon-main.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon-main.js +71 -0
- package/dist/engine/pico8/session-daemon-main.js.map +1 -0
- package/dist/engine/pico8/session-daemon.d.ts +68 -0
- package/dist/engine/pico8/session-daemon.d.ts.map +1 -0
- package/dist/engine/pico8/session-daemon.js +264 -0
- package/dist/engine/pico8/session-daemon.js.map +1 -0
- package/dist/engine/pico8/session.d.ts +140 -0
- package/dist/engine/pico8/session.d.ts.map +1 -0
- package/dist/engine/pico8/session.js +250 -0
- package/dist/engine/pico8/session.js.map +1 -0
- package/dist/engine/pico8/shell.d.ts +103 -0
- package/dist/engine/pico8/shell.d.ts.map +1 -0
- package/dist/engine/pico8/shell.js +324 -0
- package/dist/engine/pico8/shell.js.map +1 -0
- package/dist/engine/pico8/supervisor.d.ts +137 -0
- package/dist/engine/pico8/supervisor.d.ts.map +1 -0
- package/dist/engine/pico8/supervisor.js +140 -0
- package/dist/engine/pico8/supervisor.js.map +1 -0
- package/dist/engine/scaffold/index.d.ts +8 -0
- package/dist/engine/scaffold/index.d.ts.map +1 -0
- package/dist/engine/scaffold/index.js +8 -0
- package/dist/engine/scaffold/index.js.map +1 -0
- package/dist/engine/scaffold/scaffold.d.ts +49 -0
- package/dist/engine/scaffold/scaffold.d.ts.map +1 -0
- package/dist/engine/scaffold/scaffold.js +217 -0
- package/dist/engine/scaffold/scaffold.js.map +1 -0
- package/dist/engine/shrinko/adapter.d.ts +167 -0
- package/dist/engine/shrinko/adapter.d.ts.map +1 -0
- package/dist/engine/shrinko/adapter.js +107 -0
- package/dist/engine/shrinko/adapter.js.map +1 -0
- package/dist/engine/shrinko/index.d.ts +13 -0
- package/dist/engine/shrinko/index.d.ts.map +1 -0
- package/dist/engine/shrinko/index.js +13 -0
- package/dist/engine/shrinko/index.js.map +1 -0
- package/dist/engine/shrinko/shell.d.ts +69 -0
- package/dist/engine/shrinko/shell.d.ts.map +1 -0
- package/dist/engine/shrinko/shell.js +156 -0
- package/dist/engine/shrinko/shell.js.map +1 -0
- package/dist/engine/skills/index.d.ts +8 -0
- package/dist/engine/skills/index.d.ts.map +1 -0
- package/dist/engine/skills/index.js +8 -0
- package/dist/engine/skills/index.js.map +1 -0
- package/dist/engine/skills/skills.d.ts +104 -0
- package/dist/engine/skills/skills.d.ts.map +1 -0
- package/dist/engine/skills/skills.js +149 -0
- package/dist/engine/skills/skills.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +19 -0
- package/dist/version.js.map +1 -0
- package/package.json +48 -2
- package/src/bin.ts +4 -0
- package/src/cli.ts +61 -0
- package/src/commands/audio.test.ts +280 -0
- package/src/commands/audio.ts +410 -0
- package/src/commands/export.test.ts +310 -0
- package/src/commands/export.ts +316 -0
- package/src/commands/gfx-io.test.ts +178 -0
- package/src/commands/gfx.test.ts +470 -0
- package/src/commands/gfx.ts +645 -0
- package/src/commands/init.test.ts +460 -0
- package/src/commands/init.ts +193 -0
- package/src/commands/lint.test.ts +137 -0
- package/src/commands/lint.ts +117 -0
- package/src/commands/minify.test.ts +166 -0
- package/src/commands/minify.ts +156 -0
- package/src/commands/music.test.ts +272 -0
- package/src/commands/music.ts +288 -0
- package/src/commands/playtest.test.ts +317 -0
- package/src/commands/playtest.ts +773 -0
- package/src/commands/run.test.ts +238 -0
- package/src/commands/run.ts +259 -0
- package/src/commands/serve.test.ts +218 -0
- package/src/commands/serve.ts +272 -0
- package/src/commands/sfx.test.ts +349 -0
- package/src/commands/sfx.ts +300 -0
- package/src/commands/tokens.test.ts +212 -0
- package/src/commands/tokens.ts +168 -0
- package/src/commands/verify.test.ts +238 -0
- package/src/commands/verify.ts +256 -0
- package/src/commands/version.test.ts +70 -0
- package/src/commands/version.ts +24 -0
- package/src/engine/audio/index.ts +47 -0
- package/src/engine/audio/music.test.ts +188 -0
- package/src/engine/audio/music.ts +218 -0
- package/src/engine/audio/section.test.ts +124 -0
- package/src/engine/audio/section.ts +79 -0
- package/src/engine/audio/sfx.test.ts +410 -0
- package/src/engine/audio/sfx.ts +694 -0
- package/src/engine/cart/cart.test.ts +304 -0
- package/src/engine/cart/cart.ts +211 -0
- package/src/engine/cart/errors.ts +39 -0
- package/src/engine/cart/fixtures/crlf.p8 +6 -0
- package/src/engine/cart/fixtures/full.p8 +174 -0
- package/src/engine/cart/fixtures/header-only.p8 +2 -0
- package/src/engine/cart/fixtures/malformed-no-header.p8 +2 -0
- package/src/engine/cart/fixtures/minimal.p8 +10 -0
- package/src/engine/cart/fixtures/no-trailing-newline.p8 +6 -0
- package/src/engine/cart/gfx.ts +195 -0
- package/src/engine/cart/index.ts +27 -0
- package/src/engine/cart/map.ts +134 -0
- package/src/engine/config/config.ts +115 -0
- package/src/engine/config/index.ts +12 -0
- package/src/engine/gfx/grid.test.ts +128 -0
- package/src/engine/gfx/grid.ts +136 -0
- package/src/engine/gfx/index.ts +34 -0
- package/src/engine/gfx/label.test.ts +105 -0
- package/src/engine/gfx/label.ts +229 -0
- package/src/engine/gfx/overlap.test.ts +71 -0
- package/src/engine/gfx/overlap.ts +66 -0
- package/src/engine/gfx/palette.test.ts +35 -0
- package/src/engine/gfx/palette.ts +54 -0
- package/src/engine/gfx/png.test.ts +111 -0
- package/src/engine/gfx/png.ts +196 -0
- package/src/engine/gfx/render.test.ts +180 -0
- package/src/engine/gfx/render.ts +106 -0
- package/src/engine/index.ts +21 -0
- package/src/engine/pico8/adapter.ts +303 -0
- package/src/engine/pico8/drive.test.ts +415 -0
- package/src/engine/pico8/drive.ts +729 -0
- package/src/engine/pico8/harness.test.ts +181 -0
- package/src/engine/pico8/harness.ts +354 -0
- package/src/engine/pico8/index.ts +20 -0
- package/src/engine/pico8/sentinel.test.ts +80 -0
- package/src/engine/pico8/sentinel.ts +93 -0
- package/src/engine/pico8/session-daemon-entry.ts +12 -0
- package/src/engine/pico8/session-daemon-main.ts +98 -0
- package/src/engine/pico8/session-daemon.test.ts +212 -0
- package/src/engine/pico8/session-daemon.ts +306 -0
- package/src/engine/pico8/session.test.ts +239 -0
- package/src/engine/pico8/session.ts +321 -0
- package/src/engine/pico8/shell.test.ts +583 -0
- package/src/engine/pico8/shell.ts +412 -0
- package/src/engine/pico8/supervisor.test.ts +105 -0
- package/src/engine/pico8/supervisor.ts +197 -0
- package/src/engine/scaffold/index.ts +15 -0
- package/src/engine/scaffold/scaffold.ts +236 -0
- package/src/engine/shrinko/adapter.ts +241 -0
- package/src/engine/shrinko/fixtures/count-no-compressed.txt +2 -0
- package/src/engine/shrinko/fixtures/count-over-budget.txt +3 -0
- package/src/engine/shrinko/fixtures/count-under-budget.txt +3 -0
- package/src/engine/shrinko/index.ts +33 -0
- package/src/engine/shrinko/shell.ts +224 -0
- package/src/engine/shrinko/shrinko.test.ts +182 -0
- package/src/engine/skills/index.ts +14 -0
- package/src/engine/skills/skills.test.ts +376 -0
- package/src/engine/skills/skills.ts +205 -0
- package/src/index.ts +13 -0
- package/src/skills/game-design-reference/SKILL.md +70 -0
- package/src/skills/game-jam/SKILL.md +45 -0
- package/src/skills/picopilot-art/SKILL.md +62 -0
- package/src/skills/picopilot-audio/SKILL.md +194 -0
- package/src/skills/picopilot-code/SKILL.md +97 -0
- package/src/skills/picopilot-code/reference/README.md +42 -0
- package/src/skills/picopilot-code/reference/mode7-racing.md +95 -0
- package/src/skills/picopilot-code/reference/platformer.md +119 -0
- package/src/skills/picopilot-code/reference/puzzle-grid.md +106 -0
- package/src/skills/picopilot-code/reference/rpg-menus-dialog.md +122 -0
- package/src/skills/picopilot-code/reference/top-down-adventure.md +117 -0
- package/src/skills/picopilot-code/reference/twin-stick-arcade.md +128 -0
- package/src/skills/picopilot-debug/SKILL.md +195 -0
- package/src/skills/picopilot-overview/SKILL.md +52 -0
- package/src/version.ts +22 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { Cli, z } from 'incur';
|
|
4
|
+
import { AudioMmlError, mergeSfxRow, mmlToSfxRow, PITCH_MAX, SFX_MAX_ROWS, SFX_SLOT_COUNT, } from '../engine/audio/index.js';
|
|
5
|
+
import { Cart, CartParseError } from '../engine/cart/index.js';
|
|
6
|
+
/** Resolves a path argument to an absolute path (relative to cwd). */
|
|
7
|
+
function resolvePath(p) {
|
|
8
|
+
return isAbsolute(p) ? p : resolve(process.cwd(), p);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Loads + parses a cart, returning either the parsed cart or a structured reason
|
|
12
|
+
* the caller turns into an `error(...)`. Mirrors the gfx command's loader.
|
|
13
|
+
*/
|
|
14
|
+
function loadCart(cartPath) {
|
|
15
|
+
if (!existsSync(cartPath)) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
code: 'cart-not-found',
|
|
19
|
+
message: `no cart at ${cartPath}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const text = readFileSync(cartPath, 'utf8');
|
|
23
|
+
try {
|
|
24
|
+
return { ok: true, cart: Cart.parse(text) };
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
if (e instanceof CartParseError) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
code: 'cart-parse-failed',
|
|
31
|
+
message: `cart at ${cartPath} does not parse (${e.code}): ${e.message}`,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Builds the `sfx` command group. Currently just `sfx from-mml` (the
|
|
39
|
+
* picopilot-MML to `__sfx__` transpiler + cart merge).
|
|
40
|
+
*/
|
|
41
|
+
function buildSfxGroup() {
|
|
42
|
+
const sfx = Cli.create('sfx', {
|
|
43
|
+
description: 'Compose SFX as text: transpile picopilot-MML into a cart __sfx__ slot. Durations are TRACKER ROWS at one SFX speed, NOT tempo/BPM (PICO-8 has no per-note duration).',
|
|
44
|
+
});
|
|
45
|
+
sfx.command('from-mml', {
|
|
46
|
+
description: 'Transpile picopilot-MML text into one __sfx__ slot (0-63) and merge it into the cart (only that slot changes). picopilot-MML is TRACKER-ROW based: a length is a ROW COUNT (l4 = 4 rows), NOT a musical note value, and there is NO tempo/BPM (PICO-8 measures in ticks/row via `s<N>` speed). Notes carry per-note waveform (@0-7, @8-f = custom SFX-instrument), volume (v0-7), and effect (e0-7) that a score notation cannot express. SFX-level FILTER directives reach DESIGNED sounds (explosion/engine/pad): !dampen[2] (low-pass body), !reverb (echo tail), !noiz (@6 white noise), !buzz, !detune1/!detune2. Out-of-range pitch (below C0 / above D#5), >32 rows, and a bad filter level are refused loudly, never silently clamped.',
|
|
47
|
+
args: z.object({
|
|
48
|
+
slot: z.coerce
|
|
49
|
+
.number()
|
|
50
|
+
.int()
|
|
51
|
+
.min(0)
|
|
52
|
+
.max(SFX_SLOT_COUNT - 1)
|
|
53
|
+
.describe(`The target SFX slot 0-${SFX_SLOT_COUNT - 1} to write.`),
|
|
54
|
+
mml: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe('The picopilot-MML source (e.g. "s16 @1 v6 c d e f"). Omit and pass --file to read from a file instead. Modal state: @ waveform, v volume, e effect, o octave, l default length (all sticky). Durations are ROWS: c4 = 4 rows (held via tie rows). Loop with {..}; a lone { marks the pattern LEN. SFX filters (per-SFX, place anywhere): !dampen/!dampen2, !reverb, !noiz, !buzz, !detune1/!detune2 (e.g. "!dampen !reverb @6 v7 o3 e c o1 g" = an explosion).'),
|
|
58
|
+
}),
|
|
59
|
+
options: z.object({
|
|
60
|
+
cart: z
|
|
61
|
+
.string()
|
|
62
|
+
.default('main.p8')
|
|
63
|
+
.describe('The .p8 cart to merge into. Defaults to main.p8 in the current folder. (An option, not a positional, so it never collides with an omitted `mml` when using --file.)'),
|
|
64
|
+
file: z
|
|
65
|
+
.string()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe('Read the picopilot-MML from this file instead of the `mml` argument (for longer sources).'),
|
|
68
|
+
}),
|
|
69
|
+
output: z.object({
|
|
70
|
+
slot: z.number().describe('The SFX slot that was written.'),
|
|
71
|
+
cart: z
|
|
72
|
+
.string()
|
|
73
|
+
.describe('The absolute path of the cart that was written.'),
|
|
74
|
+
rows: z
|
|
75
|
+
.number()
|
|
76
|
+
.describe('The number of tracker rows the MML produced (0-32).'),
|
|
77
|
+
speed: z
|
|
78
|
+
.number()
|
|
79
|
+
.describe('The SFX speed (ticks/row) written into the header.'),
|
|
80
|
+
loopStart: z
|
|
81
|
+
.number()
|
|
82
|
+
.describe('The header loop-start value (row index, or a LEN).'),
|
|
83
|
+
loopEnd: z
|
|
84
|
+
.number()
|
|
85
|
+
.describe('The header loop-end value (0 = off or the LEN special case).'),
|
|
86
|
+
filters: z
|
|
87
|
+
.number()
|
|
88
|
+
.describe('The per-SFX FILTER byte (first header byte): the OR of the selected filters, 0 = none.'),
|
|
89
|
+
row: z
|
|
90
|
+
.string()
|
|
91
|
+
.describe('The 168-char __sfx__ text row that was merged.'),
|
|
92
|
+
warnings: z
|
|
93
|
+
.array(z.string())
|
|
94
|
+
.describe('Advisory warnings (e.g. a dotted length whose row count was rounded).'),
|
|
95
|
+
}),
|
|
96
|
+
examples: [
|
|
97
|
+
{
|
|
98
|
+
description: 'A simple square-wave arpeggio into slot 0',
|
|
99
|
+
args: { slot: 0, mml: 's8 @3 v6 c e g > c' },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
description: 'A held note (4 rows) with vibrato into slot 5 of a cart',
|
|
103
|
+
args: { slot: 5, mml: 's16 @1 v5 e2 c4' },
|
|
104
|
+
options: { cart: 'game.p8' },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
description: 'A DESIGNED explosion: DAMPEN body + REVERB tail on @6 noise',
|
|
108
|
+
args: { slot: 3, mml: 's7 !dampen2 !reverb @6 v7 o3 e c o1 g c' },
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
description: 'Read a longer source from a file',
|
|
112
|
+
args: { slot: 1 },
|
|
113
|
+
options: { file: 'lead.mml' },
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
run({ args, options, error, ok }) {
|
|
117
|
+
// Resolve the MML source: the `mml` arg OR `--file` (exactly one). This is
|
|
118
|
+
// the input surface; both are pure text, shrinko-free + PICO-8-free.
|
|
119
|
+
let mml;
|
|
120
|
+
if (options.file !== undefined) {
|
|
121
|
+
if (args.mml !== undefined) {
|
|
122
|
+
return error({
|
|
123
|
+
code: 'ambiguous-input',
|
|
124
|
+
message: 'pass EITHER the mml argument OR --file, not both (they are two ways to give the same source).',
|
|
125
|
+
exitCode: 1,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const filePath = resolvePath(options.file);
|
|
129
|
+
if (!existsSync(filePath)) {
|
|
130
|
+
return error({
|
|
131
|
+
code: 'mml-file-not-found',
|
|
132
|
+
message: `no picopilot-MML file at ${filePath}`,
|
|
133
|
+
exitCode: 1,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
mml = readFileSync(filePath, 'utf8');
|
|
137
|
+
}
|
|
138
|
+
else if (args.mml !== undefined) {
|
|
139
|
+
mml = args.mml;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
return error({
|
|
143
|
+
code: 'no-mml',
|
|
144
|
+
message: 'no picopilot-MML source: pass it as the `mml` argument or via --file.',
|
|
145
|
+
exitCode: 1,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// Transpile FIRST: a bad MML is a caller error and must never touch the
|
|
149
|
+
// cart (no bytes change on a refusal), mirroring `gfx set`'s grid-first
|
|
150
|
+
// discipline.
|
|
151
|
+
let result;
|
|
152
|
+
try {
|
|
153
|
+
result = mmlToSfxRow(mml);
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
if (e instanceof AudioMmlError) {
|
|
157
|
+
// The structured refusals + parse errors surface as incur's error
|
|
158
|
+
// envelope with a nonzero exit (never a silent clamp). The CTA points
|
|
159
|
+
// at the range/cap/level the author must respect.
|
|
160
|
+
const cta = e.code === 'audio-mml-pitch-out-of-range'
|
|
161
|
+
? {
|
|
162
|
+
description: 'PICO-8 pitch is a hard range; author within it:',
|
|
163
|
+
commands: [
|
|
164
|
+
{
|
|
165
|
+
command: 'sfx from-mml <slot> "<mml>"',
|
|
166
|
+
description: `Use octaves that keep every note within C0..D#5 (0..${PITCH_MAX}).`,
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
}
|
|
170
|
+
: e.code === 'audio-mml-sfx-overflow'
|
|
171
|
+
? {
|
|
172
|
+
description: `One SFX holds at most ${SFX_MAX_ROWS} rows; split longer melodies:`,
|
|
173
|
+
commands: [
|
|
174
|
+
{
|
|
175
|
+
command: 'sfx from-mml <other-slot> "<rest>"',
|
|
176
|
+
description: 'Author the remainder as another SFX, then arrange both with `music from-patterns`.',
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
: e.code === 'audio-mml-filter-level-out-of-range'
|
|
181
|
+
? {
|
|
182
|
+
description: 'The SFX filters + their valid levels:',
|
|
183
|
+
commands: [
|
|
184
|
+
{
|
|
185
|
+
command: 'sfx from-mml <slot> "!dampen[2] !reverb !noiz !buzz !detune1/!detune2 ..."',
|
|
186
|
+
description: '!dampen has 2 levels (!dampen/!dampen2); !reverb/!noiz/!buzz are on/off; !detune needs 1 or 2.',
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
}
|
|
190
|
+
: undefined;
|
|
191
|
+
return error({
|
|
192
|
+
code: e.code,
|
|
193
|
+
message: e.message,
|
|
194
|
+
exitCode: 1,
|
|
195
|
+
...(cta ? { cta } : {}),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
throw e;
|
|
199
|
+
}
|
|
200
|
+
const cartPath = resolvePath(options.cart);
|
|
201
|
+
const loaded = loadCart(cartPath);
|
|
202
|
+
if (!loaded.ok) {
|
|
203
|
+
return error({ code: loaded.code, message: loaded.message, exitCode: 1 });
|
|
204
|
+
}
|
|
205
|
+
// Merge into the target slot ONLY, through the cart model, so every other
|
|
206
|
+
// section (and every other SFX slot) stays byte-identical.
|
|
207
|
+
mergeSfxRow(loaded.cart, args.slot, result.row);
|
|
208
|
+
writeFileSync(cartPath, loaded.cart.serialize());
|
|
209
|
+
const warnings = result.roundingWarnings.map((w) => `length "${w.token}" = ${w.exactRows} rows rounded to ${w.roundedRows} (rows are whole; PICO-8 has no fractional-length notes).`);
|
|
210
|
+
// CTA toward the verify/hear loop: a merged SFX is next confirmed by
|
|
211
|
+
// verify (static integrity) and heard via `audio render`.
|
|
212
|
+
return ok({
|
|
213
|
+
slot: args.slot,
|
|
214
|
+
cart: cartPath,
|
|
215
|
+
rows: result.rows,
|
|
216
|
+
speed: result.speed,
|
|
217
|
+
loopStart: result.loopStart,
|
|
218
|
+
loopEnd: result.loopEnd,
|
|
219
|
+
filters: result.filters,
|
|
220
|
+
row: result.row,
|
|
221
|
+
warnings,
|
|
222
|
+
}, {
|
|
223
|
+
cta: {
|
|
224
|
+
description: 'The SFX is merged. Confirm it, then hear it:',
|
|
225
|
+
commands: [
|
|
226
|
+
{
|
|
227
|
+
command: 'verify',
|
|
228
|
+
description: 'Static gate: the cart still parses + is within budget.',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
command: 'audio render',
|
|
232
|
+
description: 'Record a WAV to hear the result (real-time capture, requires PICO-8).',
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
return sfx;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Registers the `sfx` command group (`sfx from-mml`) on the root CLI.
|
|
243
|
+
*
|
|
244
|
+
* `sfx` is a shrinko-FREE and PICO-8-FREE command group: it transpiles
|
|
245
|
+
* picopilot-MML to `__sfx__` and merges it through the cart model entirely in
|
|
246
|
+
* TS, so it works with both absent and never spawns a child process. The
|
|
247
|
+
* picopilot-MML dialect (ADR-0005 + the audio spike finding) is TRACKER-ROW
|
|
248
|
+
* based: durations are row counts at one SFX speed, NOT tempo (ADR-0008), and
|
|
249
|
+
* out-of-range pitch / >32 rows are structured refusals, never silent clamps.
|
|
250
|
+
*/
|
|
251
|
+
export function registerSfx(cli) {
|
|
252
|
+
cli.command(buildSfxGroup());
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=sfx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sfx.js","sourceRoot":"","sources":["../../src/commands/sfx.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type Cli } from 'incur';
|
|
2
|
+
import { type ShrinkoAdapter } from '../engine/shrinko/index.js';
|
|
3
|
+
/** PICO-8's Lua token budget. A cart over this will not load in PICO-8 (US #3). */
|
|
4
|
+
export declare const TOKEN_BUDGET = 8192;
|
|
5
|
+
/**
|
|
6
|
+
* Builds the {@link ShrinkoAdapter} a command uses, given the incur-resolved
|
|
7
|
+
* env. The default is the shell-out {@link ShellShrinkoAdapter}, whose child
|
|
8
|
+
* process inherits ONLY this `env` (its `PATH` is the test isolation lever).
|
|
9
|
+
*
|
|
10
|
+
* A test passes its own factory to {@link registerTokens} to SWAP a stub
|
|
11
|
+
* adapter (the seam-swap contract): the command's public output/exit must be
|
|
12
|
+
* unchanged whether the real shell adapter or a native-TS stub is behind the
|
|
13
|
+
* seam, because the command only ever talks to the {@link ShrinkoAdapter}
|
|
14
|
+
* interface.
|
|
15
|
+
*/
|
|
16
|
+
export type ShrinkoAdapterFactory = (env: NodeJS.ProcessEnv) => ShrinkoAdapter;
|
|
17
|
+
/**
|
|
18
|
+
* Registers `picopilot tokens` (US #3), the first shrinko-backed command.
|
|
19
|
+
*
|
|
20
|
+
* It reports `{tokens, pct, chars, compressed}` for a cart against the 8,192
|
|
21
|
+
* token budget via shrinko8 `--count`, and CTAs to `minify` when over budget so
|
|
22
|
+
* the agent is led straight to the fix (the detect-then-fix loop, ADR CTAs).
|
|
23
|
+
*
|
|
24
|
+
* Two-tier contract (ADR-0002): `tokens` is shrinko-BACKED, so with shrinko
|
|
25
|
+
* ABSENT it returns the structured `shrinko-not-found` error (nonzero exit,
|
|
26
|
+
* exact remedy `uv pip install shrinko`) rather than crashing or reporting a
|
|
27
|
+
* hollow zero. The dependency is reached entirely through the {@link
|
|
28
|
+
* ShrinkoAdapter} seam, so a native-TS shrinko would satisfy this command
|
|
29
|
+
* unchanged.
|
|
30
|
+
*
|
|
31
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
32
|
+
* tests pass a stub to swap the seam and to drive present/absent without the
|
|
33
|
+
* real binary.
|
|
34
|
+
*/
|
|
35
|
+
export declare function registerTokens(cli: Cli.Cli, adapterFactory?: ShrinkoAdapterFactory): void;
|
|
36
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/commands/tokens.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAElC,OAAO,EACN,KAAK,cAAc,EAGnB,MAAM,4BAA4B,CAAC;AAEpC,mFAAmF;AACnF,eAAO,MAAM,YAAY,OAAO,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,cAAc,CAAC;AAK/E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC7B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,qBAA6C,GAC3D,IAAI,CAqHN"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { isAbsolute, resolve } from 'node:path';
|
|
3
|
+
import { z } from 'incur';
|
|
4
|
+
import { ShrinkoParseError, ShellShrinkoAdapter, } from '../engine/shrinko/index.js';
|
|
5
|
+
/** PICO-8's Lua token budget. A cart over this will not load in PICO-8 (US #3). */
|
|
6
|
+
export const TOKEN_BUDGET = 8192;
|
|
7
|
+
const defaultAdapterFactory = (env) => new ShellShrinkoAdapter({ env });
|
|
8
|
+
/**
|
|
9
|
+
* Registers `picopilot tokens` (US #3), the first shrinko-backed command.
|
|
10
|
+
*
|
|
11
|
+
* It reports `{tokens, pct, chars, compressed}` for a cart against the 8,192
|
|
12
|
+
* token budget via shrinko8 `--count`, and CTAs to `minify` when over budget so
|
|
13
|
+
* the agent is led straight to the fix (the detect-then-fix loop, ADR CTAs).
|
|
14
|
+
*
|
|
15
|
+
* Two-tier contract (ADR-0002): `tokens` is shrinko-BACKED, so with shrinko
|
|
16
|
+
* ABSENT it returns the structured `shrinko-not-found` error (nonzero exit,
|
|
17
|
+
* exact remedy `uv pip install shrinko`) rather than crashing or reporting a
|
|
18
|
+
* hollow zero. The dependency is reached entirely through the {@link
|
|
19
|
+
* ShrinkoAdapter} seam, so a native-TS shrinko would satisfy this command
|
|
20
|
+
* unchanged.
|
|
21
|
+
*
|
|
22
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
23
|
+
* tests pass a stub to swap the seam and to drive present/absent without the
|
|
24
|
+
* real binary.
|
|
25
|
+
*/
|
|
26
|
+
export function registerTokens(cli, adapterFactory = defaultAdapterFactory) {
|
|
27
|
+
cli.command('tokens', {
|
|
28
|
+
description: 'Count a cart against the 8,192-token PICO-8 budget (via shrinko8 --count). Requires shrinko.',
|
|
29
|
+
args: z.object({
|
|
30
|
+
cart: z
|
|
31
|
+
.string()
|
|
32
|
+
.default('main.p8')
|
|
33
|
+
.describe('The .p8 cart to count. Defaults to main.p8 in the current folder.'),
|
|
34
|
+
}),
|
|
35
|
+
// Declaring PATH here makes incur resolve it from the (test-overridable)
|
|
36
|
+
// env source and hand it to us, so we pass a controlled PATH to the child.
|
|
37
|
+
env: z.object({
|
|
38
|
+
PATH: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Used to locate shrinko8 in the child process.'),
|
|
42
|
+
}),
|
|
43
|
+
output: z.object({
|
|
44
|
+
tokens: z.number().describe('The token count shrinko reported.'),
|
|
45
|
+
pct: z
|
|
46
|
+
.number()
|
|
47
|
+
.describe('tokens as a percentage of the 8,192 budget (rounded); >100 means over budget.'),
|
|
48
|
+
chars: z.number().describe('The character count shrinko reported.'),
|
|
49
|
+
compressed: z
|
|
50
|
+
.number()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('The compressed byte count (absent for some input formats).'),
|
|
53
|
+
budget: z
|
|
54
|
+
.number()
|
|
55
|
+
.describe('The token budget the percentage is against (8192).'),
|
|
56
|
+
overBudget: z.boolean().describe('True when tokens exceed the budget.'),
|
|
57
|
+
}),
|
|
58
|
+
examples: [
|
|
59
|
+
{ description: 'Count main.p8' },
|
|
60
|
+
{ description: 'Count a specific cart', args: { cart: 'game.p8' } },
|
|
61
|
+
],
|
|
62
|
+
async run({ args, env, error, ok }) {
|
|
63
|
+
const cartPath = isAbsolute(args.cart)
|
|
64
|
+
? args.cart
|
|
65
|
+
: resolve(process.cwd(), args.cart);
|
|
66
|
+
// A missing cart is a picopilot-side error, distinct from shrinko absence:
|
|
67
|
+
// surface it clearly instead of letting shrinko fail opaquely on the path.
|
|
68
|
+
if (!existsSync(cartPath)) {
|
|
69
|
+
return error({
|
|
70
|
+
code: 'cart-not-found',
|
|
71
|
+
message: `no cart at ${cartPath}`,
|
|
72
|
+
exitCode: 1,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const adapter = adapterFactory(env);
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
result = await adapter.count(cartPath);
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// shrinko RAN but its output was unparseable (version/format drift):
|
|
82
|
+
// a distinct `shrinko-failed`, NOT `shrinko-not-found` (which is absence).
|
|
83
|
+
if (e instanceof ShrinkoParseError) {
|
|
84
|
+
return error({
|
|
85
|
+
code: 'shrinko-failed',
|
|
86
|
+
message: `shrinko ran but its --count output could not be parsed: ${e.message}`,
|
|
87
|
+
exitCode: 1,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
throw e;
|
|
91
|
+
}
|
|
92
|
+
if (!result.ok) {
|
|
93
|
+
// The two-tier structured failure (ADR-0002 / US #17): shrinko absent.
|
|
94
|
+
// The message CARRIES the exact remedy + needs so an agent reading only
|
|
95
|
+
// the message still learns the fix; the code is the machine handle.
|
|
96
|
+
return error({
|
|
97
|
+
code: result.reason,
|
|
98
|
+
message: `shrinko is not installed. ${result.remedy} (needs: ${result.needs.join(', ')})`,
|
|
99
|
+
exitCode: 1,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const { tokens, chars, compressed } = result.value;
|
|
103
|
+
const pct = Math.round((tokens / TOKEN_BUDGET) * 100);
|
|
104
|
+
const overBudget = tokens > TOKEN_BUDGET;
|
|
105
|
+
// Over budget → CTA to `minify` (the detect-then-fix loop). Under budget
|
|
106
|
+
// needs no CTA: nothing to do.
|
|
107
|
+
const cta = overBudget
|
|
108
|
+
? {
|
|
109
|
+
description: 'Over the 8,192-token budget. Reclaim tokens with:',
|
|
110
|
+
commands: [
|
|
111
|
+
{
|
|
112
|
+
command: 'minify',
|
|
113
|
+
description: 'Safe-minify the cart to reduce tokens without changing behaviour.',
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
: undefined;
|
|
118
|
+
return ok({
|
|
119
|
+
tokens,
|
|
120
|
+
pct,
|
|
121
|
+
chars,
|
|
122
|
+
compressed,
|
|
123
|
+
budget: TOKEN_BUDGET,
|
|
124
|
+
overBudget,
|
|
125
|
+
}, cta === undefined ? undefined : { cta });
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/commands/tokens.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Cli } from 'incur';
|
|
2
|
+
import { type ShrinkoAdapter } from '../engine/shrinko/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* The one-line self-scoping the `picopilot verify` envelope always carries, so
|
|
5
|
+
* the connotation of "verify" never misleads an agent into treating a green
|
|
6
|
+
* static gate as terminal (ADR-0003). It states BOTH facts: verify is static,
|
|
7
|
+
* and passing does NOT mean the cart runs.
|
|
8
|
+
*/
|
|
9
|
+
export declare const VERIFY_SCOPE: "static gate: tokens + integrity; passing does NOT mean the cart runs";
|
|
10
|
+
/**
|
|
11
|
+
* The exit code for the DISTINCT `gate-incapable` outcome (shrinko absent).
|
|
12
|
+
* Chosen as 2 to be categorically separate from `fail` (exit 1, a cart that
|
|
13
|
+
* flunked a check) and from `pass` (exit 0): an absent capability is neither a
|
|
14
|
+
* green gate nor a failed check but a gate that could not run its most
|
|
15
|
+
* important check, so it gets its own nonzero code an agent can branch on.
|
|
16
|
+
*/
|
|
17
|
+
export declare const GATE_INCAPABLE_EXIT = 2;
|
|
18
|
+
/** See {@link ShrinkoAdapterFactory} in `tokens.ts` — verify reuses the same seam. */
|
|
19
|
+
export type ShrinkoAdapterFactory = (env: NodeJS.ProcessEnv) => ShrinkoAdapter;
|
|
20
|
+
/**
|
|
21
|
+
* Registers `picopilot verify` (US #15, #18), the STATIC cart-acceptance gate an
|
|
22
|
+
* agent drives each iteration toward.
|
|
23
|
+
*
|
|
24
|
+
* It runs two static checks and returns ONE structured envelope:
|
|
25
|
+
*
|
|
26
|
+
* - **integrity** — the cart parses and round-trips cleanly through the cart
|
|
27
|
+
* model (well-formed sections). No run.
|
|
28
|
+
* - **tokens** — the cart is within the 8,192-token budget (via the shrinko
|
|
29
|
+
* adapter `--count`).
|
|
30
|
+
*
|
|
31
|
+
* (Lint folds in when the lint task lands; in v1-core verify = tokens +
|
|
32
|
+
* integrity.)
|
|
33
|
+
*
|
|
34
|
+
* Three categorical outcomes (ADR-0003):
|
|
35
|
+
*
|
|
36
|
+
* - **pass** (exit 0) — both checks green. The envelope SELF-SCOPES (states it
|
|
37
|
+
* is static and that passing does not mean the cart runs) and CTAs to
|
|
38
|
+
* `picopilot run` ("static checks pass, now confirm it boots"), so the agent
|
|
39
|
+
* is led from well-formed to actually-runs.
|
|
40
|
+
* - **fail** (exit 1) — a check flunked (over budget, or a malformed/non-parsing
|
|
41
|
+
* cart). A structured `verify-failed` error listing which checks failed.
|
|
42
|
+
* - **gate-incapable** (exit {@link GATE_INCAPABLE_EXIT}) — shrinko is ABSENT,
|
|
43
|
+
* so the token check (the #1 failure mode, token bloat) cannot run. verify
|
|
44
|
+
* returns a DISTINCT `gate-incapable` result, NEVER green, categorically
|
|
45
|
+
* separate from pass and fail. This is the gate-as-theatre regression guard:
|
|
46
|
+
* verify must never report green by skipping its most important check.
|
|
47
|
+
*
|
|
48
|
+
* verify is STATIC and never runs the cart, so it has NO PICO-8 dependency (US
|
|
49
|
+
* #19). It reaches shrinko entirely through the {@link ShrinkoAdapter} seam, so
|
|
50
|
+
* a native-TS shrinko would satisfy it unchanged.
|
|
51
|
+
*
|
|
52
|
+
* @param adapterFactory injects the adapter (defaults to the shell adapter);
|
|
53
|
+
* tests pass a stub to drive present / absent / over-budget without the real
|
|
54
|
+
* binary.
|
|
55
|
+
*/
|
|
56
|
+
export declare function registerVerify(cli: Cli.Cli, adapterFactory?: ShrinkoAdapterFactory): void;
|
|
57
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/commands/verify.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,GAAG,EAAI,MAAM,OAAO,CAAC;AAGlC,OAAO,EACN,KAAK,cAAc,EAGnB,MAAM,4BAA4B,CAAC;AAGpC;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EACxB,sEAA+E,CAAC;AAEjF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC,sFAAsF;AACtF,MAAM,MAAM,qBAAqB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,cAAc,CAAC;AA2C/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,cAAc,CAC7B,GAAG,EAAE,GAAG,CAAC,GAAG,EACZ,cAAc,GAAE,qBAA6C,GAC3D,IAAI,CA8IN"}
|