bareloop 0.2.0 → 0.3.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.
package/types/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export { makeSpine } from "./spine.js";
2
- export { ralph, runClose } from "./ralph.js";
3
- export { validateConfig, diffPaths, globToPrefix, LOOP_SHAPES, SLOTS, VERBS } from "./validate.js";
4
- export { validateJob, jobSpecHash, checkApproval, CLOSE_TYPES, CLASSES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS } from "./job.js";
2
+ export { runJob } from "./run.js";
3
+ export { ralph, runClose, CLOSE_FAULTS } from "./ralph.js";
4
+ export { validateConfig, diffPaths, globToPrefix, scanSecrets, LOOP_SHAPES, SLOTS, VERBS } from "./validate.js";
5
+ export { validateJob, jobSpecHash, checkApproval, CLOSE_TYPES, CLASSES, CLASS_BY_CLOSE, GOLD_COMPARE, CADENCE_UNITS, PROVIDERS, CONDITION_KEYS, STEP_MODES, TOOL_MENU, LOCKED_TOOLS } from "./job.js";
5
6
  export { interpret, STALL_REDS } from "./interpret.js";
6
7
  export { extractRules, MAX_RULES, MAX_RULE_CHARS } from "./extract.js";
8
+ export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASSES } from "./ledger.js";
@@ -4,13 +4,26 @@
4
4
  * @param {object|string} configRaw schema v1 config (object or raw JSON text)
5
5
  * @param {object} opts
6
6
  * @param {string} opts.task implement instruction shown to the worker
7
- * @param {string} opts.target absolute path the artifact is written to
7
+ * @param {string} [opts.target] absolute path the artifact is written to —
8
+ * required in text mode; unused in tool mode (the worker writes through
9
+ * the gated tools, wherever the fence allows)
8
10
  * @param {string[]} opts.close argv whose exit code is truth (shell-owned)
11
+ * @param {number} [opts.closeExpect] the exit code the SIGNED spec calls success
12
+ * @param {{pattern: string, min: number}} [opts.closeJudged] the signed spec's
13
+ * judgment-rendered signal — proof the close actually judged something before
14
+ * its exit code is believed in EITHER direction (PRD v1.11; the drafted config
15
+ * cannot express it, and must not: it is the arbiter's own honesty check)
16
+ * @param {string} [opts.closeGapKeep] the signed spec's kept-failures pattern (F28):
17
+ * close-output lines matching it survive the gap bound, so a large suite's
18
+ * `not ok` names reach the worker instead of being elided into "N fail" — also
19
+ * arbiter territory, inexpressible in the drafted config
9
20
  * @param {string} opts.workdir run directory (litectx root, gate audit, scope base)
10
21
  * @param {number} opts.capRuns shell iteration budget; the config may tighten via loop.maxIterations, never exceed
11
22
  * @param {(type: string, data?: object) => object} opts.emit spine emitter
12
23
  * @param {object} opts.provider a bareagent provider — SHELL-owned binding (adaptlearn F8: an unsealed binding is a gate bypass)
13
- * @param {number} [opts.shellCapUsd=2] the shell's USD cap; config budgetUsd is clamped by validation
24
+ * @param {number} [opts.shellCapUsd=2] the shell's USD cap; a config budgetUsd above it REDS at validation (bounds — no silent clamping)
25
+ * @param {number} [opts.closeTimeoutMs] close wall-clock cap, threaded to the shell
26
+ * (shell/operator territory — the workflow config cannot express it)
14
27
  * @param {string[]} [opts.jobWriteScope] the job spec's outer write fence (operator law,
15
28
  * job-v1) — enforced HERE, the one choke point where a config becomes a Gate:
16
29
  * every workflow scope must fit inside it (scope-escape config-red otherwise),
@@ -22,17 +35,33 @@
22
35
  * (arbiter-touch / cap-touch revision-reds otherwise; the run continues on the old
23
36
  * config). Revisor spend rides the run's own gate handlers — same budget axis as
24
37
  * the worker.
38
+ * @param {string} [opts.closeState] the close's CURRENT output on the tree as it stands
39
+ * (the shell's pre-token close check, F13) — shown to the first attempt only, and
40
+ * never framed as an attempt: the worker cannot run the close itself (`run` is a
41
+ * locked verb), so without this it is asked to fix a failure it cannot see
42
+ * @param {'text'|'tools'} [opts.mode] middle mode (2b): 'text' (default) writes the ONE
43
+ * target from the response artifact; 'tools' gives the worker Gate-governed file
44
+ * tools — SPEC-side territory (the step declares it; the config cannot express it)
45
+ * @param {string[]} [opts.tools] the spec's tool grant (subset of read|grep|write,
46
+ * job-v1 validated); defaults to the full menu in tool mode
25
47
  * @returns {Promise<'green'|'escalated'|'config-red'>}
26
48
  */
27
- export function interpret(configRaw: object | string, { task, target, close, workdir, capRuns, emit, provider, shellCapUsd, jobWriteScope, revisor }: {
49
+ export function interpret(configRaw: object | string, { task, target, close, workdir, capRuns, emit, provider, shellCapUsd, jobWriteScope, revisor, closeTimeoutMs, mode, tools, closeState, closeExpect, closeJudged, closeGapKeep }: {
28
50
  task: string;
29
- target: string;
51
+ target?: string | undefined;
30
52
  close: string[];
53
+ closeExpect?: number | undefined;
54
+ closeJudged?: {
55
+ pattern: string;
56
+ min: number;
57
+ } | undefined;
58
+ closeGapKeep?: string | undefined;
31
59
  workdir: string;
32
60
  capRuns: number;
33
61
  emit: (type: string, data?: object) => object;
34
62
  provider: object;
35
63
  shellCapUsd?: number | undefined;
64
+ closeTimeoutMs?: number | undefined;
36
65
  jobWriteScope?: string[] | undefined;
37
66
  revisor?: ((o: {
38
67
  config: object;
@@ -44,14 +73,18 @@ export function interpret(configRaw: object | string, { task, target, close, wor
44
73
  parseError?: string | null;
45
74
  costUsd?: number;
46
75
  }>) | undefined;
76
+ closeState?: string | undefined;
77
+ mode?: "text" | "tools" | undefined;
78
+ tools?: string[] | undefined;
47
79
  }): Promise<"green" | "escalated" | "config-red">;
48
- /** @typedef {Error & {category?: string}} CategorizedError the failure map's carrier: ralph relays by `category` */
80
+ /** @typedef {Error & {category?: string, lib?: string}} CategorizedError the failure map's carrier: ralph relays by `category`, and by `lib` when the thrower knows which library owns the failure (the ledger prefers it over prose) */
49
81
  export const STALL_REDS: 2;
50
82
  /**
51
- * the failure map's carrier: ralph relays by `category`
83
+ * the failure map's carrier: ralph relays by `category`, and by `lib` when the thrower knows which library owns the failure (the ledger prefers it over prose)
52
84
  */
53
85
  export type CategorizedError = Error & {
54
86
  category?: string;
87
+ lib?: string;
55
88
  };
56
89
  /**
57
90
  * litectx recall hit — body present only with `{body: true}`
package/types/job.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- /** @typedef {{code: string, path: string, detail?: string}} Red */
1
+ /** @typedef {{code: string, path: string, detail?: string, verb?: string}} Red — `verb` rides request-reds as structured data (the ledger keys on it, never on prose) */
2
2
  /**
3
3
  * Validate an operator-owned job spec (`schema: "job-v1"`). Never throws on
4
4
  * JSON text or plain parsed data — the ingest contract; a live object with a
@@ -44,14 +44,45 @@ export const CLASS_BY_CLOSE: Readonly<{
44
44
  hitl: readonly string[];
45
45
  }>;
46
46
  export const GOLD_COMPARE: readonly string[];
47
+ /** step middle modes (2b): text = single-target artifact; tools = Gate-governed file tools */
48
+ export const STEP_MODES: readonly string[];
49
+ /** the tool grant menu (2b interview #1): read/grep/write, plus the two litectx
50
+ * RETRIEVAL verbs (F19) — `run` stays locked-but-listed; a spec requesting it
51
+ * reds, and that red IS the request-red evidence admission waits on (curation
52
+ * doctrine, PRD F2).
53
+ *
54
+ * `recall`/`get` are READ-ONLY BY CONSTRUCTION, not by promise: `recall` returns
55
+ * pointers (path/symbol/line-range, no bodies) and `get` trades ONE pointer for
56
+ * ONE chunk — it refuses any range that is not a chunk boundary, so it cannot be
57
+ * widened into a whole-file read, and it is content-hash gated (a drifted file
58
+ * throws rather than serving a stale body). Admitting them does NOT weaken the
59
+ * `run` lock: the worker still cannot execute anything, so it still cannot run
60
+ * its own close (the arbiter). F19: `shell_read` starts at byte ZERO and cannot
61
+ * seek, so a pointer was INERT — the worker paged 1.37 MB of source through
62
+ * context to reach 8 lines. The ranged read is the pager.
63
+ *
64
+ * `edit` (BA-13, bare-agent 0.29.0) is the anchored exact-once replace — a
65
+ * WRITE-class verb judged by the SAME writeScope fence as `write` (bareguard
66
+ * action type 'edit'). Admitted because whole-file rewrite is measurably
67
+ * unreliable at size (F31: 4 of 5 big-file whole-writes broke the tree) and
68
+ * taxes output tokens ∝ file size. It admits no execution either. */
69
+ export const TOOL_MENU: readonly string[];
70
+ /** locked-but-listed tools: real capabilities deliberately outside the grant
71
+ * menu. Requesting one is a DISTINCT `request-red` (module 4) — the ledger
72
+ * counts admission demand, and a generic invalid-value would bury it as a typo. */
73
+ export const LOCKED_TOOLS: readonly string[];
47
74
  export const CADENCE_UNITS: readonly string[];
48
75
  export const PROVIDERS: readonly string[];
49
76
  /** V3 environment label: declared keys only — every field is a lineage-key
50
77
  * candidate at N3. `provider` is part of the key by definition (top-level,
51
78
  * not duplicated here). */
52
79
  export const CONDITION_KEYS: readonly string[];
80
+ /**
81
+ * — `verb` rides request-reds as structured data (the ledger keys on it, never on prose)
82
+ */
53
83
  export type Red = {
54
84
  code: string;
55
85
  path: string;
56
86
  detail?: string;
87
+ verb?: string;
57
88
  };
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Classify one spine's events into incident occurrences (the design table in
3
+ * bareloop vocabulary). Deliberate exclusions — bare cap-halt (a budget story),
4
+ * close-verdict reds and artifact-red (worker stories, the §5b line), gate-red
5
+ * (governance working as intended), pr-red (operator environment, not a suite
6
+ * lib), smoke-red escalations (already counted via primitive-smoke), hitl-close
7
+ * and close-unsupported (by design) — classify to nothing.
8
+ * @param {any[]} events one spine's parsed events, in seq order
9
+ * @param {{spine?: string}} [opts] spine id stamped into samples
10
+ * @returns {Occurrence[]}
11
+ */
12
+ export function classifyIncidents(events: any[], { spine }?: {
13
+ spine?: string;
14
+ }): Occurrence[];
15
+ /**
16
+ * Fold ledger rows to current state — latest cumulative count per key, latest
17
+ * human-appended status riding on top (default 'open'). A status row for a key
18
+ * with no incident row still folds (occurrences 0): a human note must never
19
+ * silently vanish.
20
+ * @param {any[]} rows parsed ledger rows, in seq order
21
+ * @returns {Record<string, FoldEntry>}
22
+ */
23
+ export function foldLedger(rows: any[]): Record<string, FoldEntry>;
24
+ /**
25
+ * Compute the append-only delta rows: group occurrences by key; a row is due
26
+ * only when the key is new or its corpus total grew past the folded count.
27
+ * detail is first-seen (the fold's wins); samples are the corpus's first few.
28
+ * @param {Record<string, FoldEntry>} fold current ledger state
29
+ * @param {Occurrence[]} occurrences the FULL corpus's occurrences
30
+ * @returns {object[]} lib-incident rows, unstamped (no seq/ts)
31
+ */
32
+ export function ledgerDeltas(fold: Record<string, FoldEntry>, occurrences: Occurrence[]): object[];
33
+ /**
34
+ * The collector: read the ledger, classify the FULL spine corpus, append the
35
+ * deltas (spine conventions: type first, ts stamped last, seq continuing
36
+ * monotonically across appends). Idempotent over the same corpus; the CLI
37
+ * wrapping this lands at N5, the panel reads the same file at N6.
38
+ * @param {{ledgerFile: string, spineFiles: string[]}} o spineFiles: the corpus
39
+ * (pass ALL of it each time — counts are totals, not increments)
40
+ * @returns {{appended: object[], fold: Record<string, FoldEntry>}}
41
+ */
42
+ export function updateLedger({ ledgerFile, spineFiles }: {
43
+ ledgerFile: string;
44
+ spineFiles: string[];
45
+ }): {
46
+ appended: object[];
47
+ fold: Record<string, FoldEntry>;
48
+ };
49
+ /** severity order, worst-first (the fold's display order; frequency ranks within) */
50
+ export const LEDGER_CLASSES: readonly string[];
51
+ export type Sample = {
52
+ spine: string;
53
+ seq: number;
54
+ };
55
+ export type Occurrence = {
56
+ key: string;
57
+ class: string;
58
+ lib: string;
59
+ verb: string;
60
+ sig: string;
61
+ detail: string;
62
+ sample: Sample;
63
+ };
64
+ export type FoldEntry = {
65
+ key: string;
66
+ lib: string;
67
+ verb: string;
68
+ class: string;
69
+ sig: string;
70
+ detail: string;
71
+ occurrences: number;
72
+ samples: Sample[];
73
+ suggestedAsk: string;
74
+ status: string;
75
+ ref?: string;
76
+ };
package/types/ralph.d.ts CHANGED
@@ -1,6 +1,34 @@
1
1
  /**
2
2
  * Run a close command; its exit code is the verdict (hard green, PRD §4).
3
- * 0 → satisfied; nonzero → needs_revision (gap fed back); spawn error → failed (terminal).
3
+ * `expect` → satisfied; any other exit → needs_revision (gap fed back).
4
+ *
5
+ * THE FORBIDDEN ZONE (PRD v1.11, F17; adaptlearn F25/V10). The two clean bands
6
+ * are green (exit == expect, judgment rendered) and red (exit != expect,
7
+ * judgment rendered). Every other outcome rendered NO JUDGMENT and is therefore
8
+ * NOT A VERDICT — it gets its own name and escalates; coercing one into a
9
+ * verdict IS the instrument fault (law #8 generalized):
10
+ * - `failed` — the close cannot RUN (spawn error). → broken-close
11
+ * - `timed-out` — it ran and never finished judging. → close-timeout
12
+ * - `killed` — it died by signal (status null, no spawn error). → close-killed
13
+ * - `crashed` — it ran, exited, and judged nothing. → close-crashed
14
+ * `timed-out` is split OUT of `failed` deliberately: "raise the timeout" and
15
+ * "fix the argv" are different human decisions, so pooling them erases the
16
+ * decision information the escalation exists to carry.
17
+ *
18
+ * `judged` is the judgment-rendered signal, and it is the hard one. Exit code
19
+ * ALONE cannot separate a crash-at-load from an honest red — they are
20
+ * byte-identical at this seam, and against `node --test` so are the test counts
21
+ * (node synthesizes ONE failing test for the file that crashed, so a crash
22
+ * reports `tests 1 / fail 1`, exactly like a one-assertion failure). So the
23
+ * signal cannot be "zero judged"; it must be a FLOOR against a declared
24
+ * baseline: litectx runs ~390 tests, and a run claiming it judged 1 did not
25
+ * judge. `judged.pattern` extracts one integer from the close's own (redacted)
26
+ * output; below `judged.min` the close crashed, WHATEVER its exit code says —
27
+ * and that check runs on the GREEN side too: a close that exits 0 having judged
28
+ * nothing is a confident fake green, the only real failure there is (law #8).
29
+ * `judged` is OPTIONAL — a linter or a hitl close may have no count to give —
30
+ * and its absence stamps `unaudited: true` so the blind spot is NAMED on the
31
+ * record rather than passed off as a trustworthy exit code.
4
32
  *
5
33
  * `redact` scrubs the close's own output at the SOURCE — before the gap becomes
6
34
  * a spine event or a worker prompt — so a secret a checked command echoes
@@ -10,24 +38,49 @@
10
38
  * primitive (bareguard's, wired by the interpreter), never an emergent
11
39
  * component — so V4 holds. The shell's canonical emission IS the redacted text;
12
40
  * a benign gap is returned byte-identical (default: identity).
41
+ * `cwd` is where the close RUNS, and it is load-bearing (F8): a close is a
42
+ * command in a repository — `npm test`, `make check` — and every one of them is
43
+ * cwd-relative. Run it anywhere but the workdir and the arbiter judges the wrong
44
+ * tree: exit-code-is-truth silently becomes exit-code-of-some-other-repo-is-truth.
45
+ * The default (the runner's own cwd) exists only for callers with an absolute
46
+ * close; the runner and the interpreter always pass the workdir.
47
+ *
48
+ * `gapKeep` (F28) is a regex SOURCE whose matching lines are PRESERVED through
49
+ * the gap bound in addition to the head/tail sample: a large TAP suite prints
50
+ * its `not ok` lines in the middle of the stream, exactly where `boundGap`
51
+ * elides — so the failing-test NAMES (the causal input the worker navigates on)
52
+ * were deleted in transit and the worker was told "5 fail" and never WHICH. It
53
+ * is arbiter territory like the rest here: the drafted config cannot express it.
54
+ *
13
55
  * @param {string[]} close argv, e.g. ['node', '--test', 'close/']
14
56
  * @param {(s: string) => string} [redact] source scrubber; identity by default
57
+ * @param {{ timeoutMs?: number, cwd?: string, expect?: number, judged?: {pattern: string, min: number}, gapKeep?: string }} [opts]
58
+ * close wall-clock cap (operator-set via the runner, never the config's — the
59
+ * agent must not author its arbiter's clock), the directory the close runs in
60
+ * (the workdir — the tree it is judging), the exit code the SIGNED spec calls
61
+ * success, the judgment-rendered signal, and the kept-failures pattern. All
62
+ * five are arbiter territory: the drafted workflow config cannot express any.
63
+ * @returns {{verdict: 'satisfied'|'needs_revision'|'failed'|'timed-out'|'killed'|'crashed',
64
+ * gap?: string, exitCode?: number, signal?: string, detail?: string,
65
+ * judgedCount?: number|null, unaudited?: boolean}}
15
66
  */
16
- export function runClose(close: string[], redact?: (s: string) => string): {
17
- verdict: string;
18
- detail: string;
19
- gap?: undefined;
20
- exitCode?: undefined;
21
- } | {
22
- verdict: string;
23
- detail?: undefined;
24
- gap?: undefined;
25
- exitCode?: undefined;
26
- } | {
27
- verdict: string;
28
- gap: string;
29
- exitCode: number | null;
30
- detail?: undefined;
67
+ export function runClose(close: string[], redact?: (s: string) => string, { timeoutMs, cwd, expect, judged, gapKeep }?: {
68
+ timeoutMs?: number;
69
+ cwd?: string;
70
+ expect?: number;
71
+ judged?: {
72
+ pattern: string;
73
+ min: number;
74
+ };
75
+ gapKeep?: string;
76
+ }): {
77
+ verdict: "satisfied" | "needs_revision" | "failed" | "timed-out" | "killed" | "crashed";
78
+ gap?: string;
79
+ exitCode?: number;
80
+ signal?: string;
81
+ detail?: string;
82
+ judgedCount?: number | null;
83
+ unaudited?: boolean;
31
84
  };
32
85
  /**
33
86
  * The loop: `while close-red and under-cap: run the middle`. Stops at first
@@ -50,12 +103,52 @@ export function runClose(close: string[], redact?: (s: string) => string): {
50
103
  * @param {(type: string, data?: object) => object} opts.emit a spine emitter
51
104
  * @param {(s: string) => string} [opts.redact] source scrubber for close output
52
105
  * (secrets never enter the spine); injected so the shell stays stdlib-only
106
+ * @param {number} [opts.closeTimeoutMs] close wall-clock cap (shell/operator
107
+ * territory — the workflow config cannot express it)
108
+ * @param {string} [opts.cwd] the directory every close runs in — the tree it is
109
+ * judging (F8: a cwd-relative close run elsewhere judges the wrong repository)
110
+ * @param {number} [opts.expect] the exit code the SIGNED spec calls success
111
+ * @param {{pattern: string, min: number}} [opts.judged] the judgment-rendered signal
112
+ * @param {string} [opts.gapKeep] kept-failures pattern (F28): matching close-output
113
+ * lines survive the gap bound so a large suite's `not ok` names reach the worker
114
+ * @param {() => string[]} [opts.workerWrites] the crash-attribution instrument (F32):
115
+ * answers "which files has the WORKER written so far this run?" — the runner wires
116
+ * it to the gate audit's allow-decision writes. Injected like `redact` so the shell
117
+ * stays stdlib-only and dumb; consulted ONLY at a crashed verdict. With no seam, or
118
+ * with zero writes, a crash stays what it always was: an instrument stop.
53
119
  * @returns {Promise<'green'|'escalated'>}
54
120
  */
55
- export function ralph({ middle, close, capRuns, emit, redact }: {
121
+ export function ralph({ middle, close, capRuns, emit, redact, closeTimeoutMs, cwd, expect, judged, gapKeep, workerWrites }: {
56
122
  middle: (iteration: number, gap?: string) => void | Promise<void>;
57
123
  close: string[];
58
124
  capRuns: number;
59
125
  emit: (type: string, data?: object) => object;
60
126
  redact?: ((s: string) => string) | undefined;
127
+ closeTimeoutMs?: number | undefined;
128
+ cwd?: string | undefined;
129
+ expect?: number | undefined;
130
+ judged?: {
131
+ pattern: string;
132
+ min: number;
133
+ } | undefined;
134
+ gapKeep?: string | undefined;
135
+ workerWrites?: (() => string[]) | undefined;
61
136
  }): Promise<"green" | "escalated">;
137
+ /**
138
+ * The forbidden zone, named (PRD v1.11). Each row is an outcome in which the
139
+ * close rendered NO JUDGMENT — so it is not a verdict, it never feeds a gap
140
+ * back, and it is never retried. They are kept SEPARATE on purpose: pooling
141
+ * them would erase the decision information the escalation exists to carry —
142
+ * "raise the timeout", "fix the argv", and "re-run, it was OOM-killed" are
143
+ * three different human answers.
144
+ * Exported because the RUNNER's close-first precheck runs the same arbiter
145
+ * before any tokens: two maps would be two instruments, and two instruments
146
+ * disagreeing about the same outcome is the fault this whole addendum exists
147
+ * to close.
148
+ * @type {Record<string, {category: string, decision: string, options: string[]}>}
149
+ */
150
+ export const CLOSE_FAULTS: Record<string, {
151
+ category: string;
152
+ decision: string;
153
+ options: string[];
154
+ }>;
package/types/run.d.ts ADDED
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Run an approved job end to end: approval gate → primitive smoke → sequential
3
+ * per-step interpret loops under the one ledger → the hitl step opens the draft
4
+ * PR (deterministic git, never model tools) and becomes the decision-ready
5
+ * escalation. Every predicate step runs its close FIRST (close-first skip,
6
+ * resume model): already-green skips the step for zero tokens as a distinct
7
+ * spine record, so a stopped run reruns from where it died — the workdir plus
8
+ * the closes are the checkpoint. The sealed config draft (one shot + one
9
+ * redraft) is deferred to the first step that actually needs a worker; a clean
10
+ * rerun pays zero provider calls.
11
+ *
12
+ * N2 bounds (honest, documented): text-mode steps write a single `target`
13
+ * artifact; tool-mode steps (2b) work multi-file through the gated shell tools,
14
+ * granted per step by the SPEC (mode/tools — the drafted config cannot express
15
+ * either); `gold`/`rubric` closes refuse `close-unsupported` (execution lands
16
+ * with the verdict classes, N4); the pricing-red halt lands at the step
17
+ * boundary — the Gate still caps within a step, but an unpriced result cannot
18
+ * be summed, so the run stops rather than counting it $0 (F6).
19
+ *
20
+ * @param {object|string} rawSpec the job spec (job-v1), text or parsed
21
+ * @param {object} opts
22
+ * @param {unknown} opts.approvals `{ specHash, signer, ts }` records (from OUTSIDE the spec)
23
+ * @param {string} opts.workdir the run directory (the fence's root)
24
+ * @param {string} [opts.target] the step artifact path (text-mode steps; unused by tool-mode steps)
25
+ * @param {any} opts.provider shell-owned LLM binding (never the config's)
26
+ * @param {(type: string, data?: object) => object} opts.emit spine emitter
27
+ * @param {number} [opts.capRuns] shell-owned per-step iteration cap
28
+ * @param {number} [opts.shellCapUsd] the shell's hard USD ceiling
29
+ * @param {number} [opts.closeTimeoutMs] close wall-clock cap (shell territory)
30
+ * @param {ExecCmd} [opts.execCmd] process runner for the hitl PR mechanics
31
+ * (shell-owned seam, same doctrine as the provider binding)
32
+ * @returns {Promise<string>} outcome: 'green' | 'escalated' | 'unapproved-spec' |
33
+ * 'job-red' | 'smoke-red' | 'config-red' | 'pricing-red' | 'provider-red' |
34
+ * 'cap-halt' | 'close-unsupported' | `step-red:<id>`
35
+ */
36
+ export function runJob(rawSpec: object | string, { approvals, workdir, target, provider, emit, capRuns, shellCapUsd, closeTimeoutMs, execCmd }: {
37
+ approvals: unknown;
38
+ workdir: string;
39
+ target?: string | undefined;
40
+ provider: any;
41
+ emit: (type: string, data?: object) => object;
42
+ capRuns?: number | undefined;
43
+ shellCapUsd?: number | undefined;
44
+ closeTimeoutMs?: number | undefined;
45
+ execCmd?: ExecCmd | undefined;
46
+ }): Promise<string>;
47
+ export type Red = {
48
+ code: string;
49
+ path: string;
50
+ detail?: string;
51
+ };
52
+ export type ExecCmd = (argv: string[], opts?: {
53
+ cwd?: string;
54
+ }) => {
55
+ status: number | null;
56
+ stdout: string;
57
+ stderr: string;
58
+ };
package/types/text.d.ts CHANGED
@@ -1 +1,35 @@
1
- export function stripFences(t: string): string;
1
+ /**
2
+ * Fence-robust artifact extraction (F2 port requirements #1/#2):
3
+ * - a fenced block opening within the first {@link FENCE_PREAMBLE_LINES} lines
4
+ * is the artifact — prose-wrapped and preamble fences extract clean (the F21
5
+ * instrument caveat: prose written to the target corrupted the close signal);
6
+ * - a fence opening deeper is the artifact's own content — the trimmed whole
7
+ * text is the artifact (fence-heavy artifacts belong in tool mode, where the
8
+ * worker writes files directly and nothing is parsed);
9
+ * - no fence → the trimmed whole text, with N0's unclosed-leading-fence strip
10
+ * as the fallback (reference parity);
11
+ * - nothing extractable → `code: null` plus a named reason: artifact-red
12
+ * material for the caller's OWN axis, never a silent empty write.
13
+ * Never throws.
14
+ * @param {string|null|undefined} t
15
+ * @returns {{ code: string|null, red: string|null }}
16
+ */
17
+ export function extractArtifact(t: string | null | undefined): {
18
+ code: string | null;
19
+ red: string | null;
20
+ };
21
+ /**
22
+ * The ONE spelling of the F6 honest-null cost read on a bare-agent result.
23
+ * `metrics.costUsd` is the honest null when NOTHING priced — never fall
24
+ * through to `cost` when metrics exist: `cost` sums priced rounds only, so an
25
+ * all-unpriced run reports cost 0 and `?? cost` would launder the explicit
26
+ * unknown into a silent $0 (the F6 class — four shipped launderings were this
27
+ * exact fallback chain hand-spelled). `unpricedRounds` makes a PARTIALLY
28
+ * unpriced run visible (costUsd finite but an under-count).
29
+ * @param {any} r a Loop.run() result (or null)
30
+ * @returns {{ costUsd: number|null, unpricedRounds: number }}
31
+ */
32
+ export function priceOf(r: any): {
33
+ costUsd: number | null;
34
+ unpricedRounds: number;
35
+ };
@@ -25,6 +25,18 @@ export function scopeContained(s: string): boolean;
25
25
  export function isObj(v: unknown): boolean;
26
26
  /** @param {unknown} v @returns {v is string} */
27
27
  export function isNonEmptyString(v: unknown): v is string;
28
+ /**
29
+ * Scan a RAW text stream (a spine file, a close's output, a transcript) for
30
+ * known secret shapes and return the literal matches. The ONE spelling of the
31
+ * text-side scan, for the same reason SECRET_PATTERNS is the ONE inventory: a
32
+ * hand-rolled copy that misses a shape is a leak on the very output it guards
33
+ * (seven copies of this expression lived in scripts/ before it landed here).
34
+ * `sweepSecretLiterals` is the config-tree twin — same shapes, tree walk.
35
+ * Never throws; returns [] for a missing stream.
36
+ * @param {unknown} raw
37
+ * @returns {string[]} every match, in stream order per pattern
38
+ */
39
+ export function scanSecrets(raw: unknown): string[];
28
40
  /**
29
41
  * Red every string in a config tree that carries a known secret-token shape.
30
42
  * @param {any} root
@@ -66,4 +78,5 @@ export function diffPaths(a: object, b: object): string[];
66
78
  export const LOOP_SHAPES: readonly string[];
67
79
  export const SLOTS: readonly string[];
68
80
  export const VERBS: readonly string[];
81
+ export const REMEMBER_KINDS: string[];
69
82
  export const SECRET_PATTERNS: RegExp[];