bareloop 0.25.1 → 0.27.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.
@@ -1,3 +1,5 @@
1
+ /** @param {boolean} isRepo @returns {typeof DESTINATION_FIELD_REPO} */
2
+ export function destinationFieldFor(isRepo: boolean): typeof DESTINATION_FIELD_REPO;
1
3
  /** The frozen question set for a picked class. Throws on a locked or unknown
2
4
  * one: reaching here with either means admission did not refuse, which is a
3
5
  * bareloop bug, and failing loudly beats interviewing for a class we cannot run.
@@ -6,6 +8,32 @@ export function questionsFor(verdictType: string): any;
6
8
  /** The question numbers whose answer must be present for that class's interview
7
9
  * to be finished. @param {string} verdictType @returns {number[]} */
8
10
  export function requiredAnswersFor(verdictType: string): number[];
11
+ /** The signed table's label for each of that class's question keys (PRD item
12
+ * 33's "The intake form (RULED)" ruling) — a key with no table row (hitl's own
13
+ * fourth) is simply absent, never a fabricated label. Throws on the same locked
14
+ * or unknown class {@link questionsFor} does, for the same reason.
15
+ * @param {string} verdictType @returns {Record<string|number, string>} */
16
+ export function labelsFor(verdictType: string): Record<string | number, string>;
17
+ /**
18
+ * THE REAL PROTECTIONS a person is shown, and what {@link runConfirmTurn}
19
+ * records as `accepted.protections` — fixes finding #1 from live run
20
+ * mu0voeo4 (2026-09-14): the confirm turn used to show whatever prose the
21
+ * MODEL invented for `protections`, and that run's model invented a
22
+ * "behavior-preservation guard" this build cannot check — no kind in the
23
+ * catalogue can verify runtime behaviour, and the author call's own note
24
+ * elsewhere says outright that behaviour-preservation is not composed here.
25
+ * This reads the SAME guards `classGuards` will actually compose into the
26
+ * close — one source, never a second hand-typed list — rendered through the
27
+ * frozen line in {@link GUARD_DESCRIPTIONS}, plus the write fence (a real,
28
+ * signed fence — {@link writeScopeBlock}'s own subject) when one is set.
29
+ * @param {{verdictType: string, lang: string, writeScope?: string[]|null}} o
30
+ * @returns {string[]}
31
+ */
32
+ export function confirmProtections({ verdictType, lang, writeScope }: {
33
+ verdictType: string;
34
+ lang: string;
35
+ writeScope?: string[] | null;
36
+ }): string[];
9
37
  /**
10
38
  * The mechanical seam between the catalogue and the schema, read BOTH ways: a
11
39
  * catalogue parameter with no schema would be silently undeclarable, and a schema
@@ -196,6 +224,52 @@ export function catalogueBlock(catalogue?: Record<string, any>, { verdictType }?
196
224
  export function instrumentsBlock(lang: string): string;
197
225
  /** The three laws. Stated to the author; enforced, where checkable, by M2. */
198
226
  export function lawsBlock(): string;
227
+ /**
228
+ * WHAT MAY CHANGE, AND WHAT IS ONLY READ — replaces the old Q2 (PRD item 33 M3
229
+ * piece 3). Q2 used to ask the person, in prose, which files change and which
230
+ * are read-only; that decision is now Destination, PROVEN against the
231
+ * repository rather than typed (`docs/product/ITEM33-BUILD.md` "M3", ruling 2),
232
+ * and for a repo source Destination's answer IS the signed `writeScope` fence.
233
+ * This states that fact to the composer instead of letting it vanish along
234
+ * with Q2 — the same "nothing the composer received before may silently
235
+ * disappear" rule `applyGenreEnv` already keeps for a dropped env var.
236
+ *
237
+ * THROWS on an empty `writeScope`: Destination is a MANDATORY fence
238
+ * (`run-interview.mjs` refuses an empty answer at capture), so a caller
239
+ * reaching this with nothing to state is a bareloop wiring bug, not a job with
240
+ * no fence — a composer told it may change everything guards nothing.
241
+ * @param {string[]} writeScope
242
+ */
243
+ export function writeScopeBlock(writeScope: string[]): string;
244
+ /**
245
+ * THE CONFIRMED-PLAN BLOCK (PRD item 33 M3 piece 4) — present only when the
246
+ * person already ran a confirm turn (`runConfirmTurn`'s `accepted`). States
247
+ * the composition law as an ORDER over the ACCEPTED checks, exactly the
248
+ * pattern {@link writeScopeBlock} already is for the fence: "compose these
249
+ * checks and no other" rather than a code matcher over the goal prose
250
+ * (ruling 6). Protections are restated as unchanged, never as checks to add.
251
+ * Open questions the person's own fix rounds left unresolved (D3) are named
252
+ * so the composer states them in notes rather than silently deciding them.
253
+ * `notChecked` (fix #1, run mu0voeo4) is anything the person asked for that
254
+ * no listed check can verify — shown so the composer names the gap in its
255
+ * own notes rather than the plan quietly reading as if everything asked for
256
+ * is covered.
257
+ * `answeredQuestions` (F175's open half, run mu0voeo4, hamr's 2026-09-16
258
+ * ruling) is every `Q:/A:` pair the person answered inline when a "Confirm"
259
+ * or "Type the goal yourself" pick forced a raised question before
260
+ * acceptance — BINDING, never re-decided by the composer, and a DIFFERENT
261
+ * meaning from `openQuestions` (which means "could not resolve"): the two
262
+ * never merge.
263
+ * @param {{checks: string[], protections: string[], openQuestions?: string[], notChecked?: string[],
264
+ * answeredQuestions?: string[]}} confirmed
265
+ */
266
+ export function confirmedBlock(confirmed: {
267
+ checks: string[];
268
+ protections: string[];
269
+ openQuestions?: string[];
270
+ notChecked?: string[];
271
+ answeredQuestions?: string[];
272
+ }): string;
199
273
  /**
200
274
  * The whole authoring prompt, as one string.
201
275
  *
@@ -208,12 +282,19 @@ export function lawsBlock(): string;
208
282
  * the one thing that tells the composer what DONE is allowed to mean here; a
209
283
  * defaulted class would be the prompt answering a question the user was asked.
210
284
  *
285
+ * `writeScope` carries what the old Q2 used to (PRD item 33 M3 piece 3): Q2 is
286
+ * gone from the numbered interview, so what may change and what is read-only
287
+ * reaches the composer through {@link writeScopeBlock} instead, fed from
288
+ * Destination's own proven fence — never silently dropped.
289
+ *
211
290
  * @param {{answers: Record<string|number, string>, questions?: Record<string|number, string>,
212
291
  * facts: any, listingBlock: string, lang: string, verdictType: string,
213
292
  * guards: {name: string, kind: string, params: Record<string, any>, fill: string[]}[],
214
- * ownedEnvNames?: string[], mode?: 'tool'|'text', catalogue?: Record<string, any>}} o
293
+ * ownedEnvNames?: string[], mode?: 'tool'|'text', catalogue?: Record<string, any>,
294
+ * writeScope?: string[]|null,
295
+ * confirmed?: {checks: string[], protections: string[], openQuestions?: string[], notChecked?: string[], answeredQuestions?: string[]}|null}} o
215
296
  */
216
- export function authorPrompt({ answers, questions, facts, listingBlock, lang, verdictType, guards, ownedEnvNames, mode, catalogue }: {
297
+ export function authorPrompt({ answers, questions, facts, listingBlock, lang, verdictType, guards, ownedEnvNames, mode, catalogue, writeScope, confirmed, }: {
217
298
  answers: Record<string | number, string>;
218
299
  questions?: Record<string | number, string>;
219
300
  facts: any;
@@ -229,6 +310,14 @@ export function authorPrompt({ answers, questions, facts, listingBlock, lang, ve
229
310
  ownedEnvNames?: string[];
230
311
  mode?: "tool" | "text";
231
312
  catalogue?: Record<string, any>;
313
+ writeScope?: string[] | null;
314
+ confirmed?: {
315
+ checks: string[];
316
+ protections: string[];
317
+ openQuestions?: string[];
318
+ notChecked?: string[];
319
+ answeredQuestions?: string[];
320
+ } | null;
232
321
  }): string;
233
322
  /**
234
323
  * The seed read, as the model sees it: mechanical labels, no editorial. Per-stage
@@ -495,6 +584,119 @@ export function askStructured({ messages, generate, mode, retries, label, book,
495
584
  red: Red | null;
496
585
  budget: "cap-halt" | "pricing-red" | null;
497
586
  }>;
587
+ /** The output channel. It records and acknowledges; it takes no action.
588
+ * @param {{calls: any[]}} box */
589
+ export function confirmTool(box: {
590
+ calls: any[];
591
+ }): {
592
+ name: string;
593
+ description: string;
594
+ parameters: Record<string, any>;
595
+ execute: (args: any) => Promise<string>;
596
+ };
597
+ /**
598
+ * The confirm turn's per-round prompt (model-facing, registered). Shows the
599
+ * person's own answers with their labels (the signed table's Field/Holds
600
+ * pairing, same as {@link authorPrompt}'s interview block), the survey or
601
+ * listing already paid for, the write fence, the detected language, "worse
602
+ * than before" when the person gave one (repo only), and — on a fix round —
603
+ * what the person asked to change.
604
+ * F183 (2026-09-16, ruling A, run mu2qmept): also carries the REAL,
605
+ * code-derived `protections` list ({@link confirmProtections} — the same
606
+ * guards the close will actually compose plus the real write fence, never a
607
+ * second hand-typed list) as an ALREADY COVERED block, with the instruction
608
+ * that anything on it never belongs in `notChecked`. Twice live (mu2bjmed,
609
+ * mu2qmept) the model claimed a guard or the write fence was missing while
610
+ * blind to what was actually enforced — this is the fix: show it the facts
611
+ * before it drafts, never hand-write a matcher over its answer.
612
+ * @param {{answers: Record<string|number, string>, questions: Record<string|number, string>,
613
+ * labels?: Record<string|number, string>, facts?: any, listing?: string|null,
614
+ * writeScope?: string[]|null, isRepo: boolean, lang: string, worseThanBefore?: string,
615
+ * fixText?: string|null, protections?: string[]}} o
616
+ */
617
+ export function confirmPrompt({ answers, questions, labels, facts, listing, writeScope, isRepo, lang, worseThanBefore, fixText, protections }: {
618
+ answers: Record<string | number, string>;
619
+ questions: Record<string | number, string>;
620
+ labels?: Record<string | number, string>;
621
+ facts?: any;
622
+ listing?: string | null;
623
+ writeScope?: string[] | null;
624
+ isRepo: boolean;
625
+ lang: string;
626
+ worseThanBefore?: string;
627
+ fixText?: string | null;
628
+ protections?: string[];
629
+ }): string;
630
+ /**
631
+ * THE CONFIRM TURN: the $0 half (worse-than-before for a repo, a language
632
+ * pick when `lang` carries `detectLanguage`'s own `ambiguous` shape), then up
633
+ * to 2 PAID rounds (ruling 5) of draft → show → the person's menu pick
634
+ * (ruling 5 addendum). `ask` is the ONE interactive seam — an async function
635
+ * from a "what's being asked" descriptor to the person's answer, or `null`
636
+ * meaning input ended (the same signal `scripts/run-interview.mjs`'s own
637
+ * `nextLine()` already uses) — never a process exit here, because this is a
638
+ * library function, not a script. A `null` at ANY ask stops the whole turn as
639
+ * `confirm-abandoned`, spending nothing beyond whatever rounds already ran.
640
+ *
641
+ * D3's shape is enforced by the loop bound alone (`round <= 2`), never a
642
+ * counter checked after the fact: a "fix" on round 2 returns immediately —
643
+ * there is no 3rd call to make.
644
+ *
645
+ * @param {{verdictType: string, answers: Record<string|number, string>,
646
+ * questions: Record<string|number, string>, labels?: Record<string|number, string>,
647
+ * facts?: any, listing?: string|null, writeScope?: string[]|null, isRepo: boolean,
648
+ * lang: string|{kind: 'ambiguous', candidates: string[], dir: string},
649
+ * generate: Function, book: ReturnType<typeof makeCostBook>,
650
+ * ask: (step: {kind: string, [k: string]: any}) => Promise<string|null>,
651
+ * onPhase?: (phase: string, data?: any) => void, mode?: 'tool'|'text',
652
+ * worseThanBefore?: string}} o
653
+ * @returns {Promise<{ok: boolean,
654
+ * stop: null|'cap-halt'|'pricing-red'|'provider-red'|'artifact-red'|'confirm-abandoned'|'confirm-restart',
655
+ * rounds: number,
656
+ * accepted: {goal: string, checks: string[], protections: string[], lang: string,
657
+ * worseThanBefore: string, openQuestions: string[], notChecked: string[], answeredQuestions?: string[]}|null,
658
+ * reds: Red[], cost: any}>}
659
+ */
660
+ export function runConfirmTurn({ verdictType, answers, questions, labels, facts, listing, writeScope, isRepo, lang, generate, book, ask, onPhase, mode, worseThanBefore: presetWorseThanBefore, }: {
661
+ verdictType: string;
662
+ answers: Record<string | number, string>;
663
+ questions: Record<string | number, string>;
664
+ labels?: Record<string | number, string>;
665
+ facts?: any;
666
+ listing?: string | null;
667
+ writeScope?: string[] | null;
668
+ isRepo: boolean;
669
+ lang: string | {
670
+ kind: "ambiguous";
671
+ candidates: string[];
672
+ dir: string;
673
+ };
674
+ generate: Function;
675
+ book: ReturnType<typeof makeCostBook>;
676
+ ask: (step: {
677
+ kind: string;
678
+ [k: string]: any;
679
+ }) => Promise<string | null>;
680
+ onPhase?: (phase: string, data?: any) => void;
681
+ mode?: "tool" | "text";
682
+ worseThanBefore?: string;
683
+ }): Promise<{
684
+ ok: boolean;
685
+ stop: null | "cap-halt" | "pricing-red" | "provider-red" | "artifact-red" | "confirm-abandoned" | "confirm-restart";
686
+ rounds: number;
687
+ accepted: {
688
+ goal: string;
689
+ checks: string[];
690
+ protections: string[];
691
+ lang: string;
692
+ worseThanBefore: string;
693
+ openQuestions: string[];
694
+ notChecked: string[];
695
+ answeredQuestions?: string[];
696
+ } | null;
697
+ reds: Red[];
698
+ cost: any;
699
+ }>;
498
700
  /**
499
701
  * THE AUTHORING FLOW.
500
702
  *
@@ -506,7 +708,13 @@ export function askStructured({ messages, generate, mode, retries, label, book,
506
708
  * The return is deliberately complete rather than tidy:
507
709
  * - `ok` is true iff a declaration passed validation and was measured;
508
710
  * - `declaration` is the LAST ACCEPTED one, genre-env injected — the exact form
509
- * that will run and that the signature will cover;
711
+ * that will run and that the signature will cover; unless F176's fallback
712
+ * fired (see `fellBack`), in which case it is the newest SOUND accepted one
713
+ * instead — a "last accepted" close whose seed read instrument-stopped is
714
+ * never handed to a signer as though it were the best close this run found;
715
+ * - `fellBack` is `null` unless the fallback above fired, in which case it names
716
+ * `{from, to, brokenStages}` — which iteration was dropped, which one now
717
+ * stands, and which of the dropped one's stages instrument-stopped;
510
718
  * - `reds` is what the LAST validated iteration said. `ok:true` with a non-empty
511
719
  * `reds` means the final revision was rejected and the previous close stands:
512
720
  * the rejection is reported, never hidden, and never silently shipped either;
@@ -531,9 +739,12 @@ export function askStructured({ messages, generate, mode, retries, label, book,
531
739
  * onPhase?: (phase: string, data?: any) => void,
532
740
  * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
533
741
  * maxRevisions?: number, structureRetries?: number,
534
- * structuredMode?: 'tool'|'text', catalogue?: Record<string, any>}} o
742
+ * structuredMode?: 'tool'|'text', catalogue?: Record<string, any>, writeScope?: string[]|null,
743
+ * priorCalls?: {label: string, costUsd: number|null, unpricedRounds: number}[]|null,
744
+ * priorRaws?: any[]|null,
745
+ * confirmed?: {checks: string[], protections: string[], openQuestions?: string[], notChecked?: string[], answeredQuestions?: string[]}|null}} o
535
746
  */
536
- export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, scout, listing, generate, seedReadFn, closeCtx, ceilingUsd, onPhase, onCall, maxRevisions, structureRetries, structuredMode, catalogue, }: {
747
+ export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, writeScope, scout, listing, priorCalls, priorRaws, confirmed, generate, seedReadFn, closeCtx, ceilingUsd, onPhase, onCall, maxRevisions, structureRetries, structuredMode, catalogue, }: {
537
748
  workdir: string;
538
749
  seedRef: string;
539
750
  lang: string;
@@ -569,6 +780,20 @@ export function authorClose({ workdir, seedRef, lang, verdictType, answers, ques
569
780
  structureRetries?: number;
570
781
  structuredMode?: "tool" | "text";
571
782
  catalogue?: Record<string, any>;
783
+ writeScope?: string[] | null;
784
+ priorCalls?: {
785
+ label: string;
786
+ costUsd: number | null;
787
+ unpricedRounds: number;
788
+ }[] | null;
789
+ priorRaws?: any[] | null;
790
+ confirmed?: {
791
+ checks: string[];
792
+ protections: string[];
793
+ openQuestions?: string[];
794
+ notChecked?: string[];
795
+ answeredQuestions?: string[];
796
+ } | null;
572
797
  }): Promise<any>;
573
798
  /** @typedef {{code: string, path: string, detail: string, [k: string]: any}} Red */
574
799
  /** the output channel's name. It takes no action; it carries the answer. */
@@ -604,114 +829,157 @@ export const REVISE_INSTRUCTION: string;
604
829
  export const STRUCTURE_INSTRUCTION_TOOL: string;
605
830
  export const STRUCTURE_INSTRUCTION_TEXT: string;
606
831
  /**
607
- * THE INTERVIEW, KEYED BY VERDICT CLASS (hamr's ruling, PRD v1.57 §2).
608
- *
609
- * One question set per GENRE has no terminating shape — genres are a fat long
610
- * tail that cannot be enumerated, and TYPES was the first specimen rather than
611
- * the pattern. One set per verdict CLASS has exactly three. Genre understanding
612
- * does not disappear: it moves into the COMPOSITION over the kind catalogue
613
- * (`CLASS_STATEMENTS` below, rendered into the authoring prompt), which is the
614
- * layer already built to hold it — the authoring call composes owned kinds and
615
- * cannot emit anything else (D2/D3), so a genre it has never seen is a
616
- * composition problem, not a catalogue problem.
617
- *
618
- * The GREEN set descends from questions 1–6 of the frozen TYPES set (prereg §5).
619
- * TWO slots have been deleted since, both because the interview was ASKING FOR
620
- * SOMETHING IT ALREADY HAD or already knew:
621
- *
622
- * - D13's genre CONFIRM ("this looks like a type-fixing job, correct?") —
623
- * superseded in full: the interview never asks about a genre again, and the
624
- * refusal it carried MOVED to the composer, on the same counted
625
- * `request-red` path (see `authorCloseForJob`);
626
- * - "Is there a code repo I can look at? Where?" — DROPPED on hamr's ruling
627
- * after he drove the terminal interview himself (2026-08-15, verbatim: *"drop
628
- * Q6"*). The repository is a MANDATORY, STRUCTURED input already —
629
- * `runInterview`'s own `repoPath`, which the runner takes from `--patient` and
630
- * refuses to start without. Asking a person to re-type in prose a path the
631
- * machine is holding in a variable is the SWE tax this product refuses, and
632
- * it invites a second, drifting answer for the same fact (hamr's own answer
633
- * on the live run was *"Yes — the patient itself."*). Nothing downstream lost
634
- * anything: the scout reads `repoPath`, never the answer.
635
- *
636
- * Question 2 was WIDENED in the same ruling: it now asks for the read-or-draw-from
637
- * files as well as the ones that change. The two halves belong in one answer
638
- * because they are one decision for the person answering — what the work touches,
639
- * and what it touches only to look at.
640
- *
641
- * The five that remain are genre-neutral, which is why every generalisation so far
642
- * has deleted a slot rather than reworded one. They are NUMBERED CONTIGUOUSLY from
643
- * 1: the number a person sees is the key their answer is filed under, and a gap
644
- * would be a hole in a form nobody can explain.
832
+ * THE UNIFIED INTAKE FORM (PRD item 33 M3 piece 3, `docs/product/ITEM33-BUILD.md`
833
+ * "M3", and the 2026-09-10 signed ruling "The intake form (RULED)" in
834
+ * `docs/product/PRD.md` item 33). Motivated by run `mtv8jihy` (F159): the OLD
835
+ * five-question green set produced a rubric compiled onto doc-comment rules —
836
+ * the wrong bar for what the person actually asked — because Q2 asked in prose
837
+ * for a decision the machine can prove instead, and Q3/Q5 asked the same "what
838
+ * must not go wrong" question twice from two directions.
839
+ *
840
+ * THE SAME SIX FIELDS FOR GREEN AND SOFT-GREEN, in the order a person sees them:
841
+ *
842
+ * Source replaces the read half of the old Q2 — MECHANICAL, proven
843
+ * against the machine ($0), never typed as free prose.
844
+ * Destination replaces the change half of the old Q2 (and, for a repo
845
+ * source, IS the signed `writeScope` fence) — MECHANICAL,
846
+ * same reason.
847
+ * Goal the old Q1, unchanged: "What do you want done?"
848
+ * What success looks like the old Q4, unchanged: a machine-countable check.
849
+ * Guardrails the old Q3 ("must not change") and Q5 ("worse than
850
+ * before") JOINED into one field — they were always one
851
+ * decision answered twice, and Q5 already only mattered for
852
+ * a repo source (the M3 confirm turn, not built in this
853
+ * piece, is where the repo-only half of it gets asked again
854
+ * if needed).
855
+ * Judge examples soft-green only — the old Q7 verbatim ("one you'd pass and
856
+ * one you'd fail, and say why"). The old Q6 ("what separates
857
+ * a pass from a fail") is retired as its own question: the
858
+ * "why" lines of a real pass/fail pair are what the rubric
859
+ * card compiles from (`cardauthor.js`), and asking the
860
+ * differentiator twice — once in the abstract, once against
861
+ * a real example — is the same double-ask Q3/Q5 already were.
862
+ *
863
+ * SOURCE and DESTINATION ARE MECHANICAL FIELDS, not entries in the numbered
864
+ * question record below: `runInterview`'s `answers` dict is what a close's
865
+ * composer prompt quotes verbatim (Q<n>/A<n>), and quoting a machine-proven fact
866
+ * back at the model as if a person had typed it would be the model reading a
867
+ * fabricated conversation. Their WORDING still lives here (`SOURCE_FIELD`,
868
+ * `DESTINATION_FIELD_REPO`, `DESTINATION_FIELD_PLAIN`, immediately below) so
869
+ * `scripts/run-interview.mjs` prints library text and nothing of its own, exactly
870
+ * as ruling 1 requires for the free-text questions.
871
+ *
872
+ * The free-text trio (soon quartet, soft-green only) IS numbered contiguously
873
+ * from 1 — the number a person sees is the key their answer is filed under, and
874
+ * that is what `questionsFor`/`requiredAnswersFor`/`QUESTION_SETS` still expose,
875
+ * unchanged in shape, to every existing caller.
876
+ */
877
+ export const SOURCE_FIELD: Readonly<{
878
+ id: "source";
879
+ kind: "mechanical";
880
+ label: "Source";
881
+ prompt: "A local folder, subfolder or file, or one URL the machine can reach (no login, no setup).";
882
+ }>;
883
+ /** Destination's wording when Source resolved to a git repo: the answer IS the
884
+ * signed `writeScope` fence (ITEM33-BUILD.md M3 ruling 2). */
885
+ export const DESTINATION_FIELD_REPO: Readonly<{
886
+ id: "destination";
887
+ kind: "mechanical";
888
+ label: "Destination";
889
+ prompt: string;
890
+ }>;
891
+ /** Destination's wording for every other kind of Source (a plain folder, a
892
+ * file, a URL): a directory the run may write into, never a filename
893
+ * (2026-09-12 Destination correction, PRD item 33). */
894
+ export const DESTINATION_FIELD_PLAIN: Readonly<{
895
+ id: "destination";
896
+ kind: "mechanical";
897
+ label: "Destination";
898
+ prompt: string;
899
+ }>;
900
+ /**
901
+ * THE FREE-TEXT TRIO EVERY CLASS SHARES: Goal, What success looks like,
902
+ * Guardrails — numbered contiguously from 1. This is what `answers[1..3]` are
903
+ * filed under for every class; soft-green appends a fourth
904
+ * (`SOFTGREEN_QUESTIONS`) and hitl appends a different fourth
905
+ * (`HITL_QUESTIONS`), each its own next number rather than a fixed one, so a
906
+ * future trim of this trio renumbers instead of leaving a gap.
907
+ *
908
+ * THE TEXT IS THE SIGNED TABLE'S "Holds" COLUMN, VERBATIM (PRD item 33's
909
+ * 2026-09-10 "The intake form (RULED)" ruling, `docs/product/PRD.md`) — not the
910
+ * old worded questions ("What do you want done?", "How do you check today
911
+ * whether it's working?", "...worse than before?"). Motivated by run `mtv8jihy`
912
+ * (F159, `docs/logs/FINDINGS.md`): a rulebook this prompt commit did not touch
913
+ * still had to compile against a card built from a person's actual bar, and the
914
+ * old wording never matched what got signed — this reshape (commit 1ac4d05)
915
+ * trimmed the SET to three slots but left their PROSE unreconciled with the
916
+ * table it was meant to realize. `FIELD_LABELS` (below) carries the matching
917
+ * label for each key, so a caller wanting BOTH shows them together — see
918
+ * `authorPrompt`'s interview block and `scripts/run-interview.mjs`.
645
919
  */
646
920
  export const GREEN_QUESTIONS: Readonly<{
647
- 1: "What do you want done?";
648
- 2: "Which files or folders should change? And which files should the work read or draw from (they stay untouched)?";
649
- 3: "Is there anything that must not change?";
650
- 4: "How do you check today whether it's working?";
651
- 5: "What would make you say this came back worse than before?";
921
+ 1: "what you want to achieve";
922
+ 2: "checks a machine can count";
923
+ 3: "what must not happen or change";
652
924
  }>;
653
925
  /**
654
- * THE HITL SET (N4 slice 1) — the green questions BYTE FOR BYTE, plus one.
655
- *
656
- * The five are unchanged deliberately rather than economically: the
657
- * mechanical-first composition law (2026-08-07) says every hitl close is
658
- * *deterministic stages first, judge minimal, human last*, so a hitl job needs
659
- * exactly the same mechanical facts a green one does — what changes, what must
660
- * not, how it is checked today, what "worse" looks like. Rewording them would
661
- * fork one interview into two that drift.
662
- *
663
- * The last one is the only thing the green set cannot supply, and it is not a
664
- * flourish: `human-confirms` requires an `ask` — the question the signer answers
665
- * at the end of the run — and nothing else in the interview names it. Without it
666
- * the composer would have to invent what a person is deciding, which is the one
667
- * thing a human stage must never have done for it. It is the SPREAD's next number
668
- * rather than a fixed one, so a green-side deletion renumbers it instead of
669
- * leaving a gap (it was 7 while the green set held six, and is 6 now it holds five).
926
+ * The signed table's own label for each of {@link GREEN_QUESTIONS}' keys — "the
927
+ * Field column", where {@link GREEN_QUESTIONS} is "the Holds column". Kept as a
928
+ * SEPARATE map rather than folding the label into the question string itself:
929
+ * every existing caller of `questionsFor`/`requiredAnswersFor`/`QUESTION_SETS`
930
+ * (`src/authorjob.js`, `src/cardauthor.js`, `scripts/run-interview.mjs`) treats
931
+ * a question as a plain string handed verbatim into a prompt or a `Q<n>. `
932
+ * line, and folding a label in would change that shape for every one of them.
933
+ * A caller that wants the label calls {@link labelsFor} exactly the way it
934
+ * calls `questionsFor` — additive, nothing existing breaks.
670
935
  */
671
- export const HITL_QUESTIONS: Readonly<{
672
- 6: "When you look at the finished result yourself, what are you deciding?";
673
- 1: "What do you want done?";
674
- 2: "Which files or folders should change? And which files should the work read or draw from (they stay untouched)?";
675
- 3: "Is there anything that must not change?";
676
- 4: "How do you check today whether it's working?";
677
- 5: "What would make you say this came back worse than before?";
936
+ export const FIELD_LABELS: Readonly<{
937
+ 1: "Goal";
938
+ 2: "What success looks like";
939
+ 3: "Guardrails";
940
+ }>;
941
+ /** The soft-green quartet's own key for Judge Examples — exported so
942
+ * `cardauthor.js` reads the SAME number this module files the answer under,
943
+ * rather than a second, hand-typed "6"/"7". */
944
+ export const SOFTGREEN_JUDGE_EXAMPLES_KEY: number;
945
+ /** The Judge Examples question — the signed table's own Holds text, verbatim
946
+ * ("one pass, one fail, and why"), not the old worded Q7 ("Give one example
947
+ * you'd pass and one you'd fail, and say why."). Exported so `cardauthor.js`'s
948
+ * prompt and this module's own `SOFTGREEN_QUESTIONS` render the identical
949
+ * string; its label ("Judge examples") lives in `SOFTGREEN_FIELD_LABELS`. */
950
+ export const JUDGE_EXAMPLES_QUESTION: "one pass, one fail, and why";
951
+ /** {@link FIELD_LABELS} plus Judge Examples' own label, for the soft-green set. */
952
+ export const SOFTGREEN_FIELD_LABELS: Readonly<{
953
+ 1: "Goal";
954
+ 2: "What success looks like";
955
+ 3: "Guardrails";
678
956
  }>;
679
957
  /**
680
- * THE SOFTGREEN SET (module 3, design record §4.6) — the green questions BYTE
681
- * FOR BYTE, plus TWO.
682
- *
683
- * Same discipline as the hitl set, and for the same reason: a softgreen close is
684
- * *mechanical stages first, the judged stage after* (§4.5), so a softgreen job
685
- * needs every mechanical fact a green one does. No renumbering of the green set,
686
- * no genre question, nothing reworded.
687
- *
688
- * The two additions are the only things the green set cannot supply, and neither
689
- * is a flourish — they are the judged floor's two REQUIRED inputs:
690
- * - Q6 becomes the RUBRIC CARD (§4.3): the person's own pass/fail lines,
691
- * compiled into enumerated items, signed, and enumerated in the spec hash.
692
- * The documented ceiling rides with it — the judge catches violations of
693
- * STATED card items only, and the fix for a miss is a new card line and a
694
- * re-sign, never a smarter judge.
695
- * - Q7 becomes the frozen CALIBRATION SET (§4.4): the whole pipe must grade it
696
- * correctly before the close is signable.
697
- * Both are asked in the person's own terms — an example they would pass and one
698
- * they would fail, not a rubric they are asked to write — because compiling a lay
699
- * answer into extractable items is the LLM's job (D5, proposed-and-signed) and
700
- * asking the person to do it is the SWE tax this product refuses.
701
- *
702
- * What the two answers COMPILE INTO is module 4. This module carries them
703
- * exactly as the hitl set carries its own last answer: they ride in `answers`,
704
- * they reach the composer verbatim through the prompt's interview block, and
705
- * nothing here derives anything from them.
958
+ * THE SOFTGREEN SET — the green trio plus Judge Examples. A judged close needs
959
+ * exactly this one more thing the green set cannot supply: a real pass/fail
960
+ * pair in the person's own words, which module 4 (`cardauthor.js`) compiles
961
+ * into the signed rubric card and the frozen calibration set. It rides in
962
+ * `answers` and reaches the composer verbatim through the prompt's interview
963
+ * block, exactly as every other free-text answer does — this module derives
964
+ * nothing from it.
706
965
  */
707
966
  export const SOFTGREEN_QUESTIONS: Readonly<{
708
- 6: "When you judge the result yourself, what separates a pass from a fail? Name the few things you actually look for.";
709
- 7: "Give one example you'd pass and one you'd fail, and say why.";
710
- 1: "What do you want done?";
711
- 2: "Which files or folders should change? And which files should the work read or draw from (they stay untouched)?";
712
- 3: "Is there anything that must not change?";
713
- 4: "How do you check today whether it's working?";
714
- 5: "What would make you say this came back worse than before?";
967
+ 1: "what you want to achieve";
968
+ 2: "checks a machine can count";
969
+ 3: "what must not happen or change";
970
+ }>;
971
+ /**
972
+ * THE HITL SET (N4 slice 1, kept in code only — never offered in the form or
973
+ * anywhere customer-facing, PRD item 33 M3 ruling 8) — the green trio plus one.
974
+ * `human-confirms` requires an `ask`, the question the signer answers at the
975
+ * end of the run, and nothing else in the interview names it; without it the
976
+ * composer would have to invent what a person is deciding. It is the green
977
+ * set's next number, not a fixed one, so a green-side trim renumbers it too.
978
+ */
979
+ export const HITL_QUESTIONS: Readonly<{
980
+ 1: "what you want to achieve";
981
+ 2: "checks a machine can count";
982
+ 3: "what must not happen or change";
715
983
  }>;
716
984
  /**
717
985
  * The three sets, all LIVE as of softgreen module 3. The locked shape stays
@@ -721,11 +989,17 @@ export const SOFTGREEN_QUESTIONS: Readonly<{
721
989
  * distinction, at the interview layer) — and a locked class refuses at ADMISSION,
722
990
  * before its questions run, so the two readers throw rather than hand back
723
991
  * nothing. A fourth class arrives locked, and the machinery is here for it.
724
- * @type {Record<string, {locked: boolean, questions: Record<string|number, string>|null, required: readonly number[]|null}>}
992
+ * `labels` travels ALONGSIDE `questions`, one label per key that the signed
993
+ * table names one for (PRD item 33 M3 piece 3's wording fix) — hitl's own
994
+ * fourth question has no table row (it is never customer-facing, ruling 8) and
995
+ * so carries none; a reader finding no label for a key falls back to showing
996
+ * none, never a fabricated one.
997
+ * @type {Record<string, {locked: boolean, questions: Record<string|number, string>|null, labels: Record<string|number, string>, required: readonly number[]|null}>}
725
998
  */
726
999
  export const QUESTION_SETS: Record<string, {
727
1000
  locked: boolean;
728
1001
  questions: Record<string | number, string> | null;
1002
+ labels: Record<string | number, string>;
729
1003
  required: readonly number[] | null;
730
1004
  }>;
731
1005
  /**
@@ -739,6 +1013,85 @@ export const QUESTION_SETS: Record<string, {
739
1013
  * @type {Record<string, string|null>}
740
1014
  */
741
1015
  export const CLASS_STATEMENTS: Record<string, string | null>;
1016
+ /**
1017
+ * Asked ONLY for a repo source (ruling 2 addendum's D7) — a plain folder or a
1018
+ * URL has no "before" a repo's own history gives it to compare against. This
1019
+ * is the OLD Q5 wording, verbatim ("What would make you say this came back
1020
+ * worse than before?"), which M3 piece 3's reshape folded into Guardrails —
1021
+ * it survives here as its OWN field because the confirm turn (ruling 5's
1022
+ * 2026-09-13 addendum) includes it as a constraint only when the person
1023
+ * actually gave one, and folding it silently into the Guardrails free-text
1024
+ * slot would lose that "present or absent" distinction. An empty answer is
1025
+ * "nothing beyond what Guardrails already said" and is not required — see
1026
+ * the frozen-wording test below: this phrase appears NOWHERE in
1027
+ * {@link questionsFor}'s own sets, so a caller cannot double-ask it by
1028
+ * reading the wrong table.
1029
+ */
1030
+ export const WORSE_THAN_BEFORE_FIELD: Readonly<{
1031
+ id: "worseThanBefore";
1032
+ kind: "mechanical";
1033
+ label: "Worse than before";
1034
+ prompt: "What would make you say this came back worse than before?";
1035
+ }>;
1036
+ /**
1037
+ * Asked ONLY when `src/detectlang.js`'s `detectLanguage` reports its
1038
+ * `ambiguous` outcome — two supported languages' manifests at the same
1039
+ * (nearest) directory level (M3 ruling 3, point 3). The candidate list is
1040
+ * NOT embedded here: it travels with the caller's own `ambiguous` result
1041
+ * (`langResult.candidates`), so this module never hand-types a second copy
1042
+ * of the supported-language set. An unsupported pick still goes through the
1043
+ * existing `language-unsupported` refusal — this field only offers a choice
1044
+ * among the candidates `detectLanguage` actually found.
1045
+ */
1046
+ export const LANGUAGE_PICK_FIELD: Readonly<{
1047
+ id: "language";
1048
+ kind: "mechanical";
1049
+ label: "Language";
1050
+ prompt: "This repo has more than one supported language's manifest at the same level. Which one is this job about?";
1051
+ }>;
1052
+ /**
1053
+ * F175's open half (docs/logs/FINDINGS.md, run mu0voeo4), hamr's ruling
1054
+ * (2026-09-16): "Confirm" and "Type the goal yourself" may not bypass a
1055
+ * `questions` entry the model itself raised on the plan being accepted — the
1056
+ * person answers it inline, one question at a time, with NO extra model
1057
+ * call (the round cap and D3 are untouched). This is the `field` handed to
1058
+ * `ask({kind: 'answer', ...})` for each forced question — `question`,
1059
+ * `index` (1-based) and `total` ride alongside on the step itself, not on
1060
+ * this frozen constant, because they differ per question within one turn.
1061
+ */
1062
+ export const ANSWER_QUESTION_FIELD: Readonly<{
1063
+ id: "answerQuestion";
1064
+ kind: "mechanical";
1065
+ label: "Answer";
1066
+ prompt: string;
1067
+ }>;
1068
+ /**
1069
+ * THE CONFIRM TURN'S MENU — a structured CHOICE the person picks from after
1070
+ * each round, never free text matched against a pattern (ruling 6's "no
1071
+ * hand-authored check matcher" reasoning applies here too: the person's own
1072
+ * reply is read as one of these four keys, never parsed for intent).
1073
+ * confirm — sign the plan and the drafted goal sentence as shown.
1074
+ * fix — free text describing what to change; feeds round r+1, up
1075
+ * to the 2-round cap (ruling 5); after round 2 a "fix" is
1076
+ * passed to the composer verbatim and shown at signing as an
1077
+ * open question rather than spending a 3rd call (D3).
1078
+ * type-goal — the person's own sentence REPLACES the drafted goal,
1079
+ * verbatim (redacted); everything else in the plan stands.
1080
+ * start-over — abandon this confirm turn; the person reruns the
1081
+ * interview from the beginning (`confirm-restart`).
1082
+ * @type {Record<'confirm'|'fix'|'type-goal'|'start-over', string>}
1083
+ */
1084
+ export const CONFIRM_MENU: Record<"confirm" | "fix" | "type-goal" | "start-over", string>;
1085
+ /**
1086
+ * PLAIN-ENGLISH lines for the guards `classGuards` can compose, keyed by
1087
+ * guard NAME — frozen beside {@link CONFIRM_MENU} because both are constants
1088
+ * the confirm turn shows a person verbatim. One entry per guard that exists
1089
+ * in `MECHANICAL_GUARDS` (`src/authoring.js`); {@link confirmProtections}
1090
+ * throws if a guard `classGuards` hands back has no line here, so a battery
1091
+ * that grows a guard fails loudly instead of showing a blank description.
1092
+ * @type {Record<string, string>}
1093
+ */
1094
+ export const GUARD_DESCRIPTIONS: Record<string, string>;
742
1095
  export const AUTHOR_SYSTEM: string;
743
1096
  /**
744
1097
  * ONE schema per PARAMETER NAME, composed into kinds by the catalogue's own
@@ -967,6 +1320,24 @@ export const PARAM_SCHEMAS: Readonly<{
967
1320
  description: string;
968
1321
  };
969
1322
  }>;
1323
+ export const CONFIRM_TOOL_NAME: "confirm_plan";
1324
+ /** Acknowledgement text the confirm tool's `execute` returns — mirrors
1325
+ * {@link DECLARATION_ACK}'s role: an output channel that records and
1326
+ * acknowledges, takes no action, and ends the call the moment it fires
1327
+ * (`makeLoopGenerate` stops the loop on any tool execute). */
1328
+ export const CONFIRM_ACK: "plan received";
1329
+ /** the confirm channel's own STRUCTURE_INSTRUCTION_TOOL analogue, shown after
1330
+ * a malformed reply so the retry names the ONE required shape rather than
1331
+ * repeating the whole system prompt */
1332
+ export const CONFIRM_STRUCTURE_INSTRUCTION: string;
1333
+ /**
1334
+ * The confirm turn's system prompt (model-facing, registered — a commit
1335
+ * touching this needs Failure/Addresses/Corrects labels, `src/promptregisters.js`).
1336
+ * States ruling 6 ("a genre never adds a check the goal did not ask for") as
1337
+ * an ORDER to the model itself, the same register {@link AUTHOR_SYSTEM} and
1338
+ * {@link CLASS_STATEMENTS} already use, rather than a code matcher over prose.
1339
+ */
1340
+ export const CONFIRM_SYSTEM: string;
970
1341
  export type Red = {
971
1342
  code: string;
972
1343
  path: string;