bareloop 0.25.1 → 0.26.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,44 @@ 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
+ * @param {{checks: string[], protections: string[], openQuestions?: string[], notChecked?: string[]}} confirmed
258
+ */
259
+ export function confirmedBlock(confirmed: {
260
+ checks: string[];
261
+ protections: string[];
262
+ openQuestions?: string[];
263
+ notChecked?: string[];
264
+ }): string;
199
265
  /**
200
266
  * The whole authoring prompt, as one string.
201
267
  *
@@ -208,12 +274,19 @@ export function lawsBlock(): string;
208
274
  * the one thing that tells the composer what DONE is allowed to mean here; a
209
275
  * defaulted class would be the prompt answering a question the user was asked.
210
276
  *
277
+ * `writeScope` carries what the old Q2 used to (PRD item 33 M3 piece 3): Q2 is
278
+ * gone from the numbered interview, so what may change and what is read-only
279
+ * reaches the composer through {@link writeScopeBlock} instead, fed from
280
+ * Destination's own proven fence — never silently dropped.
281
+ *
211
282
  * @param {{answers: Record<string|number, string>, questions?: Record<string|number, string>,
212
283
  * facts: any, listingBlock: string, lang: string, verdictType: string,
213
284
  * guards: {name: string, kind: string, params: Record<string, any>, fill: string[]}[],
214
- * ownedEnvNames?: string[], mode?: 'tool'|'text', catalogue?: Record<string, any>}} o
285
+ * ownedEnvNames?: string[], mode?: 'tool'|'text', catalogue?: Record<string, any>,
286
+ * writeScope?: string[]|null,
287
+ * confirmed?: {checks: string[], protections: string[], openQuestions?: string[], notChecked?: string[]}|null}} o
215
288
  */
216
- export function authorPrompt({ answers, questions, facts, listingBlock, lang, verdictType, guards, ownedEnvNames, mode, catalogue }: {
289
+ export function authorPrompt({ answers, questions, facts, listingBlock, lang, verdictType, guards, ownedEnvNames, mode, catalogue, writeScope, confirmed, }: {
217
290
  answers: Record<string | number, string>;
218
291
  questions?: Record<string | number, string>;
219
292
  facts: any;
@@ -229,6 +302,13 @@ export function authorPrompt({ answers, questions, facts, listingBlock, lang, ve
229
302
  ownedEnvNames?: string[];
230
303
  mode?: "tool" | "text";
231
304
  catalogue?: Record<string, any>;
305
+ writeScope?: string[] | null;
306
+ confirmed?: {
307
+ checks: string[];
308
+ protections: string[];
309
+ openQuestions?: string[];
310
+ notChecked?: string[];
311
+ } | null;
232
312
  }): string;
233
313
  /**
234
314
  * The seed read, as the model sees it: mechanical labels, no editorial. Per-stage
@@ -495,6 +575,109 @@ export function askStructured({ messages, generate, mode, retries, label, book,
495
575
  red: Red | null;
496
576
  budget: "cap-halt" | "pricing-red" | null;
497
577
  }>;
578
+ /** The output channel. It records and acknowledges; it takes no action.
579
+ * @param {{calls: any[]}} box */
580
+ export function confirmTool(box: {
581
+ calls: any[];
582
+ }): {
583
+ name: string;
584
+ description: string;
585
+ parameters: Record<string, any>;
586
+ execute: (args: any) => Promise<string>;
587
+ };
588
+ /**
589
+ * The confirm turn's per-round prompt (model-facing, registered). Shows the
590
+ * person's own answers with their labels (the signed table's Field/Holds
591
+ * pairing, same as {@link authorPrompt}'s interview block), the survey or
592
+ * listing already paid for, the write fence, the detected language, "worse
593
+ * than before" when the person gave one (repo only), and — on a fix round —
594
+ * what the person asked to change.
595
+ * @param {{answers: Record<string|number, string>, questions: Record<string|number, string>,
596
+ * labels?: Record<string|number, string>, facts?: any, listing?: string|null,
597
+ * writeScope?: string[]|null, isRepo: boolean, lang: string, worseThanBefore?: string,
598
+ * fixText?: string|null}} o
599
+ */
600
+ export function confirmPrompt({ answers, questions, labels, facts, listing, writeScope, isRepo, lang, worseThanBefore, fixText }: {
601
+ answers: Record<string | number, string>;
602
+ questions: Record<string | number, string>;
603
+ labels?: Record<string | number, string>;
604
+ facts?: any;
605
+ listing?: string | null;
606
+ writeScope?: string[] | null;
607
+ isRepo: boolean;
608
+ lang: string;
609
+ worseThanBefore?: string;
610
+ fixText?: string | null;
611
+ }): string;
612
+ /**
613
+ * THE CONFIRM TURN: the $0 half (worse-than-before for a repo, a language
614
+ * pick when `lang` carries `detectLanguage`'s own `ambiguous` shape), then up
615
+ * to 2 PAID rounds (ruling 5) of draft → show → the person's menu pick
616
+ * (ruling 5 addendum). `ask` is the ONE interactive seam — an async function
617
+ * from a "what's being asked" descriptor to the person's answer, or `null`
618
+ * meaning input ended (the same signal `scripts/run-interview.mjs`'s own
619
+ * `nextLine()` already uses) — never a process exit here, because this is a
620
+ * library function, not a script. A `null` at ANY ask stops the whole turn as
621
+ * `confirm-abandoned`, spending nothing beyond whatever rounds already ran.
622
+ *
623
+ * D3's shape is enforced by the loop bound alone (`round <= 2`), never a
624
+ * counter checked after the fact: a "fix" on round 2 returns immediately —
625
+ * there is no 3rd call to make.
626
+ *
627
+ * @param {{verdictType: string, answers: Record<string|number, string>,
628
+ * questions: Record<string|number, string>, labels?: Record<string|number, string>,
629
+ * facts?: any, listing?: string|null, writeScope?: string[]|null, isRepo: boolean,
630
+ * lang: string|{kind: 'ambiguous', candidates: string[], dir: string},
631
+ * generate: Function, book: ReturnType<typeof makeCostBook>,
632
+ * ask: (step: {kind: string, [k: string]: any}) => Promise<string|null>,
633
+ * onPhase?: (phase: string, data?: any) => void, mode?: 'tool'|'text',
634
+ * worseThanBefore?: string}} o
635
+ * @returns {Promise<{ok: boolean,
636
+ * stop: null|'cap-halt'|'pricing-red'|'provider-red'|'artifact-red'|'confirm-abandoned'|'confirm-restart',
637
+ * rounds: number,
638
+ * accepted: {goal: string, checks: string[], protections: string[], lang: string,
639
+ * worseThanBefore: string, openQuestions: string[], notChecked: string[]}|null,
640
+ * reds: Red[], cost: any}>}
641
+ */
642
+ export function runConfirmTurn({ verdictType, answers, questions, labels, facts, listing, writeScope, isRepo, lang, generate, book, ask, onPhase, mode, worseThanBefore: presetWorseThanBefore, }: {
643
+ verdictType: string;
644
+ answers: Record<string | number, string>;
645
+ questions: Record<string | number, string>;
646
+ labels?: Record<string | number, string>;
647
+ facts?: any;
648
+ listing?: string | null;
649
+ writeScope?: string[] | null;
650
+ isRepo: boolean;
651
+ lang: string | {
652
+ kind: "ambiguous";
653
+ candidates: string[];
654
+ dir: string;
655
+ };
656
+ generate: Function;
657
+ book: ReturnType<typeof makeCostBook>;
658
+ ask: (step: {
659
+ kind: string;
660
+ [k: string]: any;
661
+ }) => Promise<string | null>;
662
+ onPhase?: (phase: string, data?: any) => void;
663
+ mode?: "tool" | "text";
664
+ worseThanBefore?: string;
665
+ }): Promise<{
666
+ ok: boolean;
667
+ stop: null | "cap-halt" | "pricing-red" | "provider-red" | "artifact-red" | "confirm-abandoned" | "confirm-restart";
668
+ rounds: number;
669
+ accepted: {
670
+ goal: string;
671
+ checks: string[];
672
+ protections: string[];
673
+ lang: string;
674
+ worseThanBefore: string;
675
+ openQuestions: string[];
676
+ notChecked: string[];
677
+ } | null;
678
+ reds: Red[];
679
+ cost: any;
680
+ }>;
498
681
  /**
499
682
  * THE AUTHORING FLOW.
500
683
  *
@@ -531,9 +714,12 @@ export function askStructured({ messages, generate, mode, retries, label, book,
531
714
  * onPhase?: (phase: string, data?: any) => void,
532
715
  * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
533
716
  * maxRevisions?: number, structureRetries?: number,
534
- * structuredMode?: 'tool'|'text', catalogue?: Record<string, any>}} o
717
+ * structuredMode?: 'tool'|'text', catalogue?: Record<string, any>, writeScope?: string[]|null,
718
+ * priorCalls?: {label: string, costUsd: number|null, unpricedRounds: number}[]|null,
719
+ * priorRaws?: any[]|null,
720
+ * confirmed?: {checks: string[], protections: string[], openQuestions?: string[], notChecked?: string[]}|null}} o
535
721
  */
536
- export function authorClose({ workdir, seedRef, lang, verdictType, answers, questions, scout, listing, generate, seedReadFn, closeCtx, ceilingUsd, onPhase, onCall, maxRevisions, structureRetries, structuredMode, catalogue, }: {
722
+ 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
723
  workdir: string;
538
724
  seedRef: string;
539
725
  lang: string;
@@ -569,6 +755,19 @@ export function authorClose({ workdir, seedRef, lang, verdictType, answers, ques
569
755
  structureRetries?: number;
570
756
  structuredMode?: "tool" | "text";
571
757
  catalogue?: Record<string, any>;
758
+ writeScope?: string[] | null;
759
+ priorCalls?: {
760
+ label: string;
761
+ costUsd: number | null;
762
+ unpricedRounds: number;
763
+ }[] | null;
764
+ priorRaws?: any[] | null;
765
+ confirmed?: {
766
+ checks: string[];
767
+ protections: string[];
768
+ openQuestions?: string[];
769
+ notChecked?: string[];
770
+ } | null;
572
771
  }): Promise<any>;
573
772
  /** @typedef {{code: string, path: string, detail: string, [k: string]: any}} Red */
574
773
  /** the output channel's name. It takes no action; it carries the answer. */
@@ -604,114 +803,157 @@ export const REVISE_INSTRUCTION: string;
604
803
  export const STRUCTURE_INSTRUCTION_TOOL: string;
605
804
  export const STRUCTURE_INSTRUCTION_TEXT: string;
606
805
  /**
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.
806
+ * THE UNIFIED INTAKE FORM (PRD item 33 M3 piece 3, `docs/product/ITEM33-BUILD.md`
807
+ * "M3", and the 2026-09-10 signed ruling "The intake form (RULED)" in
808
+ * `docs/product/PRD.md` item 33). Motivated by run `mtv8jihy` (F159): the OLD
809
+ * five-question green set produced a rubric compiled onto doc-comment rules —
810
+ * the wrong bar for what the person actually asked — because Q2 asked in prose
811
+ * for a decision the machine can prove instead, and Q3/Q5 asked the same "what
812
+ * must not go wrong" question twice from two directions.
813
+ *
814
+ * THE SAME SIX FIELDS FOR GREEN AND SOFT-GREEN, in the order a person sees them:
815
+ *
816
+ * Source replaces the read half of the old Q2 — MECHANICAL, proven
817
+ * against the machine ($0), never typed as free prose.
818
+ * Destination replaces the change half of the old Q2 (and, for a repo
819
+ * source, IS the signed `writeScope` fence) — MECHANICAL,
820
+ * same reason.
821
+ * Goal the old Q1, unchanged: "What do you want done?"
822
+ * What success looks like the old Q4, unchanged: a machine-countable check.
823
+ * Guardrails the old Q3 ("must not change") and Q5 ("worse than
824
+ * before") JOINED into one field — they were always one
825
+ * decision answered twice, and Q5 already only mattered for
826
+ * a repo source (the M3 confirm turn, not built in this
827
+ * piece, is where the repo-only half of it gets asked again
828
+ * if needed).
829
+ * Judge examples soft-green only — the old Q7 verbatim ("one you'd pass and
830
+ * one you'd fail, and say why"). The old Q6 ("what separates
831
+ * a pass from a fail") is retired as its own question: the
832
+ * "why" lines of a real pass/fail pair are what the rubric
833
+ * card compiles from (`cardauthor.js`), and asking the
834
+ * differentiator twice — once in the abstract, once against
835
+ * a real example — is the same double-ask Q3/Q5 already were.
836
+ *
837
+ * SOURCE and DESTINATION ARE MECHANICAL FIELDS, not entries in the numbered
838
+ * question record below: `runInterview`'s `answers` dict is what a close's
839
+ * composer prompt quotes verbatim (Q<n>/A<n>), and quoting a machine-proven fact
840
+ * back at the model as if a person had typed it would be the model reading a
841
+ * fabricated conversation. Their WORDING still lives here (`SOURCE_FIELD`,
842
+ * `DESTINATION_FIELD_REPO`, `DESTINATION_FIELD_PLAIN`, immediately below) so
843
+ * `scripts/run-interview.mjs` prints library text and nothing of its own, exactly
844
+ * as ruling 1 requires for the free-text questions.
845
+ *
846
+ * The free-text trio (soon quartet, soft-green only) IS numbered contiguously
847
+ * from 1 — the number a person sees is the key their answer is filed under, and
848
+ * that is what `questionsFor`/`requiredAnswersFor`/`QUESTION_SETS` still expose,
849
+ * unchanged in shape, to every existing caller.
645
850
  */
646
- 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?";
851
+ export const SOURCE_FIELD: Readonly<{
852
+ id: "source";
853
+ kind: "mechanical";
854
+ label: "Source";
855
+ prompt: "A local folder, subfolder or file, or one URL the machine can reach (no login, no setup).";
856
+ }>;
857
+ /** Destination's wording when Source resolved to a git repo: the answer IS the
858
+ * signed `writeScope` fence (ITEM33-BUILD.md M3 ruling 2). */
859
+ export const DESTINATION_FIELD_REPO: Readonly<{
860
+ id: "destination";
861
+ kind: "mechanical";
862
+ label: "Destination";
863
+ prompt: string;
864
+ }>;
865
+ /** Destination's wording for every other kind of Source (a plain folder, a
866
+ * file, a URL): a directory the run may write into, never a filename
867
+ * (2026-09-12 Destination correction, PRD item 33). */
868
+ export const DESTINATION_FIELD_PLAIN: Readonly<{
869
+ id: "destination";
870
+ kind: "mechanical";
871
+ label: "Destination";
872
+ prompt: string;
652
873
  }>;
653
874
  /**
654
- * THE HITL SET (N4 slice 1) — the green questions BYTE FOR BYTE, plus one.
875
+ * THE FREE-TEXT TRIO EVERY CLASS SHARES: Goal, What success looks like,
876
+ * Guardrails — numbered contiguously from 1. This is what `answers[1..3]` are
877
+ * filed under for every class; soft-green appends a fourth
878
+ * (`SOFTGREEN_QUESTIONS`) and hitl appends a different fourth
879
+ * (`HITL_QUESTIONS`), each its own next number rather than a fixed one, so a
880
+ * future trim of this trio renumbers instead of leaving a gap.
655
881
  *
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).
882
+ * THE TEXT IS THE SIGNED TABLE'S "Holds" COLUMN, VERBATIM (PRD item 33's
883
+ * 2026-09-10 "The intake form (RULED)" ruling, `docs/product/PRD.md`) — not the
884
+ * old worded questions ("What do you want done?", "How do you check today
885
+ * whether it's working?", "...worse than before?"). Motivated by run `mtv8jihy`
886
+ * (F159, `docs/logs/FINDINGS.md`): a rulebook this prompt commit did not touch
887
+ * still had to compile against a card built from a person's actual bar, and the
888
+ * old wording never matched what got signed — this reshape (commit 1ac4d05)
889
+ * trimmed the SET to three slots but left their PROSE unreconciled with the
890
+ * table it was meant to realize. `FIELD_LABELS` (below) carries the matching
891
+ * label for each key, so a caller wanting BOTH shows them together — see
892
+ * `authorPrompt`'s interview block and `scripts/run-interview.mjs`.
670
893
  */
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?";
894
+ export const GREEN_QUESTIONS: Readonly<{
895
+ 1: "what you want to achieve";
896
+ 2: "checks a machine can count";
897
+ 3: "what must not happen or change";
898
+ }>;
899
+ /**
900
+ * The signed table's own label for each of {@link GREEN_QUESTIONS}' keys — "the
901
+ * Field column", where {@link GREEN_QUESTIONS} is "the Holds column". Kept as a
902
+ * SEPARATE map rather than folding the label into the question string itself:
903
+ * every existing caller of `questionsFor`/`requiredAnswersFor`/`QUESTION_SETS`
904
+ * (`src/authorjob.js`, `src/cardauthor.js`, `scripts/run-interview.mjs`) treats
905
+ * a question as a plain string handed verbatim into a prompt or a `Q<n>. `
906
+ * line, and folding a label in would change that shape for every one of them.
907
+ * A caller that wants the label calls {@link labelsFor} exactly the way it
908
+ * calls `questionsFor` — additive, nothing existing breaks.
909
+ */
910
+ export const FIELD_LABELS: Readonly<{
911
+ 1: "Goal";
912
+ 2: "What success looks like";
913
+ 3: "Guardrails";
914
+ }>;
915
+ /** The soft-green quartet's own key for Judge Examples — exported so
916
+ * `cardauthor.js` reads the SAME number this module files the answer under,
917
+ * rather than a second, hand-typed "6"/"7". */
918
+ export const SOFTGREEN_JUDGE_EXAMPLES_KEY: number;
919
+ /** The Judge Examples question — the signed table's own Holds text, verbatim
920
+ * ("one pass, one fail, and why"), not the old worded Q7 ("Give one example
921
+ * you'd pass and one you'd fail, and say why."). Exported so `cardauthor.js`'s
922
+ * prompt and this module's own `SOFTGREEN_QUESTIONS` render the identical
923
+ * string; its label ("Judge examples") lives in `SOFTGREEN_FIELD_LABELS`. */
924
+ export const JUDGE_EXAMPLES_QUESTION: "one pass, one fail, and why";
925
+ /** {@link FIELD_LABELS} plus Judge Examples' own label, for the soft-green set. */
926
+ export const SOFTGREEN_FIELD_LABELS: Readonly<{
927
+ 1: "Goal";
928
+ 2: "What success looks like";
929
+ 3: "Guardrails";
678
930
  }>;
679
931
  /**
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.
932
+ * THE SOFTGREEN SET — the green trio plus Judge Examples. A judged close needs
933
+ * exactly this one more thing the green set cannot supply: a real pass/fail
934
+ * pair in the person's own words, which module 4 (`cardauthor.js`) compiles
935
+ * into the signed rubric card and the frozen calibration set. It rides in
936
+ * `answers` and reaches the composer verbatim through the prompt's interview
937
+ * block, exactly as every other free-text answer does — this module derives
938
+ * nothing from it.
706
939
  */
707
940
  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?";
941
+ 1: "what you want to achieve";
942
+ 2: "checks a machine can count";
943
+ 3: "what must not happen or change";
944
+ }>;
945
+ /**
946
+ * THE HITL SET (N4 slice 1, kept in code only — never offered in the form or
947
+ * anywhere customer-facing, PRD item 33 M3 ruling 8) — the green trio plus one.
948
+ * `human-confirms` requires an `ask`, the question the signer answers at the
949
+ * end of the run, and nothing else in the interview names it; without it the
950
+ * composer would have to invent what a person is deciding. It is the green
951
+ * set's next number, not a fixed one, so a green-side trim renumbers it too.
952
+ */
953
+ export const HITL_QUESTIONS: Readonly<{
954
+ 1: "what you want to achieve";
955
+ 2: "checks a machine can count";
956
+ 3: "what must not happen or change";
715
957
  }>;
716
958
  /**
717
959
  * The three sets, all LIVE as of softgreen module 3. The locked shape stays
@@ -721,11 +963,17 @@ export const SOFTGREEN_QUESTIONS: Readonly<{
721
963
  * distinction, at the interview layer) — and a locked class refuses at ADMISSION,
722
964
  * before its questions run, so the two readers throw rather than hand back
723
965
  * 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}>}
966
+ * `labels` travels ALONGSIDE `questions`, one label per key that the signed
967
+ * table names one for (PRD item 33 M3 piece 3's wording fix) — hitl's own
968
+ * fourth question has no table row (it is never customer-facing, ruling 8) and
969
+ * so carries none; a reader finding no label for a key falls back to showing
970
+ * none, never a fabricated one.
971
+ * @type {Record<string, {locked: boolean, questions: Record<string|number, string>|null, labels: Record<string|number, string>, required: readonly number[]|null}>}
725
972
  */
726
973
  export const QUESTION_SETS: Record<string, {
727
974
  locked: boolean;
728
975
  questions: Record<string | number, string> | null;
976
+ labels: Record<string | number, string>;
729
977
  required: readonly number[] | null;
730
978
  }>;
731
979
  /**
@@ -739,6 +987,69 @@ export const QUESTION_SETS: Record<string, {
739
987
  * @type {Record<string, string|null>}
740
988
  */
741
989
  export const CLASS_STATEMENTS: Record<string, string | null>;
990
+ /**
991
+ * Asked ONLY for a repo source (ruling 2 addendum's D7) — a plain folder or a
992
+ * URL has no "before" a repo's own history gives it to compare against. This
993
+ * is the OLD Q5 wording, verbatim ("What would make you say this came back
994
+ * worse than before?"), which M3 piece 3's reshape folded into Guardrails —
995
+ * it survives here as its OWN field because the confirm turn (ruling 5's
996
+ * 2026-09-13 addendum) includes it as a constraint only when the person
997
+ * actually gave one, and folding it silently into the Guardrails free-text
998
+ * slot would lose that "present or absent" distinction. An empty answer is
999
+ * "nothing beyond what Guardrails already said" and is not required — see
1000
+ * the frozen-wording test below: this phrase appears NOWHERE in
1001
+ * {@link questionsFor}'s own sets, so a caller cannot double-ask it by
1002
+ * reading the wrong table.
1003
+ */
1004
+ export const WORSE_THAN_BEFORE_FIELD: Readonly<{
1005
+ id: "worseThanBefore";
1006
+ kind: "mechanical";
1007
+ label: "Worse than before";
1008
+ prompt: "What would make you say this came back worse than before?";
1009
+ }>;
1010
+ /**
1011
+ * Asked ONLY when `src/detectlang.js`'s `detectLanguage` reports its
1012
+ * `ambiguous` outcome — two supported languages' manifests at the same
1013
+ * (nearest) directory level (M3 ruling 3, point 3). The candidate list is
1014
+ * NOT embedded here: it travels with the caller's own `ambiguous` result
1015
+ * (`langResult.candidates`), so this module never hand-types a second copy
1016
+ * of the supported-language set. An unsupported pick still goes through the
1017
+ * existing `language-unsupported` refusal — this field only offers a choice
1018
+ * among the candidates `detectLanguage` actually found.
1019
+ */
1020
+ export const LANGUAGE_PICK_FIELD: Readonly<{
1021
+ id: "language";
1022
+ kind: "mechanical";
1023
+ label: "Language";
1024
+ prompt: "This repo has more than one supported language's manifest at the same level. Which one is this job about?";
1025
+ }>;
1026
+ /**
1027
+ * THE CONFIRM TURN'S MENU — a structured CHOICE the person picks from after
1028
+ * each round, never free text matched against a pattern (ruling 6's "no
1029
+ * hand-authored check matcher" reasoning applies here too: the person's own
1030
+ * reply is read as one of these four keys, never parsed for intent).
1031
+ * confirm — sign the plan and the drafted goal sentence as shown.
1032
+ * fix — free text describing what to change; feeds round r+1, up
1033
+ * to the 2-round cap (ruling 5); after round 2 a "fix" is
1034
+ * passed to the composer verbatim and shown at signing as an
1035
+ * open question rather than spending a 3rd call (D3).
1036
+ * type-goal — the person's own sentence REPLACES the drafted goal,
1037
+ * verbatim (redacted); everything else in the plan stands.
1038
+ * start-over — abandon this confirm turn; the person reruns the
1039
+ * interview from the beginning (`confirm-restart`).
1040
+ * @type {Record<'confirm'|'fix'|'type-goal'|'start-over', string>}
1041
+ */
1042
+ export const CONFIRM_MENU: Record<"confirm" | "fix" | "type-goal" | "start-over", string>;
1043
+ /**
1044
+ * PLAIN-ENGLISH lines for the guards `classGuards` can compose, keyed by
1045
+ * guard NAME — frozen beside {@link CONFIRM_MENU} because both are constants
1046
+ * the confirm turn shows a person verbatim. One entry per guard that exists
1047
+ * in `MECHANICAL_GUARDS` (`src/authoring.js`); {@link confirmProtections}
1048
+ * throws if a guard `classGuards` hands back has no line here, so a battery
1049
+ * that grows a guard fails loudly instead of showing a blank description.
1050
+ * @type {Record<string, string>}
1051
+ */
1052
+ export const GUARD_DESCRIPTIONS: Record<string, string>;
742
1053
  export const AUTHOR_SYSTEM: string;
743
1054
  /**
744
1055
  * ONE schema per PARAMETER NAME, composed into kinds by the catalogue's own
@@ -967,6 +1278,24 @@ export const PARAM_SCHEMAS: Readonly<{
967
1278
  description: string;
968
1279
  };
969
1280
  }>;
1281
+ export const CONFIRM_TOOL_NAME: "confirm_plan";
1282
+ /** Acknowledgement text the confirm tool's `execute` returns — mirrors
1283
+ * {@link DECLARATION_ACK}'s role: an output channel that records and
1284
+ * acknowledges, takes no action, and ends the call the moment it fires
1285
+ * (`makeLoopGenerate` stops the loop on any tool execute). */
1286
+ export const CONFIRM_ACK: "plan received";
1287
+ /** the confirm channel's own STRUCTURE_INSTRUCTION_TOOL analogue, shown after
1288
+ * a malformed reply so the retry names the ONE required shape rather than
1289
+ * repeating the whole system prompt */
1290
+ export const CONFIRM_STRUCTURE_INSTRUCTION: string;
1291
+ /**
1292
+ * The confirm turn's system prompt (model-facing, registered — a commit
1293
+ * touching this needs Failure/Addresses/Corrects labels, `src/promptregisters.js`).
1294
+ * States ruling 6 ("a genre never adds a check the goal did not ask for") as
1295
+ * an ORDER to the model itself, the same register {@link AUTHOR_SYSTEM} and
1296
+ * {@link CLASS_STATEMENTS} already use, rather than a code matcher over prose.
1297
+ */
1298
+ export const CONFIRM_SYSTEM: string;
970
1299
  export type Red = {
971
1300
  code: string;
972
1301
  path: string;