bareloop 0.0.1 → 0.1.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/CHANGELOG.md +124 -0
- package/LICENSE +202 -0
- package/README.md +123 -11
- package/bareloop.context.md +115 -0
- package/package.json +40 -3
- package/src/extract.js +95 -0
- package/src/index.js +9 -0
- package/src/interpret.js +228 -0
- package/src/ralph.js +109 -0
- package/src/spine.js +29 -0
- package/src/text.js +9 -0
- package/src/validate.js +213 -0
- package/types/extract.d.ts +23 -0
- package/types/index.d.ts +5 -0
- package/types/interpret.d.ts +57 -0
- package/types/ralph.d.ts +48 -0
- package/types/spine.d.ts +6 -0
- package/types/text.d.ts +1 -0
- package/types/validate.d.ts +38 -0
package/src/validate.js
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// Workflow-config validator — a deterministic predicate that runs before any
|
|
2
|
+
// tokens burn (PRD §4: schema-validated, config-red before tokens). Every
|
|
3
|
+
// failure is a distinct named red { code, path, detail } so the spine can carry
|
|
4
|
+
// `config-red: missing-required:gate.writeScope`. An invalid config is a red,
|
|
5
|
+
// not a crash: this module never throws on bad input.
|
|
6
|
+
//
|
|
7
|
+
// The verb vocabulary is BOUND from litectx (adaptlearn F1 — consume, don't
|
|
8
|
+
// build; design law #10); this schema exposes the adaptlearn-proven 4-verb
|
|
9
|
+
// subset. The close and the provider are deliberately not expressible here:
|
|
10
|
+
// they arrive as unknown-field reds — the shell owns both (design law #1).
|
|
11
|
+
|
|
12
|
+
import { COMPRESS_LEVELS, KINDS, WRITE_KINDS } from 'litectx';
|
|
13
|
+
|
|
14
|
+
export const LOOP_SHAPES = ['refine', 'plan'];
|
|
15
|
+
export const SLOTS = ['before-attempt', 'after-red', 'on-green'];
|
|
16
|
+
export const VERBS = ['recall', 'compress', 'stash', 'remember'];
|
|
17
|
+
const TOP_FIELDS = ['schema', 'loop', 'memory', 'hooks', 'gate', 'escalation'];
|
|
18
|
+
const MAX_OPS_PER_SLOT = 2;
|
|
19
|
+
|
|
20
|
+
// per-verb parameter contracts: name → check(value) (op field itself excluded).
|
|
21
|
+
// remember's kinds are NARROWER than recall's: bound from litectx WRITE_KINDS
|
|
22
|
+
// (the set remember() itself validates against — adaptlearn F5's drift lesson:
|
|
23
|
+
// the export you bind can be wider than the function you call), minus doc:
|
|
24
|
+
// the doc/upload axis stays gated out deliberately.
|
|
25
|
+
const REMEMBER_KINDS = WRITE_KINDS.filter((k) => k !== 'doc');
|
|
26
|
+
const VERB_PARAMS = {
|
|
27
|
+
recall: { k: (v) => Number.isInteger(v) && v >= 1 && v <= 20, kinds: isKinds },
|
|
28
|
+
compress: { level: (v) => COMPRESS_LEVELS.includes(v) },
|
|
29
|
+
stash: {},
|
|
30
|
+
remember: { kind: (v) => REMEMBER_KINDS.includes(v) },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Map a schema writeScope entry to its enforcement prefix. bareguard
|
|
35
|
+
* fs.writeScope is prefix-containment, not glob (adaptlearn F4/F9): the
|
|
36
|
+
* trailing "/**" | "/*" form maps to its directory prefix. The validator's
|
|
37
|
+
* legality rule and the interpreter's enforcement mapping BOTH go through this
|
|
38
|
+
* one helper — if they ever used different transforms, a scope could validate
|
|
39
|
+
* green and then gate-red every write at runtime (the F9 red-class).
|
|
40
|
+
* @param {string} scope
|
|
41
|
+
*/
|
|
42
|
+
export function globToPrefix(scope) {
|
|
43
|
+
return scope.replace(/\/\*\*?$/, '');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* A scope may never reach the arbiter's inputs (design law #1): it must
|
|
48
|
+
* resolve to a PROPER subdirectory of the run directory. Absolute paths and
|
|
49
|
+
* ".." segments escape it; "." / "./**" cover the whole run directory — where
|
|
50
|
+
* the close suite lives. Windows spellings ("..\", "C:\") count as escapes.
|
|
51
|
+
* @param {string} s
|
|
52
|
+
*/
|
|
53
|
+
function scopeContained(s) {
|
|
54
|
+
if (s.startsWith('/') || s.includes('\\') || /^[a-zA-Z]:/.test(s)) return false;
|
|
55
|
+
const prefix = globToPrefix(s);
|
|
56
|
+
if (prefix === '' || prefix === '.') return false;
|
|
57
|
+
return prefix.split('/').every((seg) => seg !== '..');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Each verb is legal ONLY in the slot where it has effect: recall/compress
|
|
61
|
+
// build the attempt context (consumed by before-attempt only), stash parks the
|
|
62
|
+
// gap (which exists only after-red), remember is verdict-gated retention
|
|
63
|
+
// (on-green only, design law #2). An op that validates green but is inert at
|
|
64
|
+
// runtime would still emit hook-op events — a live-looking knob with zero
|
|
65
|
+
// effect, polluting the contrast evidence the extractor attributes by
|
|
66
|
+
// (design law #3; the F16 credit-loss class).
|
|
67
|
+
const VERB_SLOT = { recall: 'before-attempt', compress: 'before-attempt', stash: 'after-red', remember: 'on-green' };
|
|
68
|
+
|
|
69
|
+
function isKinds(v) {
|
|
70
|
+
return Array.isArray(v) && v.length > 0 && v.every((k) => KINDS.includes(k));
|
|
71
|
+
}
|
|
72
|
+
function isObj(v) {
|
|
73
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate a workflow config against schema v1.
|
|
78
|
+
* @param {object|string} input parsed config, or raw JSON text (parse failures are a red)
|
|
79
|
+
* @param {{ shellCapUsd?: number }} [opts] the shell's cap — a config may tighten it, never exceed it
|
|
80
|
+
* @returns {{ ok: boolean, reds: Array<{code: string, path: string, detail?: string}> }}
|
|
81
|
+
*/
|
|
82
|
+
export function validateConfig(input, { shellCapUsd = 2 } = {}) {
|
|
83
|
+
/** @type {Array<{code: string, path: string, detail?: string}>} */
|
|
84
|
+
const reds = [];
|
|
85
|
+
/** @type {(code: string, path: string, detail?: string) => void} */
|
|
86
|
+
const red = (code, path, detail) => { reds.push(detail ? { code, path, detail } : { code, path }); };
|
|
87
|
+
|
|
88
|
+
let c = input;
|
|
89
|
+
if (typeof c === 'string') {
|
|
90
|
+
try { c = JSON.parse(c); } catch (e) {
|
|
91
|
+
return { ok: false, reds: [{ code: 'parse-error', path: '$', detail: String(/** @type {Error} */ (e).message) }] };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!isObj(c)) return { ok: false, reds: [{ code: 'parse-error', path: '$', detail: 'config must be a JSON object' }] };
|
|
95
|
+
|
|
96
|
+
// 1. shape — unknown top-level fields red here (smuggled close/provider included)
|
|
97
|
+
for (const key of Object.keys(c)) {
|
|
98
|
+
if (!TOP_FIELDS.includes(key)) red('unknown-field', key);
|
|
99
|
+
}
|
|
100
|
+
if (c.schema === undefined) red('missing-required', 'schema');
|
|
101
|
+
else if (c.schema !== 'v1') red('invalid-value', 'schema', `expected "v1", got ${JSON.stringify(c.schema)}`);
|
|
102
|
+
|
|
103
|
+
// 2. required bindings + 3. bounds, section by section
|
|
104
|
+
const loop = isObj(c.loop) ? c.loop : {};
|
|
105
|
+
if (loop.shape === undefined) red('missing-required', 'loop.shape');
|
|
106
|
+
else if (!LOOP_SHAPES.includes(loop.shape)) red('invalid-value', 'loop.shape', `menu: ${LOOP_SHAPES.join('|')}`);
|
|
107
|
+
if (loop.maxIterations !== undefined
|
|
108
|
+
&& !(Number.isInteger(loop.maxIterations) && loop.maxIterations >= 1 && loop.maxIterations <= 8)) {
|
|
109
|
+
red('bounds', 'loop.maxIterations', '1..8');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const memory = isObj(c.memory) ? c.memory : {};
|
|
113
|
+
if (memory.store === undefined) red('missing-required', 'memory.store');
|
|
114
|
+
else if (memory.store !== 'litectx') red('invalid-value', 'memory.store', 'v1 binds "litectx"');
|
|
115
|
+
if (isObj(memory.recall)) {
|
|
116
|
+
const { k, kinds } = memory.recall;
|
|
117
|
+
if (k !== undefined && !(Number.isInteger(k) && k >= 1 && k <= 20)) red('bounds', 'memory.recall.k', '1..20');
|
|
118
|
+
if (kinds !== undefined && !isKinds(kinds)) red('invalid-value', 'memory.recall.kinds', `subset of ${KINDS.join('|')}`);
|
|
119
|
+
}
|
|
120
|
+
if (memory.compressLevel !== undefined && !COMPRESS_LEVELS.includes(memory.compressLevel)) {
|
|
121
|
+
red('invalid-value', 'memory.compressLevel', COMPRESS_LEVELS.join('|'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const gate = isObj(c.gate) ? c.gate : {};
|
|
125
|
+
if (gate.budgetUsd === undefined) red('missing-required', 'gate.budgetUsd');
|
|
126
|
+
else if (!(typeof gate.budgetUsd === 'number' && gate.budgetUsd > 0 && gate.budgetUsd <= shellCapUsd)) {
|
|
127
|
+
red('bounds', 'gate.budgetUsd', `0 < budget <= shell cap ${shellCapUsd}`);
|
|
128
|
+
}
|
|
129
|
+
if (gate.writeScope === undefined) red('missing-required', 'gate.writeScope');
|
|
130
|
+
else if (!(Array.isArray(gate.writeScope) && gate.writeScope.length > 0
|
|
131
|
+
&& gate.writeScope.every((s) => typeof s === 'string' && s.length > 0))) {
|
|
132
|
+
red('invalid-value', 'gate.writeScope', 'non-empty array of glob strings');
|
|
133
|
+
} else if (!gate.writeScope.every((s) => !globToPrefix(s).includes('*'))) {
|
|
134
|
+
// adaptlearn F9: enforcement (bareguard fs.writeScope) is prefix-containment — a wildcard
|
|
135
|
+
// anywhere but a trailing /** or /* is inexpressible there, so it would validate green
|
|
136
|
+
// and then gate-red EVERY write at runtime. Reds-before-tokens means rejecting it here.
|
|
137
|
+
red('invalid-value', 'gate.writeScope', 'wildcards only as a trailing "/**" or "/*" (enforcement is prefix-containment, adaptlearn F9)');
|
|
138
|
+
} else if (!gate.writeScope.every(scopeContained)) {
|
|
139
|
+
// A scope that can reach the arbiter's inputs is the config-level
|
|
140
|
+
// fit-to-pass surface (design law #1). Reds-before-tokens.
|
|
141
|
+
red('invalid-value', 'gate.writeScope', 'must be a proper subdirectory of the run directory — no absolute paths, no ".." segments, not the run dir itself (design law #1)');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const escalation = isObj(c.escalation) ? c.escalation : {};
|
|
145
|
+
if (escalation.mode === undefined) red('missing-required', 'escalation.mode');
|
|
146
|
+
else if (escalation.mode !== 'decision-ready') red('invalid-value', 'escalation.mode', 'must be "decision-ready"');
|
|
147
|
+
|
|
148
|
+
// 4. verb legality inside the slots
|
|
149
|
+
if (c.hooks !== undefined) {
|
|
150
|
+
const hooks = isObj(c.hooks) ? c.hooks : {};
|
|
151
|
+
if (!isObj(c.hooks)) red('invalid-value', 'hooks', 'must be an object of slots');
|
|
152
|
+
for (const [slot, ops] of Object.entries(hooks)) {
|
|
153
|
+
const at = `hooks.${slot}`;
|
|
154
|
+
if (!SLOTS.includes(slot)) { red('unknown-field', at); continue; }
|
|
155
|
+
if (!Array.isArray(ops)) { red('invalid-value', at, 'must be an array of ops'); continue; }
|
|
156
|
+
if (ops.length > MAX_OPS_PER_SLOT) { red('slot-overflow', at, `max ${MAX_OPS_PER_SLOT} ops`); continue; }
|
|
157
|
+
ops.forEach((op, i) => {
|
|
158
|
+
const opAt = `${at}.${i}`;
|
|
159
|
+
if (!isObj(op) || !VERBS.includes(op.op)) { red('verb-illegal', opAt, `verbs: ${VERBS.join('|')}`); return; }
|
|
160
|
+
if (VERB_SLOT[op.op] !== slot) {
|
|
161
|
+
red('verb-placement', opAt, `${op.op} is legal only in ${VERB_SLOT[op.op]} — the one slot where it has effect (an inert op is a fake knob in the contrast evidence)`);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const params = VERB_PARAMS[op.op];
|
|
165
|
+
for (const [key, value] of Object.entries(op)) {
|
|
166
|
+
if (key === 'op') continue;
|
|
167
|
+
// Object.hasOwn, not `in`: params named after Object.prototype members
|
|
168
|
+
// ("constructor", "toString") must not smuggle past the unknown-param red.
|
|
169
|
+
if (!Object.hasOwn(params, key)) red('verb-params', `${opAt}.${key}`, `unknown param for ${op.op}`);
|
|
170
|
+
else if (!params[key](value)) red('verb-params', `${opAt}.${key}`, `invalid value for ${op.op}.${key}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { ok: reds.length === 0, reds };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Changed JSON paths between two configs — the one-knob mutation checker
|
|
181
|
+
* (a legal mutant has exactly one). A subtree present on only one side counts
|
|
182
|
+
* as ONE path (its root), so "add an op to a slot" is one knob, not two params.
|
|
183
|
+
* @param {object} a
|
|
184
|
+
* @param {object} b
|
|
185
|
+
* @returns {string[]} sorted changed paths, dot-notation with array indices
|
|
186
|
+
*/
|
|
187
|
+
export function diffPaths(a, b) {
|
|
188
|
+
/** @type {string[]} */
|
|
189
|
+
const paths = [];
|
|
190
|
+
walk(a, b, '');
|
|
191
|
+
return paths.sort();
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {any} x
|
|
195
|
+
* @param {any} y
|
|
196
|
+
* @param {string} at
|
|
197
|
+
*/
|
|
198
|
+
function walk(x, y, at) {
|
|
199
|
+
if (x === y) return;
|
|
200
|
+
const bothObj = isObj(x) && isObj(y);
|
|
201
|
+
const bothArr = Array.isArray(x) && Array.isArray(y);
|
|
202
|
+
if (!bothObj && !bothArr) {
|
|
203
|
+
if (JSON.stringify(x) !== JSON.stringify(y)) paths.push(at || '$');
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const keys = new Set([...Object.keys(x), ...Object.keys(y)]);
|
|
207
|
+
for (const key of keys) {
|
|
208
|
+
const p = at ? `${at}.${key}` : key;
|
|
209
|
+
if (!(key in x) || !(key in y)) paths.push(p); // added/removed subtree = one knob
|
|
210
|
+
else walk(x[key], y[key], p);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Distill/update a lineage's rules from one green run's ledger facts.
|
|
3
|
+
* @param {object} opts
|
|
4
|
+
* @param {object} opts.config the config that went green
|
|
5
|
+
* @param {object} opts.provider a bareagent provider — SHELL-owned, sealed
|
|
6
|
+
* @param {string[]|null} opts.priorRules the lineage's current rules, if any
|
|
7
|
+
* @param {string[]} [opts.revisionDiff] changed config paths, when the run recovered mid-run
|
|
8
|
+
* @returns {Promise<{rules: string[]|null, valid: boolean, reds: Array<object>, costUsd: number, raw: string}>}
|
|
9
|
+
*/
|
|
10
|
+
export function extractRules({ config, provider, priorRules, revisionDiff }: {
|
|
11
|
+
config: object;
|
|
12
|
+
provider: object;
|
|
13
|
+
priorRules: string[] | null;
|
|
14
|
+
revisionDiff?: string[] | undefined;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
rules: string[] | null;
|
|
17
|
+
valid: boolean;
|
|
18
|
+
reds: Array<object>;
|
|
19
|
+
costUsd: number;
|
|
20
|
+
raw: string;
|
|
21
|
+
}>;
|
|
22
|
+
export const MAX_RULES: 5;
|
|
23
|
+
export const MAX_RULE_CHARS: 200;
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
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 { interpret, STALL_REDS } from "./interpret.js";
|
|
5
|
+
export { extractRules, MAX_RULES, MAX_RULE_CHARS } from "./extract.js";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute a workflow config against one task under the dumb shell.
|
|
3
|
+
*
|
|
4
|
+
* @param {object|string} configRaw schema v1 config (object or raw JSON text)
|
|
5
|
+
* @param {object} opts
|
|
6
|
+
* @param {string} opts.task implement instruction shown to the worker
|
|
7
|
+
* @param {string} opts.target absolute path the artifact is written to
|
|
8
|
+
* @param {string[]} opts.close argv whose exit code is truth (shell-owned)
|
|
9
|
+
* @param {string} opts.workdir run directory (litectx root, gate audit, scope base)
|
|
10
|
+
* @param {number} opts.capRuns shell iteration budget; the config may tighten via loop.maxIterations, never exceed
|
|
11
|
+
* @param {(type: string, data?: object) => object} opts.emit spine emitter
|
|
12
|
+
* @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
|
|
14
|
+
* @param {(o: {config: object, gaps: string[], policy: any, onLlmResult: any}) => Promise<{candidate: object|null, parseError?: string|null, costUsd?: number}>} [opts.revisor]
|
|
15
|
+
* optional mid-run revision seam. Fires ONCE per run after STALL_REDS consecutive
|
|
16
|
+
* close reds. The interpreter — never the revisor — owns acceptance: the candidate
|
|
17
|
+
* must validate, and gate/escalation/loop.maxIterations must be unchanged
|
|
18
|
+
* (arbiter-touch / cap-touch revision-reds otherwise; the run continues on the old
|
|
19
|
+
* config). Revisor spend rides the run's own gate handlers — same budget axis as
|
|
20
|
+
* the worker.
|
|
21
|
+
* @returns {Promise<'green'|'escalated'|'config-red'>}
|
|
22
|
+
*/
|
|
23
|
+
export function interpret(configRaw: object | string, { task, target, close, workdir, capRuns, emit, provider, shellCapUsd, revisor }: {
|
|
24
|
+
task: string;
|
|
25
|
+
target: string;
|
|
26
|
+
close: string[];
|
|
27
|
+
workdir: string;
|
|
28
|
+
capRuns: number;
|
|
29
|
+
emit: (type: string, data?: object) => object;
|
|
30
|
+
provider: object;
|
|
31
|
+
shellCapUsd?: number | undefined;
|
|
32
|
+
revisor?: ((o: {
|
|
33
|
+
config: object;
|
|
34
|
+
gaps: string[];
|
|
35
|
+
policy: any;
|
|
36
|
+
onLlmResult: any;
|
|
37
|
+
}) => Promise<{
|
|
38
|
+
candidate: object | null;
|
|
39
|
+
parseError?: string | null;
|
|
40
|
+
costUsd?: number;
|
|
41
|
+
}>) | undefined;
|
|
42
|
+
}): Promise<"green" | "escalated" | "config-red">;
|
|
43
|
+
/** @typedef {Error & {category?: string}} CategorizedError the failure map's carrier: ralph relays by `category` */
|
|
44
|
+
export const STALL_REDS: 2;
|
|
45
|
+
/**
|
|
46
|
+
* the failure map's carrier: ralph relays by `category`
|
|
47
|
+
*/
|
|
48
|
+
export type CategorizedError = Error & {
|
|
49
|
+
category?: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* litectx recall hit — body present only with `{body: true}`
|
|
53
|
+
*/
|
|
54
|
+
export type RecallHit = {
|
|
55
|
+
body?: string | null;
|
|
56
|
+
text?: string | null;
|
|
57
|
+
};
|
package/types/ralph.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
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).
|
|
4
|
+
* @param {string[]} close argv, e.g. ['node', '--test', 'close/']
|
|
5
|
+
*/
|
|
6
|
+
export function runClose(close: string[]): {
|
|
7
|
+
verdict: string;
|
|
8
|
+
detail: string;
|
|
9
|
+
gap?: undefined;
|
|
10
|
+
exitCode?: undefined;
|
|
11
|
+
} | {
|
|
12
|
+
verdict: string;
|
|
13
|
+
detail?: undefined;
|
|
14
|
+
gap?: undefined;
|
|
15
|
+
exitCode?: undefined;
|
|
16
|
+
} | {
|
|
17
|
+
verdict: string;
|
|
18
|
+
gap: string;
|
|
19
|
+
exitCode: number | null;
|
|
20
|
+
detail?: undefined;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The loop: `while close-red and under-cap: run the middle`. Stops at first
|
|
24
|
+
* green (within-run tuning past a visible close is the fit-to-pass surface,
|
|
25
|
+
* PRD §2). The two honest terminals are green and a decision-ready escalation;
|
|
26
|
+
* cap-halt means "not under cap", its own category, never merged with "wrong"
|
|
27
|
+
* (design law #8). A broken close escalates immediately — a broken arbiter
|
|
28
|
+
* must not masquerade as a bad harness.
|
|
29
|
+
*
|
|
30
|
+
* A middle that throws is read through the failure map: a throw carrying
|
|
31
|
+
* `category: 'cap-halt'` (the USD gate tripping inside the middle) escalates
|
|
32
|
+
* as cap-halt; any other named category is relayed as-is; an unnamed throw is
|
|
33
|
+
* an interpreter-red — a broken interpreter must not masquerade as a bad
|
|
34
|
+
* harness. Ralph never interprets, only relays.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} opts
|
|
37
|
+
* @param {(iteration: number, gap?: string) => void|Promise<void>} opts.middle the emergent middle; never sees close/cap
|
|
38
|
+
* @param {string[]} opts.close argv whose exit code is truth
|
|
39
|
+
* @param {number} opts.capRuns budget: max middle runs
|
|
40
|
+
* @param {(type: string, data?: object) => object} opts.emit a spine emitter
|
|
41
|
+
* @returns {Promise<'green'|'escalated'>}
|
|
42
|
+
*/
|
|
43
|
+
export function ralph({ middle, close, capRuns, emit }: {
|
|
44
|
+
middle: (iteration: number, gap?: string) => void | Promise<void>;
|
|
45
|
+
close: string[];
|
|
46
|
+
capRuns: number;
|
|
47
|
+
emit: (type: string, data?: object) => object;
|
|
48
|
+
}): Promise<"green" | "escalated">;
|
package/types/spine.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an emitter bound to one JSONL file.
|
|
3
|
+
* @param {string} file absolute path; created on first emit
|
|
4
|
+
* @returns {(type: string, data?: object) => object} emit — returns the event as written
|
|
5
|
+
*/
|
|
6
|
+
export function makeSpine(file: string): (type: string, data?: object) => object;
|
package/types/text.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function stripFences(t: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map a schema writeScope entry to its enforcement prefix. bareguard
|
|
3
|
+
* fs.writeScope is prefix-containment, not glob (adaptlearn F4/F9): the
|
|
4
|
+
* trailing "/**" | "/*" form maps to its directory prefix. The validator's
|
|
5
|
+
* legality rule and the interpreter's enforcement mapping BOTH go through this
|
|
6
|
+
* one helper — if they ever used different transforms, a scope could validate
|
|
7
|
+
* green and then gate-red every write at runtime (the F9 red-class).
|
|
8
|
+
* @param {string} scope
|
|
9
|
+
*/
|
|
10
|
+
export function globToPrefix(scope: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Validate a workflow config against schema v1.
|
|
13
|
+
* @param {object|string} input parsed config, or raw JSON text (parse failures are a red)
|
|
14
|
+
* @param {{ shellCapUsd?: number }} [opts] the shell's cap — a config may tighten it, never exceed it
|
|
15
|
+
* @returns {{ ok: boolean, reds: Array<{code: string, path: string, detail?: string}> }}
|
|
16
|
+
*/
|
|
17
|
+
export function validateConfig(input: object | string, { shellCapUsd }?: {
|
|
18
|
+
shellCapUsd?: number;
|
|
19
|
+
}): {
|
|
20
|
+
ok: boolean;
|
|
21
|
+
reds: Array<{
|
|
22
|
+
code: string;
|
|
23
|
+
path: string;
|
|
24
|
+
detail?: string;
|
|
25
|
+
}>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Changed JSON paths between two configs — the one-knob mutation checker
|
|
29
|
+
* (a legal mutant has exactly one). A subtree present on only one side counts
|
|
30
|
+
* as ONE path (its root), so "add an op to a slot" is one knob, not two params.
|
|
31
|
+
* @param {object} a
|
|
32
|
+
* @param {object} b
|
|
33
|
+
* @returns {string[]} sorted changed paths, dot-notation with array indices
|
|
34
|
+
*/
|
|
35
|
+
export function diffPaths(a: object, b: object): string[];
|
|
36
|
+
export const LOOP_SHAPES: string[];
|
|
37
|
+
export const SLOTS: string[];
|
|
38
|
+
export const VERBS: string[];
|