bareloop 0.5.0 → 0.6.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.
@@ -0,0 +1,262 @@
1
+ /**
2
+ * The strategy suffix for ONE worker's grant: every sentence that survives names
3
+ * only tools this worker can actually call. Reproduces the exported paragraphs
4
+ * exactly on a full grant.
5
+ * @param {string[]} granted the step's granted VERBS (the menu IS the grant, 2b)
6
+ * @returns {string} the system-prompt suffix, '' when nothing is granted
7
+ */
8
+ export function strategyFor(granted: string[]): string;
9
+ /**
10
+ * The retrieval pair (F19). `shell_read` cannot seek — it starts at byte zero — so a
11
+ * pointer at a symbol was inert and the worker paged whole files to reach one function.
12
+ * `ctx_recall` hands back a POINTER (no body: a search index returns pointers, and bodies
13
+ * on every hit rebuild the bloat — measured, a 5-hit recall dumps ~15k tokens unbidden);
14
+ * `ctx_get` trades that pointer for ONE chunk. A chunk starts at its doc-comment, so the
15
+ * body arrives WITH the docstring that says what it was supposed to do.
16
+ * @param {any} lc the run's LiteCtx (rooted at workdir)
17
+ * @param {string} workdir
18
+ * @param {(type: string, data: object) => void} emit the spine — a retrieval verb whose
19
+ * RESULT is invisible cannot be judged: a `ctx_get` that silently reds (stale pointer,
20
+ * bad range) looks exactly like one that worked, and the worker's fallback to a
21
+ * whole-file read looks like a free choice instead of a forced one.
22
+ */
23
+ export function createCtxTools(lc: any, workdir: string, emit: (type: string, data: object) => void): ({
24
+ name: string;
25
+ description: string;
26
+ parameters: {
27
+ type: string;
28
+ properties: {
29
+ query: {
30
+ type: string;
31
+ description: string;
32
+ };
33
+ n: {
34
+ type: string;
35
+ description: string;
36
+ };
37
+ path?: undefined;
38
+ startLine?: undefined;
39
+ endLine?: undefined;
40
+ symbol?: undefined;
41
+ id?: undefined;
42
+ text?: undefined;
43
+ };
44
+ required: string[];
45
+ };
46
+ execute: ({ query, n }: {
47
+ query: string;
48
+ n?: number;
49
+ }) => Promise<any>;
50
+ } | {
51
+ name: string;
52
+ description: string;
53
+ parameters: {
54
+ type: string;
55
+ properties: {
56
+ path: {
57
+ type: string;
58
+ description: string;
59
+ };
60
+ startLine: {
61
+ type: string;
62
+ description: string;
63
+ };
64
+ endLine: {
65
+ type: string;
66
+ description: string;
67
+ };
68
+ query?: undefined;
69
+ n?: undefined;
70
+ symbol?: undefined;
71
+ id?: undefined;
72
+ text?: undefined;
73
+ };
74
+ required: string[];
75
+ };
76
+ execute: ({ path: p, startLine, endLine }: {
77
+ path: string;
78
+ startLine: number;
79
+ endLine: number;
80
+ }) => Promise<any>;
81
+ } | {
82
+ name: string;
83
+ description: string;
84
+ parameters: {
85
+ type: string;
86
+ properties: {
87
+ symbol: {
88
+ type: string;
89
+ description: string;
90
+ };
91
+ query?: undefined;
92
+ n?: undefined;
93
+ path?: undefined;
94
+ startLine?: undefined;
95
+ endLine?: undefined;
96
+ id?: undefined;
97
+ text?: undefined;
98
+ };
99
+ required: string[];
100
+ };
101
+ execute: (args: any) => Promise<string>;
102
+ } | {
103
+ name: string;
104
+ description: string;
105
+ parameters: {
106
+ type: string;
107
+ properties: {
108
+ path: {
109
+ type: string;
110
+ description: string;
111
+ };
112
+ query?: undefined;
113
+ n?: undefined;
114
+ startLine?: undefined;
115
+ endLine?: undefined;
116
+ symbol?: undefined;
117
+ id?: undefined;
118
+ text?: undefined;
119
+ };
120
+ required: string[];
121
+ };
122
+ execute: (args: any) => Promise<string>;
123
+ } | {
124
+ name: string;
125
+ description: string;
126
+ parameters: {
127
+ type: string;
128
+ properties: {
129
+ query?: undefined;
130
+ n?: undefined;
131
+ path?: undefined;
132
+ startLine?: undefined;
133
+ endLine?: undefined;
134
+ symbol?: undefined;
135
+ id?: undefined;
136
+ text?: undefined;
137
+ };
138
+ required?: undefined;
139
+ };
140
+ execute: (args: any) => Promise<string>;
141
+ } | {
142
+ name: string;
143
+ description: string;
144
+ parameters: {
145
+ type: string;
146
+ properties: {
147
+ path: {
148
+ type: string;
149
+ description: string;
150
+ };
151
+ symbol: {
152
+ type: string;
153
+ description: string;
154
+ };
155
+ query?: undefined;
156
+ n?: undefined;
157
+ startLine?: undefined;
158
+ endLine?: undefined;
159
+ id?: undefined;
160
+ text?: undefined;
161
+ };
162
+ required: string[];
163
+ };
164
+ execute: (args: any) => Promise<string>;
165
+ } | {
166
+ name: string;
167
+ description: string;
168
+ parameters: {
169
+ type: string;
170
+ properties: {
171
+ id: {
172
+ type: string;
173
+ description: string;
174
+ };
175
+ text: {
176
+ type: string;
177
+ description: string;
178
+ };
179
+ query?: undefined;
180
+ n?: undefined;
181
+ path?: undefined;
182
+ startLine?: undefined;
183
+ endLine?: undefined;
184
+ symbol?: undefined;
185
+ };
186
+ required: string[];
187
+ };
188
+ execute: (args: any) => Promise<string>;
189
+ } | {
190
+ name: string;
191
+ description: string;
192
+ parameters: {
193
+ type: string;
194
+ properties: {
195
+ id: {
196
+ type: string;
197
+ description: string;
198
+ };
199
+ query?: undefined;
200
+ n?: undefined;
201
+ path?: undefined;
202
+ startLine?: undefined;
203
+ endLine?: undefined;
204
+ symbol?: undefined;
205
+ text?: undefined;
206
+ };
207
+ required: string[];
208
+ };
209
+ execute: (args: any) => Promise<string>;
210
+ })[];
211
+ export const PERSONA_TOOLS: string;
212
+ export const RETRIEVAL_STRATEGY: string;
213
+ export const EDIT_STRATEGY: string;
214
+ export const TOOL_BY_VERB: Readonly<{
215
+ read: "shell_read";
216
+ grep: "shell_grep";
217
+ write: "shell_write";
218
+ edit: "shell_edit";
219
+ recall: "ctx_recall";
220
+ get: "ctx_get";
221
+ impact: "ctx_impact";
222
+ related: "ctx_related";
223
+ recent: "ctx_recent";
224
+ compress: "ctx_compress";
225
+ peek: "ctx_peek";
226
+ stash: "ctx_stash";
227
+ remember: "ctx_remember";
228
+ forget: "ctx_forget";
229
+ }>;
230
+ export const ISOLATE_MAX_BYTES: 65536;
231
+ export const CTX_TOOLS: readonly string[];
232
+ export const COMPONENT_STRATEGIES: Readonly<{
233
+ select: string;
234
+ compress: string;
235
+ isolate: string;
236
+ }>;
237
+ export function toolAction(name: string, args: any, workdir?: string): {
238
+ type: string;
239
+ path: string;
240
+ args: {
241
+ bytes: number;
242
+ tool?: undefined;
243
+ };
244
+ } | {
245
+ type: string;
246
+ path: string;
247
+ args: {
248
+ tool: string;
249
+ bytes?: undefined;
250
+ };
251
+ } | {
252
+ type: string;
253
+ args: any;
254
+ path?: undefined;
255
+ };
256
+ /**
257
+ * litectx recall hit — body present only with `{body: true}`
258
+ */
259
+ export type RecallHit = {
260
+ body?: string | null;
261
+ text?: string | null;
262
+ };
@@ -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
- * (seven copies of this expression lived in scripts/ before it landed here).
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
- }