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.
@@ -431,14 +431,29 @@ export const MAX_STAGES: 12;
431
431
  */
432
432
  export const DENIED_COMMANDS: readonly string[];
433
433
  /**
434
- * THE FROZEN TYPES GENRE TEMPLATE — prereg addendum 3's six policy points,
435
- * VERBATIM. The suite reads the prereg and compares BYTES, so a paraphrase here
436
- * fails the build rather than quietly changing what every future close is
437
- * authored against. It deliberately carries no repo-specific command, path, env
438
- * value or regex: round 2 measured that stating the LAW still leaves the model
439
- * to find the VALUE, and that is the axis the build must keep live.
434
+ * THE TYPES GENRE TEMPLATE — six policy points, VERBATIM against
435
+ * `docs/product/ITEM33-BUILD.md`'s own frozen block (PRD item 33 M3 piece 4,
436
+ * D6 = A ruling, 2026-09-14). This SUPERSEDES the original prereg-frozen
437
+ * version (`docs/logs/2026-08-08-close-authoring-gate2-poc-prereg.md`,
438
+ * addendum 3, the closed record — never edited): that version stated the
439
+ * WORK-stage skeleton (typecheck, typecheck-outside, tests-kept,
440
+ * suite-green) and the checker's STRICT form as UNCONDITIONAL law, which
441
+ * conflicted with ruling 6's "a genre never adds a check the goal did not
442
+ * ask for" — run mtv8jihy's unasked `tsc --strict` stage is the case this
443
+ * cures. Composing those stages/strictness only for a CONFIRMED check
444
+ * (never a code matcher over prose, same reasoning as the no-shell law) is
445
+ * enforced here, in the prompt; the GUARDS (changed-from-seed,
446
+ * no-suppressions) are enforced separately and structurally
447
+ * (`classGuards` + `validateDeclaration`'s `guards-absent` red), so making
448
+ * the WORK stages conditional here changes nothing about their own
449
+ * always-on status. The suite reads the SIGNED block and compares BYTES, so
450
+ * a paraphrase here fails the build rather than quietly changing what every
451
+ * future close is authored against. It deliberately carries no
452
+ * repo-specific command, path, env value or regex: round 2 measured that
453
+ * stating the LAW still leaves the model to find the VALUE, and that is the
454
+ * axis the build must keep live.
440
455
  */
441
- export const TYPES_GENRE_TEMPLATE: "1. The graded instrument is the STRICT form of the language's type checker, regardless of what\n the repo's own scripts run. If the repo's script omits strictness, the close adds it.\n2. Tools are invoked so their binaries actually resolve \u2014 through the project's own package\n runner or language module runner, never a bare binary name.\n3. The stage skeleton, in first-red-wins order: changed-from-seed (guard) \u2192 typecheck (error\n count IN the target scope, baseline 0, lower-is-better) \u2192 typecheck-outside (error count\n OUTSIDE the target scope, baseline measured at seed, a ceiling \u2014 required whenever the job\n scopes to a subset of the tree; omitted only for whole-tree jobs) \u2192 tests-kept (a floor on\n tests that actually EXECUTED, baseline at seed, higher-is-better; a skipped or deselected\n test did not run and must not count) \u2192 suite-green (the suite exits clean AND reports zero\n failing tests \u2014 two assertions) \u2192 no-suppressions (guard).\n4. One population per stage \u2014 two structurally different counts never share a stage.\n5. The checker must judge the PATIENT's own tree: if imports could resolve to an installed or\n editable copy elsewhere, the environment is set so they resolve inside the patient.\n6. A number the tool did not report is unknown, never zero.";
456
+ export const TYPES_GENRE_TEMPLATE: "1. The STRICT form of the language's type checker is used only when a confirmed check \u2014 or,\n absent a confirm turn, the goal and interview answers \u2014 asks for strict typing; otherwise the\n checker runs at whatever strictness the repo's own scripts already use.\n2. Tools are invoked so their binaries actually resolve \u2014 through the project's own package\n runner or language module runner, never a bare binary name.\n3. The WORK stages \u2014 typecheck (error count IN the target scope, baseline 0, lower-is-better),\n typecheck-outside (error count OUTSIDE the target scope, baseline measured at seed, a\n ceiling \u2014 required whenever a typecheck stage is scoped to a subset of the tree), tests-kept\n (a floor on tests that actually EXECUTED, baseline at seed, higher-is-better; a skipped or\n deselected test did not run and must not count), and suite-green (the suite exits clean AND\n reports zero failing tests \u2014 two assertions) \u2014 are composed ONLY for the checks a confirmed\n plan asked for (or, absent a confirm turn, the goal and interview answers). The GUARDS \u2014\n changed-from-seed and no-suppressions \u2014 are composed always, first-red-wins:\n changed-from-seed before every WORK stage, no-suppressions last.\n4. One population per stage \u2014 two structurally different counts never share a stage.\n5. The checker must judge the PATIENT's own tree: if imports could resolve to an installed or\n editable copy elsewhere, the environment is set so they resolve inside the patient.\n6. A number the tool did not report is unknown, never zero.";
442
457
  export const GENRE_LANGUAGES: readonly string[];
443
458
  /**
444
459
  * THE GENRE, as data. The suppression batteries are the hand-written closes'
@@ -457,7 +472,7 @@ export const GENRE_LANGUAGES: readonly string[];
457
472
  */
458
473
  export const TYPES_GENRE: Readonly<{
459
474
  name: "TYPES";
460
- template: "1. The graded instrument is the STRICT form of the language's type checker, regardless of what\n the repo's own scripts run. If the repo's script omits strictness, the close adds it.\n2. Tools are invoked so their binaries actually resolve — through the project's own package\n runner or language module runner, never a bare binary name.\n3. The stage skeleton, in first-red-wins order: changed-from-seed (guard) → typecheck (error\n count IN the target scope, baseline 0, lower-is-better) → typecheck-outside (error count\n OUTSIDE the target scope, baseline measured at seed, a ceiling — required whenever the job\n scopes to a subset of the tree; omitted only for whole-tree jobs) → tests-kept (a floor on\n tests that actually EXECUTED, baseline at seed, higher-is-better; a skipped or deselected\n test did not run and must not count) → suite-green (the suite exits clean AND reports zero\n failing tests — two assertions) → no-suppressions (guard).\n4. One population per stage — two structurally different counts never share a stage.\n5. The checker must judge the PATIENT's own tree: if imports could resolve to an installed or\n editable copy elsewhere, the environment is set so they resolve inside the patient.\n6. A number the tool did not report is unknown, never zero.";
475
+ template: "1. The STRICT form of the language's type checker is used only when a confirmed check — or,\n absent a confirm turn, the goal and interview answers — asks for strict typing; otherwise the\n checker runs at whatever strictness the repo's own scripts already use.\n2. Tools are invoked so their binaries actually resolve — through the project's own package\n runner or language module runner, never a bare binary name.\n3. The WORK stages — typecheck (error count IN the target scope, baseline 0, lower-is-better),\n typecheck-outside (error count OUTSIDE the target scope, baseline measured at seed, a\n ceiling — required whenever a typecheck stage is scoped to a subset of the tree), tests-kept\n (a floor on tests that actually EXECUTED, baseline at seed, higher-is-better; a skipped or\n deselected test did not run and must not count), and suite-green (the suite exits clean AND\n reports zero failing tests — two assertions) — are composed ONLY for the checks a confirmed\n plan asked for (or, absent a confirm turn, the goal and interview answers). The GUARDS —\n changed-from-seed and no-suppressions — are composed always, first-red-wins:\n changed-from-seed before every WORK stage, no-suppressions last.\n4. One population per stage — two structurally different counts never share a stage.\n5. The checker must judge the PATIENT's own tree: if imports could resolve to an installed or\n editable copy elsewhere, the environment is set so they resolve inside the patient.\n6. A number the tool did not report is unknown, never zero.";
461
476
  languages: Readonly<{
462
477
  js: Readonly<{
463
478
  suppressions: readonly (Readonly<{
@@ -85,13 +85,16 @@ export function refuseLockedKind(kind: string): Refusal;
85
85
  * onPhase?: (phase: string, data?: any) => void,
86
86
  * onCall?: (call: {label: string, costUsd: number|null, unpricedRounds: number}) => void,
87
87
  * seedFn?: Function, scoutFn?: Function, listingFn?: Function,
88
- * authorFn?: Function, authorOpts?: object,
89
- * signerFix?: Function|any, proposeFn?: Function, compileOpts?: object}} o
88
+ * authorFn?: Function, authorOpts?: object, writeScope?: string[]|null,
89
+ * signerFix?: Function|any, proposeFn?: Function, compileOpts?: object,
90
+ * ask?: ((step: {kind: string, [k: string]: any}) => Promise<string|null>)|null,
91
+ * confirmGenerate?: Function|null, isRepo?: boolean,
92
+ * langResult?: {kind: string, candidates?: string[], [k: string]: any}|null}} o
90
93
  * @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
91
94
  * closeDecl: any, seedRef: string|null, authoring: any, judged: any, reds: Red[],
92
- * stop: string|null, cost: any, interview: any}>}
95
+ * stop: string|null, cost: any, interview: any, confirmed: any}>}
93
96
  */
94
- export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, }: {
97
+ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, ceilingUsd, writeScope, judgeModel, onPhase, onCall, seedFn, scoutFn, listingFn, authorFn, authorOpts, signerFix, proposeFn, compileOpts, ask, confirmGenerate, isRepo, langResult, }: {
95
98
  answers: Record<string | number, any>;
96
99
  repoPath?: string | null;
97
100
  lang: string;
@@ -115,9 +118,21 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
115
118
  listingFn?: Function;
116
119
  authorFn?: Function;
117
120
  authorOpts?: object;
121
+ writeScope?: string[] | null;
118
122
  signerFix?: Function | any;
119
123
  proposeFn?: Function;
120
124
  compileOpts?: object;
125
+ ask?: ((step: {
126
+ kind: string;
127
+ [k: string]: any;
128
+ }) => Promise<string | null>) | null;
129
+ confirmGenerate?: Function | null;
130
+ isRepo?: boolean;
131
+ langResult?: {
132
+ kind: string;
133
+ candidates?: string[];
134
+ [k: string]: any;
135
+ } | null;
121
136
  }): Promise<{
122
137
  ok: boolean;
123
138
  refusal: Refusal | null;
@@ -130,6 +145,7 @@ export function authorCloseForJob({ answers, repoPath, lang, verdictType, questi
130
145
  stop: string | null;
131
146
  cost: any;
132
147
  interview: any;
148
+ confirmed: any;
133
149
  }>;
134
150
  /**
135
151
  * Fold an authored close into the OPERATOR's own half of the spec.
@@ -259,6 +275,8 @@ export const REFUSAL_CATEGORY: "close-unauthorable";
259
275
  * carry. Named as data so the refusal and the fold can never disagree about
260
276
  * which half of the spec is which. */
261
277
  export const AUTHORED_SPEC_FIELDS: readonly string[];
278
+ export const CONFIRM_AUTHORED_FIELDS: readonly string[];
279
+ export const PLAIN_FOLDER_DEFERRED_FIELDS: readonly string[];
262
280
  export type Red = {
263
281
  code: string;
264
282
  path: string;
@@ -200,8 +200,12 @@ export function proposalTool(box: {
200
200
  execute: (args: any) => Promise<string>;
201
201
  };
202
202
  /**
203
- * The compile prompt. Q6's and Q7's answers travel VERBATIM — they are the whole
204
- * input, and paraphrasing a lay answer is how a rubric stops being the person's.
203
+ * The compile prompt. The Judge Examples answer travels VERBATIM — it is the
204
+ * whole input, and paraphrasing a lay answer is how a rubric stops being the
205
+ * person's. PRD item 33 M3 piece 3: this used to be two answers (Q6+Q7);
206
+ * `SOFTGREEN_JUDGE_EXAMPLES_KEY` is the ONE key both the interview and this
207
+ * compile now read, so a future renumbering of the form moves both readers
208
+ * together rather than drifting.
205
209
  *
206
210
  * The rulebook is rendered from `JUDGE_RULES` itself rather than restated, for
207
211
  * the reason the catalogue block is rendered from the catalogue: the prompt and
@@ -288,7 +292,7 @@ export function foldJudgedArtifacts(closeDecl: any, { card, cases, judgeModel }:
288
292
  judgeModel: string;
289
293
  }): any;
290
294
  /**
291
- * ONE compile call: the person's Q6 and Q7 in, a proposal out.
295
+ * ONE compile call: the person's Judge Examples answer in, a proposal out.
292
296
  *
293
297
  * It buys ONE artifact through ONE channel, and the malformed-emission ladder is
294
298
  * the SHARED one (`askStructured`) rather than a second copy — a second ladder is
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Detect a code job's language from its repository, $0, no provider.
3
+ *
4
+ * @param {string} sourcePath the Source path (a folder, or a file inside one)
5
+ * @returns {{kind: 'resolved', lang: string, dir: string, manifest: string} | {kind: 'language-unsupported', language: string, dir: string, manifest: string, refusal: LangRefusal} | {kind: 'no-code-job'} | {kind: 'ambiguous', candidates: string[], dir: string}}
6
+ */
7
+ export function detectLanguage(sourcePath: string): {
8
+ kind: "resolved";
9
+ lang: string;
10
+ dir: string;
11
+ manifest: string;
12
+ } | {
13
+ kind: "language-unsupported";
14
+ language: string;
15
+ dir: string;
16
+ manifest: string;
17
+ refusal: LangRefusal;
18
+ } | {
19
+ kind: "no-code-job";
20
+ } | {
21
+ kind: "ambiguous";
22
+ candidates: string[];
23
+ dir: string;
24
+ };
25
+ export type Red = {
26
+ code: string;
27
+ path: string;
28
+ detail: string;
29
+ [k: string]: any;
30
+ };
31
+ export type LangRefusal = {
32
+ kind: "request-red";
33
+ verb: string | null;
34
+ path: string;
35
+ detail: string;
36
+ options: string[];
37
+ red: Red;
38
+ };
package/types/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { makeSpine } from "./spine.js";
2
2
  export { runPlan } from "./planrun.js";
3
+ export { workBranchName } from "./workbranch.js";
4
+ export { answerReviewDoor } from "./reviewdoor.js";
3
5
  export { main as cliMain } from "./cli.js";
4
6
  export { ralph, runClose, runStages, CLOSE_FAULTS } from "./ralph.js";
5
7
  export { globToPrefix, scanSecrets } from "./validate.js";
@@ -7,26 +9,24 @@ export { validateJob, jobSpecHash, checkApproval, checkMenu, resolveWorkerModel,
7
9
  export { validatePlan, stageClose, EXIT_TYPES, MAX_EXITS_PER_STEP, MAX_PLAN_STEPS, WRITE_VERBS, closeStagesOf } from "./plan.js";
8
10
  export { snapshotScope, evalExits } from "./exits.js";
9
11
  export { runJob, ACCOUNTED_ROUND_TYPES } from "./run.js";
10
- export { BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate, mintBridge, appendGreen, appendRed, loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists, QUARANTINED_VERDICTS, QUARANTINED_CODE, quarantinesCredit, newestEligibleVersion, reuseEligibility, recordDoor } from "./bridges.js";
12
+ export { BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate, mintBridge, appendGreen, appendRed, loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists, QUARANTINED_VERDICTS, quarantinesCredit, newestEligibleVersion, reuseEligibility, recordDoor } from "./bridges.js";
11
13
  export { renderListing, selectionPrompt } from "./selection.js";
12
- export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow, shapeForkName } from "./reuse.js";
14
+ export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow } from "./reuse.js";
13
15
  export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES, VOUCHED_RATE_SOURCES, rateProvenance, spendProvenance } from "./ledger.js";
14
- export { workBranchName, prepareWorkBranch, WORK_BRANCH_PREFIX, WORK_BRANCH_RE } from "./workbranch.js";
15
- export { createReadShim, wrapReadTool, readShimArm, readShimStrategy, READ_SHIM_CAP, READ_SHIM_STRATEGY, READ_SHIM_DIFF_STRATEGY, READ_SHIM_ARMS } from "./readshim.js";
16
- export { runDeclaredClose, runStage, seedRead, seedAtHead, seedListing, changedSet, makeSeedTrees, LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER, EXIT_GREEN, EXIT_RED, EXIT_STOP, HUMAN_DECISIONS, SEED_EXEMPT_KINDS, JUDGED_FLOOR_KIND, normalizeHumanRuling, resolveHumanRuling, JUDGE_ATTEMPTS, MAX_JUDGED_PATHS, REVIEW_DOOR, DOOR_OPEN_OUTCOMES, REVIEW_DOOR_CLASSES, doorOpens, mechanicalStages } from "./kinds.js";
17
- export { answerReviewDoor, doorRecordOf, doorAgeGate } from "./reviewdoor.js";
18
- export { JUDGE_MODEL, resolveJudge, JUDGE_MAX_TOKENS, JUDGE_RULES, JUDGE_RULE_IDS, LOCATE_AXES, LOCATE_LABEL, validateCard, validateFacts, locatePrompt, runLocate, decide, defaultJudgeLoop, CALIBRATION_SIZE, CASE_VERDICTS, validateCalibrationSet, validateJudgedArtifacts, expectedOf } from "./judged.js";
19
- export { proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts, proposalSchema, proposalTool, cardCasesPrompt, PROPOSAL_TOOL_NAME, PROPOSAL_LABEL, MAX_PROPOSAL_RETRIES, COMPILE_SYSTEM } from "./cardauthor.js";
20
- export { runCalibration, compareExpectation, factsResist, artifactHash, INJECTION_LOCATE_BATTERY, INJECTION_CARD, CALIBRATION_LABEL, CASUALTY_AXES } from "./calibrate.js";
21
- export { validateDeclaration, normalizeDeclaration, KIND_CATALOGUE, CATALOGUE_KINDS, CATALOGUE_LIVE_KINDS, LOCKED_KINDS, TYPES_GENRE, TYPES_GENRE_TEMPLATE, GENRE_LANGUAGES, genreEnv, genreOwnedEnvNames, genreInstruments, DIRECTIONS, BASELINES, MAX_STAGES, VERDICT_CLASSES, LOCKED_CLASSES, LIVE_CLASSES, CLASS_BATTERIES, classGuards, classMenu, closeCeiling, NEVER_OFFERED_KINDS } from "./authoring.js";
22
- export { runAuthorScout, buildSeedListing, seedFileList, classifySurvey, AUTHOR_SCOUT_VERBS, SCOUT_ATTEMPTS, SURVEY_CAUSES, SCOUT_RETRY_CAUSES } from "./authorscout.js";
23
- export { scrubRaw, RAW_PERSIST_MAX, RAW_TRIM_MARKER } from "./text.js";
24
- export { authorClose, authorPrompt, declarationSchema, makeLoopGenerate, MAX_REVISIONS, QUESTION_SETS, GREEN_QUESTIONS, SOFTGREEN_QUESTIONS, CLASS_STATEMENTS, questionsFor, requiredAnswersFor } from "./authorflow.js";
25
- export { validateCloseDecl, runDeclaredStages, declaredStages, isDeclaredClose, guardNames, closeGrade, DECLARED_GENRES, DECLARED_GAP_PREFIX, DECLARED_GAP_KEEP, DECLARED_CLOSE_CLASSES, CLOSE_DECL_FIELDS, HUMAN_PAUSE, HITL_PAUSE, HITL_DECISION_RED, HUMAN_CHECKPOINTS } from "./declaredclose.js";
16
+ export { readShimArm, READ_SHIM_CAP, READ_SHIM_STRATEGY, READ_SHIM_DIFF_STRATEGY } from "./readshim.js";
17
+ export { runDeclaredClose, runStage, seedRead, seedAtHead, LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER, HUMAN_DECISIONS, SEED_EXEMPT_KINDS, normalizeHumanRuling, resolveHumanRuling, JUDGE_ATTEMPTS, MAX_JUDGED_PATHS, DOOR_OPEN_OUTCOMES, REVIEW_DOOR_CLASSES, doorOpens, mechanicalStages } from "./kinds.js";
18
+ export { JUDGE_MODEL, resolveJudge, JUDGE_RULES, JUDGE_RULE_IDS, runLocate, decide, CALIBRATION_SIZE, validateCalibrationSet, expectedOf } from "./judged.js";
19
+ export { proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts } from "./cardauthor.js";
20
+ export { runCalibration, INJECTION_LOCATE_BATTERY, CASUALTY_AXES } from "./calibrate.js";
21
+ export { validateDeclaration, KIND_CATALOGUE, LOCKED_KINDS, genreEnv, genreInstruments, LOCKED_CLASSES, CLASS_BATTERIES, classGuards, closeCeiling, NEVER_OFFERED_KINDS } from "./authoring.js";
22
+ export { runAuthorScout, buildSeedListing, AUTHOR_SCOUT_VERBS, SCOUT_ATTEMPTS, SCOUT_RETRY_CAUSES } from "./authorscout.js";
23
+ export { scrubRaw, RAW_PERSIST_MAX } from "./text.js";
24
+ export { authorClose, MAX_REVISIONS, questionsFor, requiredAnswersFor } from "./authorflow.js";
25
+ export { validateCloseDecl, runDeclaredStages, declaredStages, HUMAN_PAUSE } from "./declaredclose.js";
26
26
  export { runBehaviour, formatBehaviour } from "./behaviour.js";
27
27
  export { replayRun, formatReplay } from "./replay.js";
28
- export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, GENRE, REFUSAL_LIB, REFUSAL_CATEGORY, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
28
+ export { runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind, AUTHORED_SPEC_FIELDS } from "./authorjob.js";
29
29
  export { PROMPT_REGISTERS, isPromptFile } from "./promptregisters.js";
30
30
  export { exportBundle, bundleHash, readBundle, resolveBundleSpec, checkEnvelope, bless, verifyBlessing, appendHistory, checkBundleDeps } from "./bundle.js";
31
31
  export { readCloseScripts, checkCloseAbsolutePaths, absolutePathLiteralsOf, hashCloseScriptBytes, checkCloseByteSignature, checkStageByteSignature, signCloseScripts } from "./close-integrity.js";
32
- export { prepareSource, proveDestination, copyOut, readSourceManifest, frontDoorFromManifest, datedDestination, pickDelivery } from "./source.js";
32
+ export { prepareSource, proveDestination, copyOut, readSourceManifest, frontDoorFromManifest, datedDestination, pickDelivery, missingDependencies } from "./source.js";
package/types/job.d.ts CHANGED
@@ -4,6 +4,27 @@
4
4
  * against the worker-surface package, a locked VERDICT type is demand against
5
5
  * bareloop's OWN menu. Inferring it downstream is how a bareloop refusal gets
6
6
  * filed as an upstream bug (the BA-2 misattribution class). */
7
+ /**
8
+ * The `baseUrl` SHAPE rule, standalone (PRD item 28 ruling (d); PRD item 33
9
+ * close-out, "authoring provider selectable" — a bad `--base-url` used to
10
+ * refuse only at the end of the interview, through `validateJob`, after every
11
+ * question had already been asked). ONE spelling of the rule: `validateJob`
12
+ * below calls this, and so does `scripts/run-interview.mjs`, at flag-parse
13
+ * time, before it asks anything.
14
+ *
15
+ * No network access, so this checks SHAPE only: `https://` for a real
16
+ * endpoint, or `http://` restricted to a loopback host (a local
17
+ * OpenAI-compatible server, the only legitimate reason to skip TLS) — and
18
+ * never a credential embedded IN the URL itself. A URL travels into logs and
19
+ * spine records far more casually than a header ever would, and secrets never
20
+ * enter the tree/spine (hard line #3) — a baseUrl carrying `user:pass@host`
21
+ * is exactly that leak, just spelled differently. The returned message never
22
+ * echoes the value it judged — it is printed as-is by both callers, and a
23
+ * value that failed for carrying credentials must not repeat them back.
24
+ * @param {unknown} value
25
+ * @returns {string|null} a red `detail`, or `null` when the value is admitted
26
+ */
27
+ export function validateBaseUrl(value: unknown): string | null;
7
28
  /**
8
29
  * Validate an operator-owned job spec (`schema: "job-v1"`). Never throws on
9
30
  * JSON text or plain parsed data — the ingest contract; a live object with a
@@ -209,8 +230,9 @@ export const CADENCE_UNITS: readonly string[];
209
230
  * rows on the cost axis, and budgets sized for one do not transfer. */
210
231
  /** PRD item 28 (2026-09-06/09-09): `openai-api` — an OpenAI-shaped provider,
211
232
  * constructed via `src/providers.js`'s factory. hamr's ruling (PRD 30.7):
212
- * `deepseek-chat` is THE secondary provider (one, not a menu of half-tested
213
- * models); the factory's tier table is where that lives, not this menu.
233
+ * `deepseek-flash` is THE secondary provider (one, not a menu of half-tested
234
+ * models; F171, item 34 L16: swapped from the retired `deepseek-chat`); the
235
+ * factory's tier table is where that lives, not this menu.
214
236
  *
215
237
  * PRD item 31.3 (hamr, 2026-09-09: *"anthropic, openai, gemini drop ollama for
216
238
  * now"*): `gemini-api` joins. That is bareloop matching bare-agent's own
@@ -73,7 +73,6 @@
73
73
  * No gap → the empty string, so the brief renders byte-identically to the pre-F86
74
74
  * one. A labelled empty section would be an invitation to explain an absence the
75
75
  * run never observed (a stall never judged its exits at all).
76
- *
77
76
  * @param {string | null | undefined} gap the step's last exit gap text
78
77
  * @returns {string} the labelled block, or '' when there is nothing to show
79
78
  */
@@ -473,6 +472,47 @@ export function runPlan(job: any, { workdir, provider, nativeProvider, providerF
473
472
  } | null | undefined;
474
473
  resumable?: boolean | undefined;
475
474
  }): Promise<string>;
475
+ /**
476
+ * The runtime belt for F178 (`.git`) and F177's follow-up (`node_modules`) —
477
+ * hamr's option B ruling (2026-09-14): block WRITING to either, never reading.
478
+ * bareguard's `fs.deny` cannot be used for this: it blocks ALL fs actions,
479
+ * reads included (node_modules/bareguard/src/primitives/fs.js:43 "paths/
480
+ * prefixes denied for all fs actions", checked at :62-66 before the read/write
481
+ * split) — putting `.git` there (the first version of this fix) silently also
482
+ * blocked READING it, which was never ruled, and the worker's persona never
483
+ * names `.git`/`node_modules` as a denied book the way it does the gate audit
484
+ * / `.smoke` / `.litectx` (`tests/tools.test.js`'s persona/fence-drift guard
485
+ * caught exactly this in the full gate). `fs.deny` is exact prefix-containment
486
+ * only anyway — no glob, no path-segment match — confirmed by reading
487
+ * `within()` (node_modules/bareguard/src/primitives/fs.js:26-31): `p === b ||
488
+ * p.startsWith(b + '/')` against each configured entry, nothing else; it
489
+ * could not express `node_modules`-at-any-depth even if reads were not a
490
+ * problem (`node_modules/bareguard/src/glob.js`'s `matchAny`/`globToRegex`
491
+ * exist in the same package but `fs.js` never imports them).
492
+ *
493
+ * `tools.denyArgPatterns` (`node_modules/bareguard/src/primitives/tools.js`,
494
+ * step 3 of `Gate#_stepEval` — the SAME step `fsCheck` runs at, checked right
495
+ * after it in the `??` chain, `node_modules/bareguard/src/gate.js`) is the
496
+ * supported hook used instead: a RegExp tested against
497
+ * `JSON.stringify(action)`, keyed by `action.type` so it is scoped to
498
+ * `write`/`edit` ONLY — a `read` action never reaches this pattern at all.
499
+ * Because `fsCheck` runs first in the same `??` chain, this pattern is
500
+ * reached only when the path already passed `writeScope`/`deny` — exactly the
501
+ * residual case (an in-fence write/edit whose path still carries a `.git` or
502
+ * `node_modules` segment; `.git` itself is never IN a signed fence in the
503
+ * first place, `scopeContained` in src/validate.js refuses that at
504
+ * declaration time — this is the belt for a validator regression). Matched
505
+ * whole-segment (a look-alike like `.github`/`.gitignore`/`my.git`/
506
+ * `node_modules_util` stays admitted). Safe to match the WHOLE serialized
507
+ * action (not just the `path` field in isolation) because bareloop's own
508
+ * action shape (`toolAction`, src/tools.js) never carries a write's byte
509
+ * CONTENT in the action object — `args.bytes` is a length, not the text — so
510
+ * there is no written-file-content collision risk the way there would be for
511
+ * bareguard's own `content.denyPatterns` primitive (which explicitly strips
512
+ * payload fields for exactly that reason).
513
+ * @type {RegExp}
514
+ */
515
+ export const FORBIDDEN_WRITE_SEGMENT_PATTERN: RegExp;
476
516
  /**
477
517
  * The recorded bound-reason's ceiling in the WORKER-facing note. The reason is a
478
518
  * provider string (`denied:<tool>` today, ~20 bytes), not a gap, so this is a
@@ -8,6 +8,34 @@
8
8
  * @returns {ProviderTableEntry}
9
9
  */
10
10
  export function resolveProvider(providerName: string): ProviderTableEntry;
11
+ /**
12
+ * Report whether a resolved API key VALUE (not its presence — every call
13
+ * site already presence-checks separately) carries a shape an HTTP header
14
+ * value cannot: F181 (`docs/logs/FINDINGS.md`) — a two-line secret-store
15
+ * entry put the key plus a trailing metadata line into `OPENAI_API_KEY`
16
+ * (`"<key>\nmeta"`), the presence-only check at every door passed, and Node
17
+ * crashed `TypeError [ERR_INVALID_CHAR]` inside its own header-encode at the
18
+ * FIRST paid call — after the run had already announced itself as spending,
19
+ * inside the paid span. curl tolerated the same value (HTTP 200 twice), so a
20
+ * curl probe is a blind instrument for this defect class.
21
+ *
22
+ * NEVER TRIMS OR REPAIRS — this reports a reason, it does not fix the value
23
+ * (hamr's retry-never-repair rule, in spirit: refuse loud, don't guess at
24
+ * what the operator's secret store meant). Every call site that gets a
25
+ * non-null reason back refuses at $0, in the SAME place and the SAME way it
26
+ * already refuses a missing key — this function only adds a second shape of
27
+ * refusal to a door that already had one.
28
+ *
29
+ * The checked shape is deliberately wider than what Node's own header
30
+ * encoder rejects (Node tolerates a bare tab in a header value): CR, LF, TAB,
31
+ * every other C0/DEL control character, and leading/trailing whitespace all
32
+ * refuse, because every one of them is evidence the resolved value is not a
33
+ * clean single-line secret — the same class of "reads as one plausible thing,
34
+ * is actually stale/wrong/multi-line" this finding named.
35
+ * @param {string} value the resolved key value — never logged, never echoed by any caller
36
+ * @returns {string|null} a plain-English reason class, or `null` when the value is clean
37
+ */
38
+ export function apiKeyProblem(value: string): string | null;
11
39
  /**
12
40
  * Construct ONE provider instance for `providerName`/`model`, applying
13
41
  * `exposeErrorBody: true` (F153 — every constructed provider, unconditionally)
@@ -108,10 +136,17 @@ export const ANTHROPIC_TIER_MODELS: Readonly<{
108
136
  * of a single-model provider, not a second model masquerading as `haiku`.
109
137
  * If DeepSeek (or a future openai-api entrant) ever ships a genuinely
110
138
  * cheaper tier model, this is where it would earn its own `haiku` id.
139
+ *
140
+ * MODEL SWAP (F171, item 34 L16, hamr 2026-09-13): `deepseek-chat` is no
141
+ * longer served — a live `/models` call returned only `deepseek-flash` and
142
+ * `deepseek-v4-pro`. Switched to `deepseek-flash` (DeepSeek V4.1). F149's
143
+ * `legacyMaxTokens` measurement (asked 64 output tokens, got 665/608) was
144
+ * taken on `deepseek-chat` and is UNVERIFIED on `deepseek-flash` — kept on
145
+ * below as the fail-safe (over-capping direction is safe; see F171).
111
146
  */
112
147
  export const OPENAI_TIER_MODELS: Readonly<{
113
- sonnet: "deepseek-chat";
114
- haiku: "deepseek-chat";
148
+ sonnet: "deepseek-flash";
149
+ haiku: "deepseek-flash";
115
150
  }>;
116
151
  /**
117
152
  * Gemini (PRD item 31.3, hamr 2026-09-09: "anthropic, openai, gemini drop
package/types/source.d.ts CHANGED
@@ -1,3 +1,45 @@
1
+ /**
2
+ * Walk from `startDir` upward — inclusive — to the NEAREST ancestor whose own
3
+ * directory carries a `.git` entry, whether that entry is a real repo
4
+ * directory or a linked-worktree/submodule FILE (the caller decides what a
5
+ * file means for it). `null` when no ancestor up to the filesystem root has
6
+ * one. ONE helper for "find the repo boundary walking up", shared by
7
+ * {@link looksLikeRepoSource} and `prepareSource`'s own authoritative routing
8
+ * below (PRD item 33 M3 ruling 2 addendum, 2026-09-13: a Source that is a
9
+ * SUBFOLDER inside a git repo is a repo job, not a plain-folder one — the
10
+ * boundary is the nearest ancestor's `.git`, never only a `.git` sitting
11
+ * directly inside Source itself) and reused by `src/detectlang.js`'s own
12
+ * upward walk (`walkChain`), which only needs the boundary directory, not
13
+ * which kind of `.git` it is — never three copies of this rule.
14
+ * @param {string} startDir an absolute directory to start the walk FROM
15
+ * @returns {{dir: string, isFile: boolean}|null}
16
+ */
17
+ export function nearestGitAncestor(startDir: string): {
18
+ dir: string;
19
+ isFile: boolean;
20
+ } | null;
21
+ /**
22
+ * A CHEAP peek (a stat, never a read) at whether `source` is going to turn
23
+ * out to be a REPO source — the ONE rule for that question, shared by
24
+ * `prepareSource` (which routes the destination check below before the real
25
+ * walk further down decides authoritatively) and `scripts/run-interview.mjs`
26
+ * (PRD item 33 M3 ruling 2), which has to know BEFORE Source is even frozen
27
+ * whether Destination fills the write fence or a delivery directory. Never a
28
+ * second, hand-typed copy of this test: a URL is never a repo; a local path
29
+ * is a repo source only when it is a DIRECTORY, and the NEAREST ancestor
30
+ * (Source itself, or an ancestor above it — ruling 2's 2026-09-13 addendum)
31
+ * carries a `.git` DIRECTORY. A nearest `.git` that is a FILE (a linked
32
+ * worktree or a submodule) is NOT repo-like here — the real walk below
33
+ * refuses `source-is-linked-worktree` for that case, and this peek must
34
+ * agree closely enough to route the destination question correctly rather
35
+ * than offer the write-fence wording for a source that is about to be
36
+ * refused. An unreadable path is reported properly by the real walk; this
37
+ * peek only has to agree with that walk closely enough to route correctly,
38
+ * never to replace it.
39
+ * @param {string} source
40
+ * @returns {boolean}
41
+ */
42
+ export function looksLikeRepoSource(source: string): boolean;
1
43
  /**
2
44
  * Build the frozen source tree — `<into>/tree/input/…` (the copy), `<into>/tree/output/`
3
45
  * (empty, where the run writes), `<into>/source.json` (the manifest, OUTSIDE
@@ -46,6 +88,51 @@ export function prepareSource({ source, into, destination, fetchTimeoutMs }: {
46
88
  manifestPath: string;
47
89
  manifest: object;
48
90
  } | SourceRefusal>;
91
+ /**
92
+ * THE INSTALL-GAP DETECTOR (PRD item 33 close-out, `docs/product/PRD.md:450-452`,
93
+ * hamr's ruling 2026-09-14, option A): `prepareSource` copies ONLY git-tracked
94
+ * files (hamr's ruling, `docs/product/ITEM33-BUILD.md:225-239` — unchanged by
95
+ * this function), so a JS/TS repo's prepared copy never carries `node_modules`,
96
+ * and every close stage needing a tool (`tsc`, a test runner) instrument-stops
97
+ * with nothing telling the person why. bareloop NEVER runs an install itself —
98
+ * it only ever touches gated primitives, and a shell-out to `npm`/etc from
99
+ * inside this module would be exactly the verb hamr's law reserves for the
100
+ * worker's own granted tools, never the arbiter-side machinery that decides
101
+ * whether a run even starts. This is a pure, $0 DETECTOR: it names the gap and
102
+ * the exact command a person runs THEMSELVES in the copy, then returns.
103
+ *
104
+ * JS/TS ONLY for now — a repo in another detected language returns `null`
105
+ * here unconditionally (no `package.json` to find), which is the correct
106
+ * "nothing to report" reading, not a gap in this function; per-language
107
+ * dependency-gap data for other languages is M3b's job
108
+ * (`docs/product/ITEM33-BUILD.md`, "M3b"), not this one's.
109
+ *
110
+ * SUBFOLDER-AS-REPO (ruling 2 addendum, commit 2a020d8): a subfolder job
111
+ * freezes the WHOLE repo, so `package.json` may sit at the repo root, at the
112
+ * subfolder itself, or anywhere between — the SAME nearest-manifest-walking-up
113
+ * rule `detectLanguage` (`src/detectlang.js`) applies, done independently here
114
+ * (not by importing `detectLanguage`) to avoid a `source.js` ⇄ `detectlang.js`
115
+ * import cycle, since `detectlang.js` already imports `nearestGitAncestor`
116
+ * from this module.
117
+ *
118
+ * @param {string} treeDir the prepared tree's root (`prepareSource`'s
119
+ * returned `tree`, e.g. `<into>/tree` — a REPO source sits directly at this
120
+ * root; every other kind sits under `input/` inside it and has no repo
121
+ * dependency story yet, so callers only need this for a `kind: 'repo'`
122
+ * manifest)
123
+ * @param {string} [sourceSubdir] the manifest's own `sourceSubdir` ('' or
124
+ * undefined when Source IS the repo root)
125
+ * @returns {{manager: string, command: string, reason: string}|null} `null`
126
+ * when there is nothing to report: no JS/TS manifest found walking up from
127
+ * `sourceSubdir` to `treeDir`, the nearest one found has no dependencies,
128
+ * `node_modules` is already there, or the manifest cannot even be read as
129
+ * JSON (an invalid `package.json` is "cannot tell", never a false alarm).
130
+ */
131
+ export function missingDependencies(treeDir: string, sourceSubdir?: string): {
132
+ manager: string;
133
+ command: string;
134
+ reason: string;
135
+ } | null;
49
136
  /**
50
137
  * The delivered name (M2b fix 4, kept under D3): a destination FILE of
51
138
  * `/home/me/profile.md` lands as `/home/me/profile-2026-09-12.md`, and a
package/types/text.d.ts CHANGED
@@ -100,6 +100,41 @@ export function capStop({ ceilingUsd, knownUsd, spendComplete }: {
100
100
  knownUsd: number;
101
101
  spendComplete: boolean;
102
102
  }): "cap-halt" | "pricing-red" | null;
103
+ /**
104
+ * THE ONE SPELLING of "was this throw a call casualty, or something else" —
105
+ * F179/F180's seam. `authorscout.js`'s `settled` narrows a rejected
106
+ * `loop.run()` to this one class: swallowing every throw would launder a
107
+ * budget `HaltError`, a programming bug, or any other named terminal into
108
+ * "the call failed" — the exact class of dishonesty this repo refuses (an
109
+ * unknown reported as a known).
110
+ *
111
+ * bare-agent 0.42.0's `OpenAIProvider.generate` used to throw a raw
112
+ * `SyntaxError` from an unguarded `JSON.parse(tc.function.arguments)` after
113
+ * a billed HTTP round (F179/BA-27) — this predicate used to admit that class
114
+ * too. bare-agent 0.43.0 fixed it upstream (`parseToolCalls`, shared by
115
+ * OpenAIProvider and Ollama): a malformed tool-call arguments string now
116
+ * returns a priced round with `toolCalls: []` plus its own `malformedToolCall`
117
+ * marker instead of throwing. Verified against 0.43.0's installed source that
118
+ * no provider bareloop constructs (`AnthropicProvider`, `OpenAIProvider`,
119
+ * `GeminiProvider` — `src/providers.js`) can still throw a JSON `SyntaxError`
120
+ * out of `generate()` after a billed round: Anthropic and Gemini read tool-call
121
+ * arguments as already-parsed objects off their own response shape (nothing to
122
+ * `JSON.parse`), and every provider's raw-HTTP-body parse is already
123
+ * try/catch-wrapped inside `_request`, converted to a plain `Error` before it
124
+ * can reach here. bareloop never streams a provider response. So the
125
+ * SyntaxError branch this predicate used to carry is dead code and was
126
+ * removed in the same change that bumped to bare-agent 0.43.0 — re-add it (or
127
+ * a narrower successor) only if a real provider is found to reintroduce the
128
+ * class.
129
+ *
130
+ * A `HaltError` is deliberately excluded — it is bare-agent's own governance
131
+ * exit (a budget/turn cap), never a transport casualty, and this predicate
132
+ * must never launder one into a mere "the call failed".
133
+ * @param {any} err
134
+ * @returns {string|null} a short reason when `err` is an admitted casualty class,
135
+ * `null` for everything else (including `HaltError`) — the caller re-throws on `null`.
136
+ */
137
+ export function callCasualty(err: any): string | null;
103
138
  /**
104
139
  * ONE raw model emission, ready to persist.
105
140
  *
@@ -13,6 +13,8 @@ export function globToPrefix(scope: string): string;
13
13
  * resolve to a PROPER subdirectory of the run directory. Absolute paths and
14
14
  * ".." segments escape it; "." / "./**" cover the whole run directory — where
15
15
  * the close suite lives. Windows spellings ("..\", "C:\") count as escapes.
16
+ * A `.git` or `node_modules` segment anywhere in the prefix is refused too
17
+ * (F178/F177, see {@link FORBIDDEN_SCOPE_SEGMENTS}).
16
18
  * Exported for the job validator: the operator's outer fence obeys the SAME
17
19
  * law through the same code — two containment transforms would be the F9
18
20
  * red-class one level up.
@@ -205,6 +207,26 @@ export function hasNestedQuantifier(src: string): boolean;
205
207
  * @param {(code: string, path: string, detail?: string) => void} red
206
208
  */
207
209
  export function sweepNestedQuantifiers(root: any, red: (code: string, path: string, detail?: string) => void): void;
210
+ /**
211
+ * Path segments a fence/scope may never contain, checked WHOLE-SEGMENT
212
+ * (`prefix.split('/')`), never substring — a look-alike (`.github`,
213
+ * `.gitignore`, `my.git`, `node_modules_util`) is a DIFFERENT name and stays
214
+ * admitted. hamr's ruling (option B, 2026-09-14, block both): F178 — a
215
+ * signed fence naming `.git` could let a worker write git's own files (refs,
216
+ * the seed, `info/exclude`) and hide those writes from `changedSet`. F177's
217
+ * follow-up — a signed fence naming `node_modules` directly is the same class
218
+ * of gap (an installed/worker-written file inside it is invisible to
219
+ * `changedSet` the same way, and installs are the person's job now, F177).
220
+ * This catches only a fence that NAMES one of these directly; a fence like
221
+ * `packages/api/**` that later reaches a NESTED `node_modules` beneath it is a
222
+ * distinct, only partially closeable gap — see the runtime belt in
223
+ * planrun.js for `node_modules` (`.git` has no such gap: there is exactly one
224
+ * `.git` per repo, always at the workdir root, so its own runtime belt is a
225
+ * single `fs.deny` entry). Both are inexpressible at declaration time, the
226
+ * same law #1 already applies to `..`.
227
+ * @type {readonly string[]}
228
+ */
229
+ export const FORBIDDEN_SCOPE_SEGMENTS: readonly string[];
208
230
  /**
209
231
  * PRD item 27/M2 close-bytes signature, orchestrator-flagged gap
210
232
  * (2026-09-06): a close stage's `cmd` can name an addressable script TWO