Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
copy-paste implementation prompt before any code is written. CLI-backed
(`artifact kind:"unknown-map"`, decisions, assumptions, open loops), so its open items
drain `ratchet score confidence` until closed; no schema change. Method grafted from
dzhng/skills `explore-unknowns`, expressed in ratchet's own vocabulary. Aperture
auto-routing (scoring the task straight into the map) is deferred to a follow-up.
dzhng/skills `explore-unknowns`, expressed in ratchet's own vocabulary.
- **Aperture routes through the map.** `ratchet score aperture` now folds `/ratchet:map`
into the A3–A4 metered sequences (before `build`) and returns a `mapRequired` flag —
true at A3+, or when a single dimension the summed score under-weights demands it
(`taste = 2`, or `terrain = 2` with any `ambiguity`). `md.aperture()` renders
**`Pre-build map: required`** so `/ratchet:ignite` routes high-uncertainty work through
the fog gate instead of relying on the operator to remember. A4 still builds nothing.

## [0.5.0] - 2026-07-04 — Receipt

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,11 @@ when it isn't. In Codex, ask it to use the matching Torque Loop skill, for examp
> unfamiliar terrain, reference-implementation ports, or "I'll know it when I see it"
> taste. Walk the four unknown-quadrants (known knowns · known unknowns · unknown knowns ·
> unknown unknowns) and hand over the map *before* `/ratchet:build`; at **A4**, stop after
> the map until constraints are locked. What you deliver is the map, not a build — a wrong
> assumption caught on the map is a one-line fix; caught mid-build it is a rewrite.
> the map until constraints are locked. The aperture read raises **`Pre-build map:
> required`** for exactly these cases — including a high-`taste` or unfamiliar-`terrain`
> task the summed score would under-rate — so `/ratchet:ignite` routes into the map on its
> own. What you deliver is the map, not a build — a wrong assumption caught on the map is a
> one-line fix; caught mid-build it is a rewrite.

### Specialized

Expand Down
10 changes: 8 additions & 2 deletions skills/ignite/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,14 @@ The result names an aperture and the exact ratchet sequence to run:
| A0 Snap | 0–2 | `build → verify` — do it, prove it, stop. |
| A1 Narrow | 3–4 | `lock → build → verify → compile`. |
| A2 Working | 5–6 | the full seven-step pipeline below. |
| A3 Wide | 7–8 | add `auction` + `decide`; prototype / reference before build. |
| A4 Max | 9–10 | `lock → cut → decide`, then **STOP — produce options, do not build** until constraints are locked. |
| A3 Wide | 7–8 | `lock → map → auction → cut → decide → build …` — map the fog before you build. |
| A4 Max | 9–10 | `lock → map → cut → decide`, then **STOP — produce options, do not build** until constraints are locked. |

When the read reports **`Pre-build map: required`**, run `/ratchet:map` before any
build step and hand over the four-quadrant map first. The dial raises it at A3–A4, and
also on a single dominant dimension the score under-weights — "I'll know it when I see
it" taste, or unfamiliar terrain with any goal ambiguity — so a high-taste task can earn
a map even at a low overall score. At A4, stop after the map until constraints are locked.

Widen one level after failed verification, a surprising repo constraint, or
conflicting requirements; narrow one after the user locks a decision or the work
Expand Down
3 changes: 3 additions & 0 deletions src/markdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ function aperture(a) {
: '**Implement:** NO — lock constraints and produce options first; do not build yet.'
);
o.push(`**Metered loop:** ${a.sequence.map((s) => `\`/ratchet:${s}\``).join(' → ')}`);
if (a.mapRequired) {
o.push('**Pre-build map:** required — run `/ratchet:map` before `/ratchet:build`; map the fog first.');
}
if (a.scope) o.push(`_Scope: ${a.scope}._`);
return o.join('\n');
}
Expand Down
13 changes: 11 additions & 2 deletions src/scoring.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,8 @@ const APERTURE_LEVELS = [
{ max: 2, level: 'A0', name: 'Snap', implement: true, sequence: ['build', 'verify'] },
{ max: 4, level: 'A1', name: 'Narrow', implement: true, sequence: ['lock', 'build', 'verify', 'compile'] },
{ max: 6, level: 'A2', name: 'Working', implement: true, sequence: ['lock', 'cut', 'build', 'attack', 'patch', 'verify', 'compile'] },
{ max: 8, level: 'A3', name: 'Wide', implement: true, sequence: ['lock', 'auction', 'cut', 'decide', 'build', 'attack', 'patch', 'compile'] },
{ max: 10, level: 'A4', name: 'Max', implement: false, sequence: ['lock', 'cut', 'decide'] },
{ max: 8, level: 'A3', name: 'Wide', implement: true, sequence: ['lock', 'map', 'auction', 'cut', 'decide', 'build', 'attack', 'patch', 'compile'] },
{ max: 10, level: 'A4', name: 'Max', implement: false, sequence: ['lock', 'map', 'cut', 'decide'] },
];

// A missing dimension defaults to 1 (neutral), never 0 — treating unknown
Expand All @@ -310,12 +310,21 @@ function scoreAperture(dims) {
total += v;
}
const band = APERTURE_LEVELS.find((b) => total <= b.max) || APERTURE_LEVELS[APERTURE_LEVELS.length - 1];
// The pre-build fog gate (/ratchet:map) is earned by high overall uncertainty
// (A3+), OR by a single dimension the summed score under-weights: "know it when
// I see it" taste, or unfamiliar terrain paired with any goal ambiguity. Those
// can sit below the A3 band yet still make a confident build the wrong move — so
// the flag can fire even when the metered sequence for this band does not
// include `map`, signalling "map first anyway."
const mapRequired =
total >= 7 || scored.taste === 2 || (scored.terrain === 2 && scored.ambiguity >= 1);
return {
score: total, // 0..10
level: band.level, // A0..A4
name: band.name,
implement: band.implement, // A4: do NOT build until constraints are locked
sequence: band.sequence.slice(), // ratchet skills to run at this depth
mapRequired, // route through /ratchet:map before building (see above)
dimensions: scored,
// This reading is only valid for the task as scored, at scoring time.
scope: 'the one task scored, at scoring time — re-score if the task or its constraints change',
Expand Down
32 changes: 32 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,38 @@ ok('aperture renders with its metered ratchet loop', () => {
assert.ok(/ratchet:build/.test(out));
});

ok('aperture routes high-uncertainty work through /ratchet:map', () => {
// A3/A4 route through the pre-build fog gate, before build.
const wide = scoring.scoreAperture({ ambiguity: 2, terrain: 2, taste: 1, blastRadius: 1, reversibility: 1 }); // 7 → A3
assert.strictEqual(wide.level, 'A3');
assert.ok(wide.sequence.includes('map'), 'A3 sequence routes through map');
assert.ok(wide.sequence.indexOf('map') < wide.sequence.indexOf('build'), 'map comes before build');
assert.ok(wide.mapRequired, 'A3 requires a pre-build map');

const max = scoring.scoreAperture({ ambiguity: 2, terrain: 2, taste: 2, blastRadius: 2, reversibility: 2 }); // 10 → A4
assert.ok(max.sequence.includes('map'), 'A4 routes through map');
assert.ok(!max.sequence.includes('build'), 'A4 still produces options, not code');
assert.ok(max.mapRequired);

// The single-dimension override: "know it when I see it" taste warrants a map
// even when the summed score sits at A0.
const taste = scoring.scoreAperture({ ambiguity: 0, terrain: 0, taste: 2, blastRadius: 0, reversibility: 0 }); // 2 → A0
assert.strictEqual(taste.level, 'A0');
assert.ok(taste.mapRequired, 'high taste requires a map even at a low score');

// Unfamiliar terrain + any goal ambiguity, below the A3 band, still earns it.
const fog = scoring.scoreAperture({ ambiguity: 1, terrain: 2, taste: 0, blastRadius: 0, reversibility: 0 }); // 3 → A1
assert.ok(fog.mapRequired, 'unfamiliar terrain with ambiguity earns a map');

// Plain low-uncertainty work does not — the flag must not fire on everything.
const narrow = scoring.scoreAperture({ ambiguity: 1, terrain: 0, taste: 0, blastRadius: 1, reversibility: 1 }); // 3 → A1
assert.ok(!narrow.mapRequired, 'low-uncertainty work skips the map');

// The render surfaces the requirement, and stays quiet when it does not apply.
assert.ok(/Pre-build map:.*required/.test(md.aperture(wide)), 'render names the map requirement');
assert.ok(!/Pre-build map/.test(md.aperture(narrow)), 'render stays quiet when a map is not required');
});

ok('markdown render does not throw on populated state', () => {
const out = md.stateSummary(state.loadState(cwd));
assert.ok(out.includes('Ratchet state'));
Expand Down
Loading