@sleepy_panda_srl/portulan 0.1.3 → 0.2.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.
Files changed (76) hide show
  1. package/README.md +6 -5
  2. package/cli/README.md +142 -358
  3. package/cli/advisory.mjs +354 -0
  4. package/cli/comments.mjs +932 -0
  5. package/cli/compile.mjs +1322 -1607
  6. package/cli/context.mjs +883 -0
  7. package/cli/control-chars.mjs +12 -301
  8. package/cli/discover.mjs +17 -574
  9. package/cli/doctor.mjs +224 -1074
  10. package/cli/drills.mjs +68 -502
  11. package/cli/feedback.mjs +16 -237
  12. package/cli/finish.mjs +437 -0
  13. package/cli/form.mjs +644 -0
  14. package/cli/fuzz-shell.mjs +20 -305
  15. package/cli/gate.mjs +7 -199
  16. package/cli/goldens.mjs +26 -184
  17. package/cli/index.mjs +305 -655
  18. package/cli/init.mjs +243 -566
  19. package/cli/inside.mjs +3 -28
  20. package/cli/instructions.mjs +829 -0
  21. package/cli/ledger.mjs +778 -0
  22. package/cli/librarian.mjs +135 -599
  23. package/cli/manifest.mjs +2 -39
  24. package/cli/mutants.mjs +9 -170
  25. package/cli/new.mjs +12 -243
  26. package/cli/pack-identity.mjs +5 -74
  27. package/cli/pack-version.mjs +13 -255
  28. package/cli/plugin-lint.mjs +42 -658
  29. package/cli/portulan.mjs +5 -124
  30. package/cli/recipe-set.mjs +15 -245
  31. package/cli/release-eval.mjs +28 -348
  32. package/cli/review-meter.mjs +17 -258
  33. package/cli/rule-carriers.mjs +12 -175
  34. package/cli/sessions.mjs +82 -0
  35. package/cli/skill-goldens.mjs +10 -176
  36. package/cli/skills-set.mjs +12 -414
  37. package/cli/stop-gate.mjs +83 -514
  38. package/cli/symbols.mjs +1132 -0
  39. package/cli/telemetry.mjs +27 -492
  40. package/cli/upgrade.mjs +171 -314
  41. package/cli/vendor.mjs +146 -476
  42. package/cli/version-carriers.mjs +8 -86
  43. package/core/README.md +1 -1
  44. package/core/engine.md +3 -9
  45. package/core/operating/autonomy.md +1 -1
  46. package/core/operating/context.md +183 -0
  47. package/core/operating/evolution.md +3 -3
  48. package/core/operating/loop.md +13 -20
  49. package/core/operating/memory.md +12 -3
  50. package/core/operating/sessions.md +100 -0
  51. package/core/personas/librarian.md +2 -2
  52. package/core/skills/consolidate/SKILL.md +4 -2
  53. package/core/templates/handoff.md +28 -15
  54. package/core/templates/memory-entry.md +1 -0
  55. package/evals/README.md +255 -845
  56. package/evals/releases/0.2.0.json +146 -0
  57. package/evals/releases/0.2.0.md +67 -0
  58. package/evals/releases/README.md +3 -3
  59. package/package.json +4 -2
  60. package/packs/rituals/checkpoints/README.md +1 -1
  61. package/packs/tools/github/verify/actions-pinned.sh +10 -55
  62. package/spec/README.md +112 -6
  63. package/spec/migrations/0001-repository-declares-its-tree.mjs +1 -26
  64. package/spec/migrations/0002-bundle-fallback-path.mjs +13 -74
  65. package/spec/migrations/0003-handoff-index-not-kept.mjs +66 -0
  66. package/spec/migrations/0004-changelog-fragments.mjs +53 -0
  67. package/spec/migrations/0005-session-log-retired.mjs +69 -0
  68. package/spec/migrations/0006-boot-card.mjs +77 -0
  69. package/spec/migrations/0007-guidance-compiled.mjs +31 -0
  70. package/spec/migrations/0008-card-reading.mjs +55 -0
  71. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -0
  72. package/spec/migrations/0010-comment-rail.mjs +57 -0
  73. package/spec/migrations/README.md +45 -10
  74. package/spec/pack.schema.json +1 -1
  75. package/spec/slots.md +333 -21
  76. package/spec/workspace.schema.json +145 -4
@@ -1,111 +1,23 @@
1
1
  #!/usr/bin/env node
2
- // The core-skill golden corpus — every mandate a core skill states, bound to the live artifacts it
3
- // governs, and graded.
4
- //
5
- // Milestone 8, the row's **original first subject**: *Golden tasks per core skill.* Untouched by either
6
- // amendment — the 2026-07-28 expansion says it "stands exactly as written", and clause (a) *widened its
7
- // subject* to reach the gates "as well as the skills" without narrowing the subject it already had
8
- // (`../docs/milestones/m08.md` line 18). The gates half landed at (a) as `./goldens.mjs`; this is the
9
- // half that sentence always named and nobody had built.
10
- //
11
- // ## What a golden task IS here, and it was RULED rather than derived
12
- //
13
- // The criterion reads two ways — a deterministic corpus, or task specifications run against a live
14
- // model — and `../.portulan/gate-map.md`'s *"Session-open runs `clarify` against the milestone row
15
- // itself"* exists because milestone 4 guessed at exactly this shape of ambiguity and cost a
16
- // session-blocking question. So it was put to the maintainer instead of argued. **His ruling: a
17
- // deterministic corpus graded against LIVE ARTIFACTS in the tree.** The model-run sense belongs to the
18
- // A/B clause, which is separately named in the same row.
19
- //
20
- // ## The object: a mandate, its live artifacts, and the carrier that enforces it
21
- //
22
- // A case binds one **numbered step** of one skill's `## The pass` to the artifact set it governs, and
23
- // records which existing rail enforces it — or `null`, which is the interesting value.
24
- //
25
- // Four properties, and every one of them exists because a checkpoint found the version without it:
26
- //
27
- // 1. **The denominator is DERIVED, not chosen.** The runner enumerates the numbered steps under each
28
- // skill's `## The pass` heading and requires every one to be either `bound` or `unbindable`. A
29
- // fifteenth step is red. The first draft let the author pick which mandates to write cases for,
30
- // and a census over a set its own author drew reports "5 of 5" and means nothing — the difference
31
- // `./goldens.mjs` already draws by deriving its denominator from the yielded policy.
32
- // 2. **The heading is PREFIX-matched, and an empty step list is could-not-run.** Three spellings exist
33
- // — `## The pass`, `## The pass (bounded)`, `## The pass, in order` — so an exact match finds one
34
- // skill of three and the other two get an empty denominator that satisfies "every step is bound"
35
- // vacuously. That is `../evals/README.md`'s own sentence about clause (d): *"a check whose
36
- // enumeration went empty … reports green and has stopped being a rail."* The match is also anchored
37
- // at `The pass`, because a loose `/pass/` grabs `consolidate`'s `## The one move this pass may not
38
- // make`.
39
- // 3. **The quote must anchor EXACTLY ONCE** in the skill's own file, or the case is could-not-run —
40
- // `./mutants.mjs`'s discipline, adopted rather than re-derived. A skill whose wording is reworded,
41
- // softened or deleted reddens instead of drifting, and nothing in this tree did that before.
42
- // 4. **The predicates use the carriers' own exported functions rather than re-spelling them.** This
43
- // module imports `RETIRE_WHEN` and `parseProvenance` from `./doctor.mjs`, so the retire-when check
44
- // is `doctor`'s regex and not a second one.
45
- //
46
- // **What that does NOT do, stated because three carriers of this file's prose once said it did:**
47
- // it does not verify the `carrier` field. Nothing links `c.carrier.symbol` to an import — the
48
- // pre-commit checkpoint rewrote every carrier in a corpus file to a module that does not exist and
49
- // the corpus stayed green, and moved `.portulan/verify/index.sh` out of the tree with the same
50
- // result. `carrier` is a **declared, reviewed field**: it records which rail a mandate's enforcement
51
- // lives in, for the census, and a reviewer is what checks it. And a carrier that deleted an export
52
- // would fail this module's own import with a stack trace and exit **1**, not the 2 the earlier
53
- // sentence promised.
54
- //
55
- // ## What the carrier field does NOT establish, said here because a census implies more than it means
56
- //
57
- // An import proves the carrier **contains** the check. It does not prove the carrier **runs** it: a
58
- // rail could keep `RETIRE_WHEN` exported, stop calling it, and this corpus would not notice, because
59
- // `expect` is computed here over the artifacts rather than by asking the rail. Closing that needs a
60
- // per-mandate drill — perturb a record, require the named rail to fire with its own tell — which is
61
- // `./drills.mjs`'s shape at a finer grain and a second clause's work.
62
- //
63
- // ## `unbindable` is adjudicated, never asserted
64
- //
65
- // The dodge is obvious: call a mandate unbindable and it needs no case. So the reason takes a **closed
66
- // vocabulary** rather than free prose, on `./mutants.mjs`'s rule that a `survives` record is admissible
67
- // only as a proof and never as a standing note that a gap exists:
68
- //
69
- // * `judgement-only` — the mandate is about a human or agent decision and no artifact can witness it
70
- // ("Get the answers from the human"). **Such a step may name no artifact path**:
71
- // if you can name the artifacts, it is not judgement, it is unbuilt.
72
- // * `no-artifact` — the mandate governs something that leaves no trace in this tree.
73
- // * `cross-language` — a carrier exists but in another language, so binding it here would put two
74
- // spellings of one rule across a language boundary.
75
- // * `already-carried` — a carrier exists IN THIS LANGUAGE and already holds the mandate, so a case
76
- // here would be a third carrier of a rail that is already drilled. Added at the
77
- // pre-commit checkpoint, which found `consolidate` step 5 classed `bound` with
78
- // a carrier the runner never reads: deleting `.portulan/verify/index.sh` left
79
- // the corpus green. The row was inflating the headline, and the vocabulary had
80
- // no honest term for what it actually was.
81
- //
82
- // **And the ratio is a finding, not bookkeeping.** How much of a core skill is artifact discipline and
83
- // how much is agent judgement is a measurement about the engine, which is what this row exists to
84
- // produce. It is printed as such.
2
+ // The core-skill golden corpus — every mandate a core skill states, bound to the live artifacts it governs, and graded.
85
3
 
86
4
  import fs from "node:fs";
87
5
  import path from "node:path";
88
6
  import { pathToFileURL } from "node:url";
89
7
 
90
- // The carriers, imported rather than grepped — property 4 above. A carrier that stops exporting these
91
- // fails this module at load, which is the strongest form of the check available for free.
8
+ // The carriers' own exports, never a second spelling of their checks.
92
9
  import { RETIRE_WHEN, parseProvenance } from "./doctor.mjs";
93
10
 
94
11
  export const CASE_KINDS = ["load-bearing", "census"];
95
12
  export const UNBINDABLE_REASONS = ["judgement-only", "no-artifact", "cross-language", "already-carried"];
96
13
 
97
- // Anchored at `The pass` and prefix-matched, for the two reasons in property 2.
14
+ // Prefix-matched, as three spellings exist; anchored, as a bare `pass` matches another `consolidate` heading.
98
15
  const PASS_HEADING = /^##\s+The pass\b/i;
99
- // **Enumerated on the number, and the bold is read separately.** This was
100
- // `/^(\d+)\.\s+\*\*(.+?)\*\*/`, which made an UNBOLDED step invisible: the pre-commit checkpoint
101
- // added `5. A brand new fifth mandate, not bolded` to a pass and the corpus stayed green at 5 of 15.
102
- // The empty-enumeration guard catches the all-or-nothing case; this catches the incremental one, which
103
- // is the realistic one.
16
+ // On the number alone, the bold read separately, so an unbolded step still counts.
104
17
  const STEP = /^(\d+)\.\s+(.*)$/;
105
18
 
106
19
  export class CouldNotRun extends Error {}
107
20
 
108
- /** Every core skill in the engine, derived from the tree. A skill with no corpus file is a finding. */
109
21
  export function skillSet(repoRoot) {
110
22
  const dir = path.join(repoRoot, "core/skills");
111
23
  let entries;
@@ -122,7 +34,6 @@ export function skillSet(repoRoot) {
122
34
  return skills;
123
35
  }
124
36
 
125
- /** The numbered steps under a skill's `## The pass`. Zero is could-not-run, never a vacuous green. */
126
37
  export function passSteps(repoRoot, skill) {
127
38
  const file = path.join(repoRoot, "core/skills", skill, "SKILL.md");
128
39
  let text;
@@ -154,7 +65,6 @@ export function passSteps(repoRoot, skill) {
154
65
  return steps;
155
66
  }
156
67
 
157
- /** A quote must place exactly once in the skill's own file, or the case cannot be graded. */
158
68
  export function anchorQuote(repoRoot, skill, quote) {
159
69
  const text = fs.readFileSync(path.join(repoRoot, "core/skills", skill, "SKILL.md"), "utf8");
160
70
  let count = 0;
@@ -171,11 +81,6 @@ export function anchorQuote(repoRoot, skill, quote) {
171
81
  }
172
82
  }
173
83
 
174
- /**
175
- * The artifact set a case grades, resolved through the workspace's DECLARED slots rather than a
176
- * hard-coded path. A slot rename must not silently empty a denominator, and an empty match is
177
- * could-not-run for the same reason a zero-step list is.
178
- */
179
84
  export function slotFiles(workspaceDir, manifest, slot) {
180
85
  const rel = manifest?.slots?.[slot];
181
86
  if (typeof rel !== "string" || rel.length === 0) {
@@ -196,11 +101,9 @@ export function slotFiles(workspaceDir, manifest, slot) {
196
101
  }
197
102
 
198
103
  // ---------------------------------------------------------------------------------------------
199
- // The predicates. Each is named in its case, because a figure computed before its rule was decided is
200
- // how both of this session's opening passes produced a different number for the same question.
104
+ // The predicates
201
105
  // ---------------------------------------------------------------------------------------------
202
106
 
203
- /** A bolded field's block: the `**Field.**` line, then lines until the next bolded field or heading. */
204
107
  export function fieldBlock(text, field) {
205
108
  const lines = text.split("\n");
206
109
  const start = lines.findIndex((l) => new RegExp(`^\\s*\\*\\*${field}\\.?\\*\\*`, "i").test(l));
@@ -213,7 +116,6 @@ export function fieldBlock(text, field) {
213
116
  return out.join("\n");
214
117
  }
215
118
 
216
- /** Bullets with wrapped continuation lines rejoined — a wrapped criterion is one criterion. */
217
119
  export function bulletsOf(block) {
218
120
  const out = [];
219
121
  for (const line of block.split("\n")) {
@@ -223,17 +125,7 @@ export function bulletsOf(block) {
223
125
  return out.filter(Boolean);
224
126
  }
225
127
 
226
- // EARS as `core/templates/task.md` states it. The shape is NOT restated in this module's prose: the
227
- // template is the rule and this is a reduction of it, cited rather than re-spelled.
228
- //
229
- // **Three carriers state the shape** — that template, `core/operating/operating`-adjacent
230
- // `core/operating/verification.md`, and `core/skills/clarify/SKILL.md` — all `core/` at tier `propose`.
231
- // Reducing them to one is deferred on **budget** and on a real design question (making a CLI module the
232
- // carrier that core doctrine cites inverts the cascade), filed as
233
- // https://github.com/sleepy-panda-srl/portulan/issues/359. _(An earlier version of this comment said
234
- // there were four, one of them `docs/vision.md` at tier `prohibited`, and concluded the reduction was
235
- // impossible to an agent. `docs/vision.md` names EARS in a comparison-table row and nowhere states the
236
- // shape; the count and the conclusion were both wrong, and the pre-commit checkpoint measured it.)_
128
+ // A reduction of EARS as `core/templates/task.md` states it; the template is the rule.
237
129
  export const isEars = (b) => /\bwhen\b/i.test(b) && /\bshall\b/i.test(b);
238
130
 
239
131
  export const PREDICATES = {
@@ -247,25 +139,15 @@ export const PREDICATES = {
247
139
  ? { ok: true, total: bs.length }
248
140
  : { ok: false, why: `${bad.length} of ${bs.length} criteria are not EARS-shaped`, total: bs.length };
249
141
  },
250
- // `codify` step 3 — *"Attach how it earns its place"*. core/templates/proposal.md mandates an
251
- // **Enforcement.** field, and nothing checks it. The pre-commit checkpoint found this step classed
252
- // judgement-only on the argument that presence-checking would be "the presence floor with none of
253
- // the value" — an argument this module refutes in its own printed output, and which equally refutes
254
- // the provenance binding it did ship. Same spelling tolerance, same reason.
255
142
  "enforcement-present": (text) => {
256
143
  const has = /^\s*\*\*Enforcement\.?\*\*/im.test(text) || /^##+\s+Enforcement/im.test(text);
257
144
  return has ? { ok: true } : { ok: false, why: "no enforcement field in any spelling" };
258
145
  },
259
- // Any spelling of a provenance field. Deliberately shape-tolerant: the tree carries `**Provenance.**`
260
- // and `## Provenance` and a literal predicate would report the renamed ones as absent — the false-red
261
- // this session's own opening pass produced once against the task corpus.
262
146
  "provenance-present": (text) => {
263
147
  const has = /^\s*\*\*Provenance\.?\*\*/im.test(text) || /^##+\s+Provenance/im.test(text);
264
148
  return has ? { ok: true } : { ok: false, why: "no provenance field in any spelling" };
265
149
  },
266
- // The two defined forms, answered by `doctor`'s own exported parser. Scoped to `type: rule`, which
267
- // is the scope the mandate has and the scope `doctor` enforces at — a `decision` carrying prose
268
- // provenance is reported by `doctor` and is not a violation of this mandate.
150
+ // Scoped to `type: rule`, the scope `doctor` enforces the two forms at.
269
151
  "provenance-two-form": (text) => {
270
152
  const type = (/^\s*\*\*type:\*\*\s*(\w+)/im.exec(text) || [])[1];
271
153
  if (type !== "rule") return { ok: true, skipped: "not a rule" };
@@ -276,11 +158,6 @@ export const PREDICATES = {
276
158
  RETIRE_WHEN.test(text) ? { ok: true } : { ok: false, why: "no **Retire when:** line" },
277
159
  };
278
160
 
279
- /**
280
- * The containment case: every budget id the Workspace Definition declares must be named in the
281
- * routing sentence `consolidate` states. Derived from the schema on both sides — a fifth budget, or a
282
- * rename, reddens rather than drifting.
283
- */
284
161
  export function budgetIds(repoRoot) {
285
162
  const schema = JSON.parse(fs.readFileSync(path.join(repoRoot, "spec/workspace.schema.json"), "utf8"));
286
163
  const mem = schema.properties?.memory?.properties ?? {};
@@ -294,8 +171,7 @@ export function budgetIds(repoRoot) {
294
171
  }
295
172
 
296
173
  // ---------------------------------------------------------------------------------------------
297
- // Grading. Every case's `expect` is compared against what the predicate answers over the LIVE
298
- // artifacts; nothing here is graded against a fixture the corpus invented.
174
+ // Grading
299
175
  // ---------------------------------------------------------------------------------------------
300
176
 
301
177
  function gradeBound(skill, c, ctx) {
@@ -322,13 +198,6 @@ function gradeBound(skill, c, ctx) {
322
198
  return { total: considered, failing, detail: `${considered - failing.length}/${considered} compliant` };
323
199
  }
324
200
 
325
- /**
326
- * Grade one skill's corpus file against the tree.
327
- *
328
- * Every numbered step must be accounted for. `unbindable` is adjudicated: the reason comes from a
329
- * closed vocabulary, and a `judgement-only` step may name no artifact — if you can name the artifacts
330
- * the mandate governs, it is not judgement, it is unbuilt.
331
- */
332
201
  export function gradeSkill(skill, corpus, ctx) {
333
202
  const findings = [];
334
203
  const steps = passSteps(ctx.repoRoot, skill);
@@ -345,9 +214,7 @@ export function gradeSkill(skill, corpus, ctx) {
345
214
  );
346
215
  }
347
216
  }
348
- // Step 0 is reserved for a mandate outside `## The pass` — `consolidate`'s routing sentence lives
349
- // under `## When to use it`. Numbered 0 so the pass accounting stays a clean 1..N and the row cannot
350
- // be mistaken for a step. Any OTHER number the pass does not have is a finding.
217
+ // Step 0 is a mandate outside `## The pass`, such as `consolidate`'s routing sentence.
351
218
  for (const n of declared.keys()) {
352
219
  if (n !== 0 && !steps.some((s) => s.n === n)) {
353
220
  findings.push(`${skill}: case names step ${n}, which the pass does not have`);
@@ -363,11 +230,6 @@ export function gradeSkill(skill, corpus, ctx) {
363
230
  if (!UNBINDABLE_REASONS.includes(c.reason)) {
364
231
  findings.push(`${skill}: step ${step.n} is unbindable for ${JSON.stringify(c.reason)}, which is not one of ${UNBINDABLE_REASONS.join(" | ")}`);
365
232
  }
366
- // **A reason without an argument is a label, not an adjudication.** `./mutants.mjs` refuses
367
- // an operator carrying no `why` — *"a mutation nobody can read is not reviewable"* — and
368
- // `./goldens.mjs` refuses a `documented-hole` naming no record. This carried neither until
369
- // the pre-commit checkpoint stripped every `why` from all ten unbindable cases and watched
370
- // the corpus stay green.
371
233
  if (typeof c.why !== "string" || c.why.trim().length < 40) {
372
234
  findings.push(
373
235
  `${skill}: step ${step.n} is unbindable and carries no argument. ` +
@@ -393,11 +255,6 @@ export function gradeSkill(skill, corpus, ctx) {
393
255
  if (e instanceof CouldNotRun) throw e;
394
256
  throw new CouldNotRun(`${skill}: step ${step.n} could not be graded — ${e.message}`);
395
257
  }
396
- // **`expect.accepted` is the larger dodge, and it was entirely unadjudicated.** Any finding
397
- // could be silenced by appending a bare filename — the pre-commit checkpoint created a
398
- // non-compliant task file, listed it, and the corpus went green over eighteen silenced
399
- // findings. So an entry is an object carrying its own `why`, on the same rule as `unbindable`
400
- // above: the both-directions check stops a STALE entry, and this stops an UNJUSTIFIED one.
401
258
  const acceptedEntries = c.expect?.accepted ?? [];
402
259
  for (const a of acceptedEntries) {
403
260
  if (typeof a !== "object" || typeof a.file !== "string") {
@@ -413,8 +270,6 @@ export function gradeSkill(skill, corpus, ctx) {
413
270
  const unexpected = result.failing.filter((f) => !accepted.has(f.split(" — ")[0]));
414
271
  const repaired = [...accepted].filter((a) => !result.failing.some((f) => f.split(" — ")[0] === a));
415
272
  for (const u of unexpected) findings.push(`${skill}: step ${step.n} — ${u}`);
416
- // Both directions, as `documented-hole` has on the gate side: an accepted drift that is
417
- // REPAIRED is a finding too, or an accepted-drift list outlives the drift it records.
418
273
  for (const r of repaired) {
419
274
  findings.push(
420
275
  `${skill}: step ${step.n} — ${r} now complies, and is still listed as accepted drift. ` +
@@ -438,10 +293,6 @@ export function run(argv = process.argv.slice(2), io = console) {
438
293
  let repoRoot = ".";
439
294
  let workspaceDir = null;
440
295
  for (let i = 0; i < argv.length; i += 1) {
441
- // **A missing option value must not swallow the next flag.** `argv[++i]` on a trailing
442
- // `--repo-root` yields `undefined` and on `--repo-root --workspace x` yields `"--workspace"`,
443
- // so the tool would grade a directory nobody named and say nothing. `./rule-carriers.mjs`
444
- // treats a missing value as exit 2 and this now matches it. Copilot round 1 on #360.
445
296
  const value = (flag) => {
446
297
  const v = argv[i + 1];
447
298
  if (v === undefined || v.startsWith("--")) throw new CouldNotRun(`${flag} needs a value`);
@@ -478,8 +329,6 @@ export function run(argv = process.argv.slice(2), io = console) {
478
329
  for (const skill of skills) {
479
330
  const file = path.join(corpusDir, `${skill}.json`);
480
331
  if (!fs.existsSync(file)) {
481
- // The derived skill set is what makes this a coverage rail: a new core skill cannot ship
482
- // with no mandates accounted for.
483
332
  findings.push(`${skill}: core/skills/${skill}/ exists and evals/goldens/skills/${skill}.json does not`);
484
333
  continue;
485
334
  }
@@ -491,8 +340,6 @@ export function run(argv = process.argv.slice(2), io = console) {
491
340
  return 2;
492
341
  }
493
342
  if (corpus.skill !== skill) {
494
- // The filename is checked against the file's own field, the repair `../evals/README.md`
495
- // records for the gate corpus after a misfiled fixture graded cleanly.
496
343
  io.error(`skill-goldens: ${skill}.json declares skill ${JSON.stringify(corpus.skill)}`);
497
344
  return 2;
498
345
  }
@@ -530,22 +377,12 @@ export function run(argv = process.argv.slice(2), io = console) {
530
377
  io.log(` ${bound} of ${bound + unbindable} mandate(s) bound to live artifacts — ${loadBearing} load-bearing, ${census} census`);
531
378
  const reasons = {};
532
379
  for (const g of all) for (const r of g.rows) if (r.state === "unbindable") reasons[r.reason] = (reasons[r.reason] ?? 0) + 1;
533
- // **The split is printed, because the aggregate hides which unbindable means what.** Three prose
534
- // carriers of this file's own output once said "two thirds of these mandates are judgement" when
535
- // two thirds were merely UNBINDABLE and `cross-language` and `already-carried` mean the opposite —
536
- // a carrier exists. Caught at the pre-commit checkpoint.
537
380
  io.log(` ${unbindable} adjudicated unbindable — ${Object.entries(reasons).map(([k, v]) => `${v} ${k}`).join(", ")}`);
538
381
  io.log("");
539
382
  io.log(" THE RATIO IS A FINDING, not bookkeeping: it measures how much of a core skill is artifact");
540
383
  io.log(" discipline and how much is agent judgement. Read the SPLIT, not the total — the");
541
384
  io.log(" judgement-only rows are the A/B clause's FIRST subject; cross-language and already-carried");
542
385
  io.log(" mean a carrier exists and is somewhere else.");
543
- // **Widened 2026-08-28 on the maintainer's ruling**; this printed "only the judgement-only rows are
544
- // the A/B clause's subject" until then. That reading could not stand, and the reason is measured:
545
- // `vendor --host` carries `core/skills/` not at all, so all five judgement rows reach
546
- // nothing in a vendored arm. The A/B clause's subject is the mandates core SHIPS that an arm
547
- // actually receives. Four carriers held the narrower reading and all four moved together —
548
- // ../evals/ab/corpus.md, and the claim is registered in ../.portulan/rule-carriers.json.
549
386
  io.log(" A `census` row re-indexes a figure an existing recipe already prints; it is not a new check.");
550
387
  io.log(" `unbindable` is the dodge, so its reason comes from a closed vocabulary and is adjudicated.");
551
388
  io.log(" `carrier` is a DECLARED, reviewed field. Nothing links it to a check: a corpus naming a");
@@ -572,10 +409,7 @@ export function run(argv = process.argv.slice(2), io = console) {
572
409
  }
573
410
  }
574
411
 
575
- // The entry guard, in the ONE form `./rule-carriers.mjs` designates: `file://${argv[1]}` percent-encodes
576
- // differently from `import.meta.url` and this working copy lives under a path with spaces, so the naive
577
- // spelling exits 0 having run nothing — met four times in this repository, once in the session before
578
- // this one.
412
+ // Not `file://${argv[1]}`: `import.meta.url` percent-encodes, so a path with a space would never match.
579
413
  function isMain() {
580
414
  const invoked = process.argv[1];
581
415
  if (!invoked) return false;