bareloop 0.5.1 → 0.7.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 +1129 -1
- package/README.md +40 -13
- package/bareloop.context.md +799 -245
- package/package.json +3 -3
- package/src/bridges.js +571 -0
- package/src/clock.js +233 -0
- package/src/exits.js +8 -2
- package/src/index.js +52 -17
- package/src/job.js +231 -108
- package/src/ladder.js +196 -0
- package/src/ledger.js +55 -3
- package/src/plan.js +334 -20
- package/src/planrun.js +1519 -134
- package/src/ralph.js +373 -34
- package/src/reuse.js +1602 -0
- package/src/root.js +32 -12
- package/src/run.js +129 -391
- package/src/selection.js +160 -0
- package/src/spine.js +7 -2
- package/src/stall.js +252 -0
- package/src/tools.js +611 -0
- package/src/trend.js +405 -0
- package/src/validate.js +35 -250
- package/types/bridges.d.ts +198 -0
- package/types/clock.d.ts +131 -0
- package/types/index.d.ts +7 -6
- package/types/job.d.ts +83 -8
- package/types/ladder.d.ts +86 -0
- package/types/ledger.d.ts +3 -1
- package/types/plan.d.ts +86 -7
- package/types/planrun.d.ts +142 -5
- package/types/ralph.d.ts +143 -7
- package/types/reuse.d.ts +388 -0
- package/types/root.d.ts +10 -2
- package/types/run.d.ts +56 -17
- package/types/selection.d.ts +46 -0
- package/types/spine.d.ts +8 -1
- package/types/stall.d.ts +80 -0
- package/types/tools.d.ts +262 -0
- package/types/trend.d.ts +108 -0
- package/types/validate.d.ts +21 -38
- package/src/extract.js +0 -103
- package/src/interpret.js +0 -887
- package/types/extract.d.ts +0 -25
- package/types/interpret.d.ts +0 -201
package/types/validate.d.ts
CHANGED
|
@@ -29,54 +29,37 @@ export function isNonEmptyString(v: unknown): v is string;
|
|
|
29
29
|
* Scan a RAW text stream (a spine file, a close's output, a transcript) for
|
|
30
30
|
* known secret shapes and return the literal matches. The ONE spelling of the
|
|
31
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
|
-
*
|
|
32
|
+
* hand-rolled copy that misses a shape is a leak on the very output it guards.
|
|
33
|
+
* The dedup is now COMPLETE and the count is grepped, not remembered: all 11
|
|
34
|
+
* call sites in scripts/ go through this function, and zero hand-rolled copies
|
|
35
|
+
* of the expression remain anywhere in the repo. (F40 landed the helper and
|
|
36
|
+
* converted 2 of 7 sites; the 5 it left alone plus 4 that accreted afterwards
|
|
37
|
+
* were converted in one sweep, byte-identical behaviour — measured equivalent
|
|
38
|
+
* on 900 spiked real spines, 0 divergences.)
|
|
34
39
|
* `sweepSecretLiterals` is the config-tree twin — same shapes, tree walk.
|
|
35
40
|
* Never throws; returns [] for a missing stream.
|
|
36
41
|
* @param {unknown} raw
|
|
37
42
|
* @returns {string[]} every match, in stream order per pattern
|
|
38
43
|
*/
|
|
39
44
|
export function scanSecrets(raw: unknown): string[];
|
|
45
|
+
/**
|
|
46
|
+
* Mask known secret shapes in a text. The REDACTION twin of `scanSecrets` and
|
|
47
|
+
* the ONE spelling of it: detection and redaction read the same inventory, so a
|
|
48
|
+
* shape the validator reds can never be a shape the redactor passes (a divergence
|
|
49
|
+
* there is a leak on the very output the validator was guarding). Housed beside
|
|
50
|
+
* the inventory rather than at each consumer for the same reason `scanSecrets`
|
|
51
|
+
* is — a hand-rolled copy is how the two drift.
|
|
52
|
+
* Consumers: the plan flow's spine/close/prompt `scrub`, and the isolate verbs,
|
|
53
|
+
* which write model-authored text into `<workdir>/.litectx` — a file INSIDE the
|
|
54
|
+
* tree that outlives the run and reads back through ctx_recall.
|
|
55
|
+
* @param {unknown} raw
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
export function redactSecrets(raw: unknown): string;
|
|
40
59
|
/**
|
|
41
60
|
* Red every string in a config tree that carries a known secret-token shape.
|
|
42
61
|
* @param {any} root
|
|
43
62
|
* @param {(code: string, path: string, detail?: string) => void} red
|
|
44
63
|
*/
|
|
45
64
|
export function sweepSecretLiterals(root: any, red: (code: string, path: string, detail?: string) => void): void;
|
|
46
|
-
/**
|
|
47
|
-
* Validate a workflow config against schema v1. Returns the parsed config on
|
|
48
|
-
* ok (single parse — callers never re-parse; null on any red).
|
|
49
|
-
* @param {object|string} input parsed config, or raw JSON text (parse failures are a red)
|
|
50
|
-
* @param {{ shellCapUsd?: number, jobWriteScope?: string[] }} [opts] the shell's
|
|
51
|
-
* ceilings — a config may tighten either, never exceed. `shellCapUsd`: at N2+
|
|
52
|
-
* the shell passes min(shell cap, job budgetUsd) — the ceiling chain
|
|
53
|
-
* workflow ≤ job ≤ shell. `jobWriteScope`: the job spec's outer fence
|
|
54
|
-
* (operator law, job-v1) — every workflow scope must fit inside it.
|
|
55
|
-
* @returns {{ ok: boolean, reds: Array<{code: string, path: string, detail?: string}>, config: object|null }}
|
|
56
|
-
*/
|
|
57
|
-
export function validateConfig(input: object | string, { shellCapUsd, jobWriteScope }?: {
|
|
58
|
-
shellCapUsd?: number;
|
|
59
|
-
jobWriteScope?: string[];
|
|
60
|
-
}): {
|
|
61
|
-
ok: boolean;
|
|
62
|
-
reds: Array<{
|
|
63
|
-
code: string;
|
|
64
|
-
path: string;
|
|
65
|
-
detail?: string;
|
|
66
|
-
}>;
|
|
67
|
-
config: object | null;
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* Changed JSON paths between two configs — the one-knob mutation checker
|
|
71
|
-
* (a legal mutant has exactly one). A subtree present on only one side counts
|
|
72
|
-
* as ONE path (its root), so "add an op to a slot" is one knob, not two params.
|
|
73
|
-
* @param {object} a
|
|
74
|
-
* @param {object} b
|
|
75
|
-
* @returns {string[]} sorted changed paths, dot-notation with array indices
|
|
76
|
-
*/
|
|
77
|
-
export function diffPaths(a: object, b: object): string[];
|
|
78
|
-
export const LOOP_SHAPES: readonly string[];
|
|
79
|
-
export const SLOTS: readonly string[];
|
|
80
|
-
export const VERBS: readonly string[];
|
|
81
|
-
export const REMEMBER_KINDS: string[];
|
|
82
65
|
export const SECRET_PATTERNS: RegExp[];
|
package/src/extract.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
// The rules extractor — the lineage's inheritance representation. One sealed
|
|
2
|
-
// LLM call after a green run distills/updates the lineage's rules list from
|
|
3
|
-
// LEDGER FACTS only: the green config, the prior rules, and the revision diff
|
|
4
|
-
// if the run recovered mid-run (the diff is the lesson — failure-transition
|
|
5
|
-
// evidence, free in the event log). It never sees the close, the tests, or the
|
|
6
|
-
// worker's code: rules describe the WORKFLOW, and showing the judge would open
|
|
7
|
-
// the config-level fit-to-pass surface (PRD §2).
|
|
8
|
-
//
|
|
9
|
-
// N3 extends this with the contrast-bit rule (design law #3: verdict admits,
|
|
10
|
-
// contrast attributes — the extractor may claim a knob only with ≥1
|
|
11
|
-
// ledger-counted contrast bit; adaptlearn F16/F18 showed bare greens lose
|
|
12
|
-
// working features). At N0 the extractor is the adaptlearn-proven distiller.
|
|
13
|
-
//
|
|
14
|
-
// Honesty invariants:
|
|
15
|
-
// - One shot, no retry: malformed output is a red as DATA — the caller keeps
|
|
16
|
-
// the lineage's prior rules; there is never a silent empty inheritance.
|
|
17
|
-
// - The ≤MAX_RULES/≤MAX_RULE_CHARS bound is stated to the model but ENFORCED
|
|
18
|
-
// mechanically post-call, and enforcement rejects whole — never trims
|
|
19
|
-
// (rejecting a half-applicable output beats silently part-applying it).
|
|
20
|
-
// - Extractor spend is carried in costUsd and lands on the run's cost line —
|
|
21
|
-
// a representation whose upkeep rides free corrupts the ranking (design
|
|
22
|
-
// law #4: green gates, cost ranks).
|
|
23
|
-
|
|
24
|
-
import { createRequire } from 'node:module';
|
|
25
|
-
|
|
26
|
-
import { extractArtifact, priceOf } from './text.js';
|
|
27
|
-
|
|
28
|
-
const require = createRequire(import.meta.url);
|
|
29
|
-
const { Loop } = require('bare-agent');
|
|
30
|
-
|
|
31
|
-
export const MAX_RULES = 5;
|
|
32
|
-
export const MAX_RULE_CHARS = 200;
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Distill/update a lineage's rules from one green run's ledger facts.
|
|
36
|
-
* @param {object} opts
|
|
37
|
-
* @param {object} opts.config the config that went green
|
|
38
|
-
* @param {object} opts.provider a bareagent provider — SHELL-owned, sealed
|
|
39
|
-
* @param {string[]|null} opts.priorRules the lineage's current rules, if any
|
|
40
|
-
* @param {string[]} [opts.revisionDiff] changed config paths, when the run recovered mid-run
|
|
41
|
-
* @returns {Promise<{rules: string[]|null, valid: boolean, reds: Array<object>, costUsd: number|null, raw: string}>}
|
|
42
|
-
* costUsd is null when the spend could not be priced — the explicit-unknown
|
|
43
|
-
* signal (F6: unpriced is never free); callers must not coerce it to 0
|
|
44
|
-
*/
|
|
45
|
-
export async function extractRules({ config, provider, priorRules, revisionDiff }) {
|
|
46
|
-
const loop = new Loop({ provider, system: 'You emit exactly one JSON document and nothing else.' });
|
|
47
|
-
const prompt = `An automated coding workflow just completed a run that PASSED its hidden judgement.
|
|
48
|
-
You maintain this workflow lineage's inherited rules: short, general lessons about how to
|
|
49
|
-
configure the workflow for this task family. You never see the tasks' tests or code — only
|
|
50
|
-
workflow facts.
|
|
51
|
-
|
|
52
|
-
The config that went green:
|
|
53
|
-
${JSON.stringify(config, null, 2)}
|
|
54
|
-
${revisionDiff && revisionDiff.length > 0 ? `
|
|
55
|
-
This run stalled and recovered after revising these config paths mid-run (the revision that
|
|
56
|
-
turned red into green — strong evidence about what mattered):
|
|
57
|
-
${revisionDiff.join(', ')}
|
|
58
|
-
` : ''}${priorRules && priorRules.length > 0 ? `
|
|
59
|
-
The lineage's current rules — revise them: keep what still holds, drop what this run
|
|
60
|
-
contradicts, add at most what this run actually evidences:
|
|
61
|
-
${JSON.stringify(priorRules, null, 2)}
|
|
62
|
-
` : `
|
|
63
|
-
The lineage has no rules yet. Write only what this single run actually evidences.
|
|
64
|
-
`}
|
|
65
|
-
Output ONLY a JSON array of strings: at most ${MAX_RULES} rules, each at most ${MAX_RULE_CHARS}
|
|
66
|
-
characters. No markdown fences, no commentary.`;
|
|
67
|
-
|
|
68
|
-
// Never throw (the module contract): a provider blip during post-green
|
|
69
|
-
// extraction must not lose the green run's bookkeeping — it degrades to a
|
|
70
|
-
// red as data and the caller keeps the lineage's prior rules. Loop defaults
|
|
71
|
-
// throwOnError: true, so a transient 500 WOULD reject without this.
|
|
72
|
-
let r;
|
|
73
|
-
try {
|
|
74
|
-
r = await loop.run([{ role: 'user', content: prompt }]);
|
|
75
|
-
} catch (e) {
|
|
76
|
-
// a transport throw means the spend is UNKNOWN, not zero (F6)
|
|
77
|
-
return { rules: null, valid: false, reds: [{ code: 'provider-error', detail: String(/** @type {Error} */ (e).message ?? e) }], costUsd: null, raw: '' };
|
|
78
|
-
}
|
|
79
|
-
const { costUsd } = priceOf(r); // the ONE honest-null cost read (F6)
|
|
80
|
-
const ex = extractArtifact(r.text ?? '');
|
|
81
|
-
const raw = ex.code ?? '';
|
|
82
|
-
if (r.error) {
|
|
83
|
-
return { rules: null, valid: false, reds: [{ code: 'provider-error', detail: String(r.error) }], costUsd, raw };
|
|
84
|
-
}
|
|
85
|
-
/** @type {(code: string, detail: string) => {rules: null, valid: false, reds: Array<object>, costUsd: number|null, raw: string}} */
|
|
86
|
-
const red = (code, detail) => ({ rules: null, valid: false, reds: [{ code, detail }], costUsd, raw });
|
|
87
|
-
// ONE-parser doctrine: extractArtifact's red field is part of its contract —
|
|
88
|
-
// dropping it here buried 'empty response' under JSON.parse's generic
|
|
89
|
-
// "Unexpected end of JSON input" (interpret.js consumes ex.red the same way)
|
|
90
|
-
if (ex.red) return red('artifact-red', ex.red);
|
|
91
|
-
|
|
92
|
-
let rules;
|
|
93
|
-
try { rules = JSON.parse(raw); } catch (e) {
|
|
94
|
-
return red('parse-error', String(/** @type {Error} */ (e).message));
|
|
95
|
-
}
|
|
96
|
-
if (!Array.isArray(rules) || rules.length === 0 || !rules.every((x) => typeof x === 'string')) {
|
|
97
|
-
return red('rules-shape', 'expected a non-empty JSON array of strings');
|
|
98
|
-
}
|
|
99
|
-
if (rules.length > MAX_RULES || rules.some((x) => x.length > MAX_RULE_CHARS)) {
|
|
100
|
-
return red('rules-bound', `max ${MAX_RULES} rules of ${MAX_RULE_CHARS} chars — rejected whole, never trimmed`);
|
|
101
|
-
}
|
|
102
|
-
return { rules, valid: true, reds: [], costUsd, raw };
|
|
103
|
-
}
|