bareloop 0.8.0 → 0.9.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 +421 -0
- package/README.md +10 -3
- package/bareloop.context.md +198 -17
- package/package.json +1 -1
- package/src/authorflow.js +1280 -0
- package/src/authoring.js +1296 -0
- package/src/authorjob.js +793 -0
- package/src/authorscout.js +706 -0
- package/src/bridges.js +2 -2
- package/src/declaredclose.js +519 -0
- package/src/index.js +72 -0
- package/src/job.js +48 -4
- package/src/kinds.js +1354 -0
- package/src/ledger.js +13 -0
- package/src/plan.js +26 -1
- package/src/planrun.js +239 -16
- package/src/ralph.js +19 -4
- package/src/reuse.js +68 -7
- package/src/root.js +8 -1
- package/src/run.js +10 -3
- package/src/text.js +120 -0
- package/src/workbranch.js +261 -0
- package/types/authorflow.d.ts +720 -0
- package/types/authoring.d.ts +447 -0
- package/types/authorjob.d.ts +241 -0
- package/types/authorscout.d.ts +307 -0
- package/types/declaredclose.d.ts +174 -0
- package/types/index.d.ts +9 -1
- package/types/job.d.ts +1 -0
- package/types/kinds.d.ts +343 -0
- package/types/plan.d.ts +18 -0
- package/types/planrun.d.ts +10 -3
- package/types/ralph.d.ts +1 -0
- package/types/run.d.ts +10 -2
- package/types/text.d.ts +84 -0
- package/types/workbranch.d.ts +102 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The spine events a refusal produces — returned rather than emitted, because
|
|
3
|
+
* this module owns no spine (the runner shell does, exactly as with `runJob`).
|
|
4
|
+
*
|
|
5
|
+
* The `job-red` carries the structured `verb`/`lib`, which is the channel
|
|
6
|
+
* `classifyIncidents` already reads; the escalation carries the human's levers
|
|
7
|
+
* and is EXCLUDED from classification so the same demand is not counted twice.
|
|
8
|
+
* @param {Refusal} refusal
|
|
9
|
+
* @returns {{type: string, [k: string]: any}[]}
|
|
10
|
+
*/
|
|
11
|
+
export function refusalEvents(refusal: Refusal): {
|
|
12
|
+
type: string;
|
|
13
|
+
[k: string]: any;
|
|
14
|
+
}[];
|
|
15
|
+
/**
|
|
16
|
+
* Read the picked verdict class and that class's own frozen answers. Pure: no
|
|
17
|
+
* model, no repository, no clock.
|
|
18
|
+
*
|
|
19
|
+
* Four outcomes, and the middle two are the ones that matter:
|
|
20
|
+
* - `ok` — a class v1 builds, every answer present, a repository named;
|
|
21
|
+
* - a CLASS REFUSAL — the user picked soft-green or hitl. Counted demand for a
|
|
22
|
+
* VERDICT CLASS, refused at ADMISSION, before that class's questions run
|
|
23
|
+
* (they are named but locked, and asking them would be an interview for a
|
|
24
|
+
* job nothing here can close);
|
|
25
|
+
* - a REPO REFUSAL — a green pick with nothing deterministic to measure
|
|
26
|
+
* against. Counted demand too;
|
|
27
|
+
* - plain `reds` — the interview is not finished, or the radio was not set.
|
|
28
|
+
* NOT demand: an incomplete form is not a user asking for a capability, and
|
|
29
|
+
* an unknown class is a typo, never a request (the same split `src/job.js`
|
|
30
|
+
* makes between a locked verdict and an invalid one).
|
|
31
|
+
*
|
|
32
|
+
* @param {{answers: Record<string|number, any>, verdictType?: string|null,
|
|
33
|
+
* repoPath?: string|null, questions?: Record<string|number, string>|null}} o
|
|
34
|
+
* `verdictType` and `repoPath` are STRUCTURED input from the UI, never parsed
|
|
35
|
+
* out of the prose of an answer: a class is picked off a radio and a path is
|
|
36
|
+
* read off the machine, or neither exists.
|
|
37
|
+
* @returns {{ok: boolean, answers: Record<string|number, string>,
|
|
38
|
+
* verdictType: string|null, repoPath: string|null, refusal: Refusal|null, reds: Red[]}}
|
|
39
|
+
*/
|
|
40
|
+
export function runInterview({ answers, verdictType, repoPath, questions }: {
|
|
41
|
+
answers: Record<string | number, any>;
|
|
42
|
+
verdictType?: string | null;
|
|
43
|
+
repoPath?: string | null;
|
|
44
|
+
questions?: Record<string | number, string> | null;
|
|
45
|
+
}): {
|
|
46
|
+
ok: boolean;
|
|
47
|
+
answers: Record<string | number, string>;
|
|
48
|
+
verdictType: string | null;
|
|
49
|
+
repoPath: string | null;
|
|
50
|
+
refusal: Refusal | null;
|
|
51
|
+
reds: Red[];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The refusal for a user who asks for a LOCKED KIND by name — a judged score or
|
|
55
|
+
* a person's sign-off. Exposed because the tool-mode declaration schema makes a
|
|
56
|
+
* locked kind INEXPRESSIBLE (M3's header states the cost): the demand cannot
|
|
57
|
+
* arrive through the model, so it has to arrive through the interview layer, and
|
|
58
|
+
* this is that layer.
|
|
59
|
+
* @param {string} kind @returns {Refusal}
|
|
60
|
+
*/
|
|
61
|
+
export function refuseLockedKind(kind: string): Refusal;
|
|
62
|
+
/**
|
|
63
|
+
* Interview answers in, a signable close DECLARATION out.
|
|
64
|
+
*
|
|
65
|
+
* The order is the design's own, and every cheap refusal happens before any
|
|
66
|
+
* token: interview → seed → scout → listing → the grounded authoring loop (M3).
|
|
67
|
+
*
|
|
68
|
+
* `scoutFn`, `listingFn` and `seedFn` are seams so a caller can supply a survey
|
|
69
|
+
* it already paid for (the prereg reuses one across arms), and so the whole
|
|
70
|
+
* pipeline is testable without a provider. All three are REAL by default.
|
|
71
|
+
*
|
|
72
|
+
* THE GENRE REFUSAL LIVES HERE NOW (PRD v1.57 §2). D13's interview slot asked
|
|
73
|
+
* the user to confirm a genre; the interview no longer asks, and the refusal
|
|
74
|
+
* moved to the COMPOSER — a job the catalogue cannot measure refuses at this
|
|
75
|
+
* layer, on the same `request-red` path, still counted as demand. Two mechanical
|
|
76
|
+
* signals reach it and neither is a reading of prose: a language the genre owns
|
|
77
|
+
* no data for (so there is no guard battery to attach), and a LOCKED KIND the
|
|
78
|
+
* model reached for (the catalogue's own counted-demand red, carried up). D13's
|
|
79
|
+
* one-genre-at-a-time law is untouched.
|
|
80
|
+
*
|
|
81
|
+
* @param {{answers: Record<string|number, any>, repoPath?: string|null, lang: string,
|
|
82
|
+
* verdictType?: string|null,
|
|
83
|
+
* questions?: Record<string|number, string>|null, generate?: Function, provider?: any,
|
|
84
|
+
* seedRef?: string|null, scout?: any, listing?: any,
|
|
85
|
+
* seedFn?: Function, scoutFn?: Function, listingFn?: Function,
|
|
86
|
+
* authorFn?: Function, authorOpts?: object}} o
|
|
87
|
+
* @returns {Promise<{ok: boolean, refusal: Refusal|null, verdictType: string|null,
|
|
88
|
+
* closeDecl: any, seedRef: string|null, authoring: any, reds: Red[], stop: string|null,
|
|
89
|
+
* cost: any, interview: any}>}
|
|
90
|
+
*/
|
|
91
|
+
export function authorCloseForJob({ answers, repoPath, lang, verdictType, questions, generate, provider, seedRef, scout, listing, seedFn, scoutFn, listingFn, authorFn, authorOpts, }: {
|
|
92
|
+
answers: Record<string | number, any>;
|
|
93
|
+
repoPath?: string | null;
|
|
94
|
+
lang: string;
|
|
95
|
+
verdictType?: string | null;
|
|
96
|
+
questions?: Record<string | number, string> | null;
|
|
97
|
+
generate?: Function;
|
|
98
|
+
provider?: any;
|
|
99
|
+
seedRef?: string | null;
|
|
100
|
+
scout?: any;
|
|
101
|
+
listing?: any;
|
|
102
|
+
seedFn?: Function;
|
|
103
|
+
scoutFn?: Function;
|
|
104
|
+
listingFn?: Function;
|
|
105
|
+
authorFn?: Function;
|
|
106
|
+
authorOpts?: object;
|
|
107
|
+
}): Promise<{
|
|
108
|
+
ok: boolean;
|
|
109
|
+
refusal: Refusal | null;
|
|
110
|
+
verdictType: string | null;
|
|
111
|
+
closeDecl: any;
|
|
112
|
+
seedRef: string | null;
|
|
113
|
+
authoring: any;
|
|
114
|
+
reds: Red[];
|
|
115
|
+
stop: string | null;
|
|
116
|
+
cost: any;
|
|
117
|
+
interview: any;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Fold an authored close into the OPERATOR's own half of the spec.
|
|
121
|
+
*
|
|
122
|
+
* The two halves never mix: budgets, the fence, cadence, escalation and the
|
|
123
|
+
* provider are operator input and are not authored by anything here (the
|
|
124
|
+
* permanent hard line). What this adds is the close, the derived verdict class,
|
|
125
|
+
* and nothing else.
|
|
126
|
+
*
|
|
127
|
+
* The GOAL is passed through, not generated. F87 requires the goal to state
|
|
128
|
+
* everything the close judges, and §3 of the design record says that becomes
|
|
129
|
+
* structural once the goal RENDERS from the same answers — that rendering is UI
|
|
130
|
+
* work (PRD v1.51 §5, "lands with the close-authoring interview UI") and is
|
|
131
|
+
* deliberately not invented here.
|
|
132
|
+
*
|
|
133
|
+
* A draft that ALREADY carries the authored half is REFUSED, not merged and not
|
|
134
|
+
* overwritten. `src/job.js` states the rule for the close in its own words — two
|
|
135
|
+
* closes are two arbiters, and "picking one silently is how the signed artefact
|
|
136
|
+
* stops meaning what the signer read" — and the same holds one step earlier and
|
|
137
|
+
* for the class: this fold used to drop a draft's `close` on the floor and stamp
|
|
138
|
+
* its own `verdictType` over the operator's, so a person who typed either would
|
|
139
|
+
* read back a signed spec that never contained what they wrote. Refusing by name
|
|
140
|
+
* costs the legitimate path nothing (a draft is the operator's half BY
|
|
141
|
+
* DEFINITION, and nothing in the pipeline builds one carrying these) and it is
|
|
142
|
+
* the same direction every other gate here takes: a mismatch a human cannot see
|
|
143
|
+
* is worse than a stop a human can read.
|
|
144
|
+
* @param {any} specDraft the operator's half (everything except close/closeDecl/verdictType)
|
|
145
|
+
* @param {{closeDecl: any, verdictType: string}} authored
|
|
146
|
+
* @throws {Error} when the draft already carries close, closeDecl or verdictType
|
|
147
|
+
*/
|
|
148
|
+
export function assembleSpec(specDraft: any, { closeDecl, verdictType }: {
|
|
149
|
+
closeDecl: any;
|
|
150
|
+
verdictType: string;
|
|
151
|
+
}): any;
|
|
152
|
+
/**
|
|
153
|
+
* D9, in code: *nothing JUDGES the close; three mechanical gates plus a
|
|
154
|
+
* signature.* No LLM validates another LLM's close, here or anywhere.
|
|
155
|
+
*
|
|
156
|
+
* 1. THE DECLARATION VALIDATOR — the spec's own gate plus M2's, run GROUNDED
|
|
157
|
+
* against the real seed listing (the job validator defers that half).
|
|
158
|
+
* 2. THE CLOSE PRECHECK — every stage runs against the real patient. A stage
|
|
159
|
+
* that cannot run is `broken-close`, and it is a CASUALTY, never a red.
|
|
160
|
+
* 3. THE SEED-VERDICT READ — every stage, offered or not, run at the seed. Which
|
|
161
|
+
* are RED (the work) and which are GREEN (the guards) is shown to the user.
|
|
162
|
+
* A close with NO work stage red at seed has nothing to do and is refused
|
|
163
|
+
* decision-ready — gate-2 round 2's arm B died exactly there, on a mypy that
|
|
164
|
+
* scanned nothing and therefore read clean.
|
|
165
|
+
*
|
|
166
|
+
* Gates 2 and 3 share ONE execution, and that is stated rather than hidden: at
|
|
167
|
+
* job creation the tree IS the seed, so "does every stage run against the real
|
|
168
|
+
* patient" and "what does every stage say at the seed" are two readings of the
|
|
169
|
+
* same pass. `seedRead` runs EVERY stage rather than first-red-wins, which is
|
|
170
|
+
* D12's own named build item — a stage that never ran mints no baseline, and
|
|
171
|
+
* every shipped close opens with a guard that is red at its own seed.
|
|
172
|
+
*
|
|
173
|
+
* IT NEVER SIGNS. The approvals array and the human's word are unchanged; what
|
|
174
|
+
* comes back is the resolved spec, its hash, and the evidence to sign against.
|
|
175
|
+
*
|
|
176
|
+
* @param {{spec: any, workdir: string, seedRef?: string|null, shellCapUsd?: number,
|
|
177
|
+
* timeoutMs?: number, seedFn?: Function, listingFn?: Function, seedReadFn?: Function}} o
|
|
178
|
+
* @returns {Promise<{ok: boolean, specHash: string|null, seedRef: string|null,
|
|
179
|
+
* gates: any, work: any[], guards: any[], stops: any[], reds: Red[], refusal: Refusal|null}>}
|
|
180
|
+
*/
|
|
181
|
+
export function prepareSigning({ spec, workdir, seedRef, shellCapUsd, timeoutMs, seedFn, listingFn, seedReadFn, }: {
|
|
182
|
+
spec: any;
|
|
183
|
+
workdir: string;
|
|
184
|
+
seedRef?: string | null;
|
|
185
|
+
shellCapUsd?: number;
|
|
186
|
+
timeoutMs?: number;
|
|
187
|
+
seedFn?: Function;
|
|
188
|
+
listingFn?: Function;
|
|
189
|
+
seedReadFn?: Function;
|
|
190
|
+
}): Promise<{
|
|
191
|
+
ok: boolean;
|
|
192
|
+
specHash: string | null;
|
|
193
|
+
seedRef: string | null;
|
|
194
|
+
gates: any;
|
|
195
|
+
work: any[];
|
|
196
|
+
guards: any[];
|
|
197
|
+
stops: any[];
|
|
198
|
+
reds: Red[];
|
|
199
|
+
refusal: Refusal | null;
|
|
200
|
+
}>;
|
|
201
|
+
/** @typedef {{code: string, path: string, detail: string, [k: string]: any}} Red */
|
|
202
|
+
/** @typedef {{kind: 'request-red'|'decision-ready', verb: string|null, path: string,
|
|
203
|
+
* detail: string, options: string[], red: Red|null}} Refusal */
|
|
204
|
+
/** the one genre v1 admits (D13). The interview no longer ASKS about it — the
|
|
205
|
+
* catalogue is one genre wide and the composer is where that refuses — but the
|
|
206
|
+
* signed `closeDecl` still names the genre it was authored under. */
|
|
207
|
+
export const GENRE: "TYPES";
|
|
208
|
+
/** The territory every refusal here lands against. bareloop's OWN catalogue —
|
|
209
|
+
* the genre menu, the verdict menu, the kind menu — never a bare-suite package.
|
|
210
|
+
* Stamped at the emit site (the typed-lib rule). */
|
|
211
|
+
export const REFUSAL_LIB: "bareloop";
|
|
212
|
+
/** the escalation category a refusal escalates under. It is DELIBERATELY in the
|
|
213
|
+
* ledger's executable excluded set: the demand is already counted once as a
|
|
214
|
+
* `request-red`, and counting the escalation too would inflate the very number
|
|
215
|
+
* the admission path exists to measure. */
|
|
216
|
+
export const REFUSAL_CATEGORY: "close-unauthorable";
|
|
217
|
+
/** The fields `assembleSpec` writes, and therefore the fields a DRAFT must not
|
|
218
|
+
* carry. Named as data so the refusal and the fold can never disagree about
|
|
219
|
+
* which half of the spec is which. */
|
|
220
|
+
export const AUTHORED_SPEC_FIELDS: readonly string[];
|
|
221
|
+
export type Red = {
|
|
222
|
+
code: string;
|
|
223
|
+
path: string;
|
|
224
|
+
detail: string;
|
|
225
|
+
[k: string]: any;
|
|
226
|
+
};
|
|
227
|
+
export type Refusal = {
|
|
228
|
+
kind: "request-red" | "decision-ready";
|
|
229
|
+
verb: string | null;
|
|
230
|
+
path: string;
|
|
231
|
+
detail: string;
|
|
232
|
+
options: string[];
|
|
233
|
+
red: Red | null;
|
|
234
|
+
};
|
|
235
|
+
import { QUESTION_SETS } from './authorflow.js';
|
|
236
|
+
import { questionsFor } from './authorflow.js';
|
|
237
|
+
import { requiredAnswersFor } from './authorflow.js';
|
|
238
|
+
import { VERDICT_CLASSES } from './authoring.js';
|
|
239
|
+
import { LOCKED_CLASSES } from './authoring.js';
|
|
240
|
+
import { LIVE_CLASSES } from './authoring.js';
|
|
241
|
+
export { QUESTION_SETS, questionsFor, requiredAnswersFor, VERDICT_CLASSES, LOCKED_CLASSES, LIVE_CLASSES };
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The re-ask, when the survey came back MALFORMED. It names what failed
|
|
3
|
+
* MECHANICALLY — the parse error and its position — which is the gap genre that
|
|
4
|
+
* converts (F38/F39), and it says explicitly that the FINDINGS are not in
|
|
5
|
+
* question, only their form.
|
|
6
|
+
*
|
|
7
|
+
* There is NO JSON repair anywhere behind this and there never will be: a
|
|
8
|
+
* repairer decides what the model meant to say and writes it down as though the
|
|
9
|
+
* model had said it. That is a dishonest instrument in the one artefact whose
|
|
10
|
+
* entire job is to be honest about what the repository contains.
|
|
11
|
+
* @param {string} reason the classifier's own mechanical reason
|
|
12
|
+
*/
|
|
13
|
+
export function scoutReaskTurn(reason: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* The survey brief. Deliberately NEUTRAL about what anyone will do with the
|
|
16
|
+
* facts: this scout is not allowed to have an opinion about the work, because
|
|
17
|
+
* the authoring call is the only thing that composes a close (D11).
|
|
18
|
+
* @param {string} workdir the ABSOLUTE repository root
|
|
19
|
+
*/
|
|
20
|
+
export function scoutPrompt(workdir: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{bytes: number, rounds: number, bounded: boolean, recovered: boolean, error: string|null,
|
|
23
|
+
* attempts?: number, attemptsAllowed?: number}} SurveyMeta
|
|
24
|
+
* @typedef {{state: 'PRESENT'|'ABSENT', facts: any, reason: string|null, cause: string|null, meta: SurveyMeta}} Survey
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* THE F59 CONTRACT, IN CODE. Six routes to ABSENT, one route to PRESENT — and
|
|
28
|
+
* the LAST of them is the whole point of the finding: a parsed `{}` is the
|
|
29
|
+
* scout FAILING, never "no special facts are needed".
|
|
30
|
+
*
|
|
31
|
+
* Every ABSENT route also carries a TYPED `cause` beside its prose reason. The
|
|
32
|
+
* prose is for the operator; the type is what the retry gate reads, so widening
|
|
33
|
+
* that gate requires editing the enum rather than a regular expression over a
|
|
34
|
+
* sentence (the standing rule: hand over the enumerated set, never a rule about
|
|
35
|
+
* prose). `empty` is split OUT of the byte floor for exactly that reason — "the
|
|
36
|
+
* model said nothing" and "the model was cut off mid-sentence" are two findings
|
|
37
|
+
* that used to render as one number.
|
|
38
|
+
* @param {string} blob the scrubbed, capped survey text
|
|
39
|
+
* @param {SurveyMeta} meta
|
|
40
|
+
* @returns {Survey}
|
|
41
|
+
*/
|
|
42
|
+
export function classifySurvey(blob: string, meta: SurveyMeta): Survey;
|
|
43
|
+
/**
|
|
44
|
+
* The DEFAULT surveyor: a real bareguard fence plus the granted read-only tools.
|
|
45
|
+
* Read-only TWICE OVER — the menu carries no write verb, and the fence grants no
|
|
46
|
+
* write path — and the arbiter's own books are denied outright, exactly as every
|
|
47
|
+
* worker's are.
|
|
48
|
+
*
|
|
49
|
+
* It is a seam because it is the only part of this module that needs a live
|
|
50
|
+
* process, an index and a gate; the ROUND BOUND and the F59 recovery stay in
|
|
51
|
+
* `runAuthorScout` where they can be proven.
|
|
52
|
+
* @param {{workdir: string, granted: readonly string[], auditPath?: string, ctx?: boolean}} o
|
|
53
|
+
*/
|
|
54
|
+
export function defaultSurveyor({ workdir, granted, auditPath, ctx }: {
|
|
55
|
+
workdir: string;
|
|
56
|
+
granted: readonly string[];
|
|
57
|
+
auditPath?: string;
|
|
58
|
+
ctx?: boolean;
|
|
59
|
+
}): Promise<{
|
|
60
|
+
tools: any[];
|
|
61
|
+
policy: Function;
|
|
62
|
+
onLlmResult: Function;
|
|
63
|
+
cleanup: () => Promise<void>;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Run the authoring scout.
|
|
67
|
+
*
|
|
68
|
+
* `createLoop` and `createSurveyor` are the two seams, both REAL by default. The
|
|
69
|
+
* round bound, F59's reserved round, the blob cap, the scrub and the
|
|
70
|
+
* classification all live HERE rather than behind a seam, because those are the
|
|
71
|
+
* behaviours the finding is about.
|
|
72
|
+
*
|
|
73
|
+
* THE ATTEMPT LADDER (hamr, 2026-08-09) sits OUTSIDE all of that, and it fires
|
|
74
|
+
* on the typed malformed class only — see `SCOUT_RETRY_CAUSES` for what is
|
|
75
|
+
* deliberately excluded and why. Attempt 1 is the full survey; a re-ask is
|
|
76
|
+
* TOOLLESS over the conversation the survey already produced, because the model
|
|
77
|
+
* has already READ the repository and only its final emission was unreadable —
|
|
78
|
+
* re-paying the exploration would buy the same facts twice. Every attempt is a
|
|
79
|
+
* paid call, metered under its own label, and every attempt's raw text is
|
|
80
|
+
* persisted so the malformation can be READ later rather than remembered.
|
|
81
|
+
*
|
|
82
|
+
* ONE FALLBACK, stated because the code has it: a retry with NO conversation to
|
|
83
|
+
* re-ask over re-runs the FULL toolable survey instead. The toolless form rests
|
|
84
|
+
* entirely on "the model already read the tree", and an empty transcript is that
|
|
85
|
+
* premise gone — re-asking over nothing would be a bare "say it again" to a model
|
|
86
|
+
* that was never shown the repository. Re-surveying costs the exploration twice;
|
|
87
|
+
* the alternative buys a second sample of noise. It is not reachable through the
|
|
88
|
+
* shipped Loop (whose `msgs` starts from the caller's own messages and is never
|
|
89
|
+
* empty) and it is reachable through the `createLoop` seam, so it is a fail-safe
|
|
90
|
+
* with a test rather than a promise with a comment.
|
|
91
|
+
*
|
|
92
|
+
* @param {{workdir: string, provider?: any,
|
|
93
|
+
* rounds?: number, minBytes?: number, blobMax?: number, maxTokens?: number, ctx?: boolean,
|
|
94
|
+
* attempts?: number,
|
|
95
|
+
* createLoop?: (o: {system: string, policy: any, onLlmResult: any, provider: any}) => any,
|
|
96
|
+
* createSurveyor?: (o: {workdir: string, granted: readonly string[], ctx: boolean}) => Promise<any>}} o
|
|
97
|
+
* @returns {Promise<Survey & {raw: string, raws: ReturnType<typeof scrubRaw>[],
|
|
98
|
+
* calls: {label: string, costUsd: number|null, unpricedRounds: number}[]}>}
|
|
99
|
+
*/
|
|
100
|
+
export function runAuthorScout({ workdir, provider, rounds, minBytes, blobMax, maxTokens, ctx, attempts, createLoop, createSurveyor, }: {
|
|
101
|
+
workdir: string;
|
|
102
|
+
provider?: any;
|
|
103
|
+
rounds?: number;
|
|
104
|
+
minBytes?: number;
|
|
105
|
+
blobMax?: number;
|
|
106
|
+
maxTokens?: number;
|
|
107
|
+
ctx?: boolean;
|
|
108
|
+
attempts?: number;
|
|
109
|
+
createLoop?: (o: {
|
|
110
|
+
system: string;
|
|
111
|
+
policy: any;
|
|
112
|
+
onLlmResult: any;
|
|
113
|
+
provider: any;
|
|
114
|
+
}) => any;
|
|
115
|
+
createSurveyor?: (o: {
|
|
116
|
+
workdir: string;
|
|
117
|
+
granted: readonly string[];
|
|
118
|
+
ctx: boolean;
|
|
119
|
+
}) => Promise<any>;
|
|
120
|
+
}): Promise<Survey & {
|
|
121
|
+
raw: string;
|
|
122
|
+
raws: ReturnType<typeof scrubRaw>[];
|
|
123
|
+
calls: {
|
|
124
|
+
label: string;
|
|
125
|
+
costUsd: number | null;
|
|
126
|
+
unpricedRounds: number;
|
|
127
|
+
}[];
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* `git ls-tree -r --name-only <seed>` — the files as of the SEED COMMIT.
|
|
131
|
+
* Deliberately NOT `ls-files`, which reads the index and the working tree: this
|
|
132
|
+
* listing is what a path is judged against, and it must describe the tree the
|
|
133
|
+
* close will be measured from, not whatever is lying around today.
|
|
134
|
+
*
|
|
135
|
+
* A fault comes back as a named `stop`, never as an empty list — an unreadable
|
|
136
|
+
* listing that rendered as "no files" would fail every path in the declaration
|
|
137
|
+
* for the wrong reason.
|
|
138
|
+
* `files` is spelled `null` on the stop branch rather than left off: a caller
|
|
139
|
+
* that destructures the failure and finds `undefined` reads it as falsy, which
|
|
140
|
+
* is the "absent renders as empty" shape this whole module exists to refuse.
|
|
141
|
+
*
|
|
142
|
+
* The command itself lives in `src/kinds.js` beside the executor's other git
|
|
143
|
+
* reads (`seedListing`) — the declared close's runtime gate judges paths against
|
|
144
|
+
* the SAME listing this scout hands the author, and two spellings of "what
|
|
145
|
+
* exists at the seed" would be two instruments.
|
|
146
|
+
* @param {string} workdir @param {string} seedRef
|
|
147
|
+
* @returns {Promise<{stop: string, files: null}|{stop: null, files: string[]}>}
|
|
148
|
+
*/
|
|
149
|
+
export function seedFileList(workdir: string, seedRef: string): Promise<{
|
|
150
|
+
stop: string;
|
|
151
|
+
files: null;
|
|
152
|
+
} | {
|
|
153
|
+
stop: null;
|
|
154
|
+
files: string[];
|
|
155
|
+
}>;
|
|
156
|
+
/**
|
|
157
|
+
* THE SEED LISTING, both halves.
|
|
158
|
+
*
|
|
159
|
+
* `files` is the WHOLE tree and is what the validator judges paths against.
|
|
160
|
+
* `block` is SCOPED to the survey's own sourcePaths/testPaths and is what the
|
|
161
|
+
* prompt carries. Giving the validator the scoped half instead would make a job
|
|
162
|
+
* scoped to `src/` read as whole-tree, and M2's one-population law — which fires
|
|
163
|
+
* only on a SCOPED job — would silently stop firing.
|
|
164
|
+
*
|
|
165
|
+
* @param {{workdir: string, seedRef: string, sourcePaths?: any, testPaths?: any,
|
|
166
|
+
* listCap?: number, byteCap?: number}} o
|
|
167
|
+
* @returns {Promise<{stop: string, files: null, block: null, meta: null}
|
|
168
|
+
* |{stop: null, files: string[], block: string, meta: {seedRef: string, totalSeedFiles: number,
|
|
169
|
+
* entries: {entry: string, raw: string, section: string, matched: number}[], tier: string}}>}
|
|
170
|
+
*/
|
|
171
|
+
export function buildSeedListing({ workdir, seedRef, sourcePaths, testPaths, listCap, byteCap }: {
|
|
172
|
+
workdir: string;
|
|
173
|
+
seedRef: string;
|
|
174
|
+
sourcePaths?: any;
|
|
175
|
+
testPaths?: any;
|
|
176
|
+
listCap?: number;
|
|
177
|
+
byteCap?: number;
|
|
178
|
+
}): Promise<{
|
|
179
|
+
stop: string;
|
|
180
|
+
files: null;
|
|
181
|
+
block: null;
|
|
182
|
+
meta: null;
|
|
183
|
+
} | {
|
|
184
|
+
stop: null;
|
|
185
|
+
files: string[];
|
|
186
|
+
block: string;
|
|
187
|
+
meta: {
|
|
188
|
+
seedRef: string;
|
|
189
|
+
totalSeedFiles: number;
|
|
190
|
+
entries: {
|
|
191
|
+
entry: string;
|
|
192
|
+
raw: string;
|
|
193
|
+
section: string;
|
|
194
|
+
matched: number;
|
|
195
|
+
}[];
|
|
196
|
+
tier: string;
|
|
197
|
+
};
|
|
198
|
+
}>;
|
|
199
|
+
/** src/planrun.js:42 — the survey's hard round bound. */
|
|
200
|
+
export const AUTHOR_SCOUT_ROUNDS: 8;
|
|
201
|
+
/** src/planrun.js:44 — the survey is a PROMPT INGREDIENT for the authoring call. */
|
|
202
|
+
export const AUTHOR_SCOUT_BLOB_MAX: 8000;
|
|
203
|
+
/**
|
|
204
|
+
* src/planrun.js:62 — below this a survey is ABSENT, not empty. Sized off the
|
|
205
|
+
* archive rather than taste: 15 truncated scouts produced 0/74/86 bytes; every
|
|
206
|
+
* real survey was 5991-8056. It is imported as a NUMBER, not as an import, on
|
|
207
|
+
* purpose — planrun does not export it, and a `SCOUT_MIN_BYTES` that drifts
|
|
208
|
+
* between the two scouts is two instruments, so the suite pins them equal.
|
|
209
|
+
*/
|
|
210
|
+
export const AUTHOR_SCOUT_MIN_BYTES: 200;
|
|
211
|
+
/** F30 — the 4096 provider default cannot hold a whole survey. */
|
|
212
|
+
export const AUTHOR_SCOUT_MAX_TOKENS: 32000;
|
|
213
|
+
/**
|
|
214
|
+
* TOTAL survey attempts, HARDCODED (hamr, 2026-08-09: *"depending on causes, we
|
|
215
|
+
* can hardcode 3 attempts, but if reasons are beyond malformed then we may verge
|
|
216
|
+
* into self-healing but i don't think we should"*).
|
|
217
|
+
*
|
|
218
|
+
* It is a ceiling, not a target: an operator may pass a LOWER cap and never a
|
|
219
|
+
* higher one, exactly the direction the standing budget rule runs in (*the agent
|
|
220
|
+
* may only TIGHTEN*). A widened value CLAMPS here rather than throwing, because
|
|
221
|
+
* the number is not a signed artefact — nothing downstream depends on a caller
|
|
222
|
+
* having asked for the right one, and refusing a run over a too-generous cap
|
|
223
|
+
* costs a paid scout for no safety.
|
|
224
|
+
*
|
|
225
|
+
* Sited beside `MAX_REVISIONS`/`MAX_STRUCTURE_RETRIES` in spirit: the same
|
|
226
|
+
* bounded-retry family, one axis each. This one is the SURVEY's axis.
|
|
227
|
+
*/
|
|
228
|
+
export const SCOUT_ATTEMPTS: 3;
|
|
229
|
+
/**
|
|
230
|
+
* THE TYPED CAUSE, at the detection site.
|
|
231
|
+
*
|
|
232
|
+
* The retry gate reads this enum and nothing else. Typing it here is what stops
|
|
233
|
+
* the gate quietly widening to "any ABSENT" the next time someone edits the
|
|
234
|
+
* classifier: a new route to ABSENT has to be named, and naming it is the moment
|
|
235
|
+
* somebody decides whether it retries.
|
|
236
|
+
*/
|
|
237
|
+
export const SURVEY_CAUSES: Readonly<{
|
|
238
|
+
/** the call itself died — provider error, transport, `truncated:max_tokens` */
|
|
239
|
+
CALL_FAILED: "call-failed";
|
|
240
|
+
/** the model returned nothing at all, and nothing went wrong to explain it */
|
|
241
|
+
EMPTY: "empty";
|
|
242
|
+
/** short of the ABSENT floor but not empty — F59's cut-off population */
|
|
243
|
+
SHORT: "short";
|
|
244
|
+
/** nothing extractable in the reply */
|
|
245
|
+
NO_ARTIFACT: "no-artifact";
|
|
246
|
+
/** there IS a body and it is not JSON — run mslhn707's own death */
|
|
247
|
+
UNPARSEABLE: "unparseable";
|
|
248
|
+
/** valid JSON, wrong shape */
|
|
249
|
+
NON_OBJECT: "non-object";
|
|
250
|
+
/** valid JSON, no content — F59's whole point, and a SEMANTIC failure */
|
|
251
|
+
EMPTY_OBJECT: "empty-object";
|
|
252
|
+
}>;
|
|
253
|
+
/**
|
|
254
|
+
* THE MALFORMED CLASS — the only causes a retry fires on.
|
|
255
|
+
*
|
|
256
|
+
* What is deliberately NOT here, and why:
|
|
257
|
+
* - `call-failed` covers `truncated:max_tokens` and every transport death.
|
|
258
|
+
* Standing doctrine routes both as provider-red with NO redraft; a retry
|
|
259
|
+
* here would pay for the same failure again under a different name.
|
|
260
|
+
* - `short` is F59's cut-off population, and it already has an instrument: the
|
|
261
|
+
* reserved TOOLLESS recovery round, which fires inside the attempt.
|
|
262
|
+
* - `non-object` and `empty-object` are VALID JSON with wrong or vacuous
|
|
263
|
+
* content. That is a SEMANTIC failure, and F38/F39 measured what re-asking
|
|
264
|
+
* one buys: the same distribution, sampled twice. Reacting to content is a
|
|
265
|
+
* designed loop; this is a retry. The line between them is the self-healing
|
|
266
|
+
* line, and it is not crossed here.
|
|
267
|
+
*/
|
|
268
|
+
/** @type {readonly string[]} */
|
|
269
|
+
export const SCOUT_RETRY_CAUSES: readonly string[];
|
|
270
|
+
/**
|
|
271
|
+
* THE GRANT: the full menu MINUS every write-class and store-class verb.
|
|
272
|
+
* Read-only by MENU CONSTRUCTION — no write-class tool object is ever built, so
|
|
273
|
+
* there is nothing for a fence to have to refuse. Derived from the shipped
|
|
274
|
+
* constants so a verb added to `TOOL_MENU` is covered the day it lands.
|
|
275
|
+
*/
|
|
276
|
+
export const AUTHOR_SCOUT_VERBS: readonly string[];
|
|
277
|
+
export const SCOUT_SYSTEM: string;
|
|
278
|
+
/**
|
|
279
|
+
* F59's reserved round, as a FIXED constant. The round is toolless BY
|
|
280
|
+
* CONSTRUCTION (`defs` is `[]`, so text is the only output the model can
|
|
281
|
+
* produce) — never by asking politely in prose (F19/F37).
|
|
282
|
+
*/
|
|
283
|
+
export const SCOUT_RECOVERY_PROMPT: string;
|
|
284
|
+
/** per-path full-listing cap; over it, names give way to counted directories */
|
|
285
|
+
export const LIST_CAP: 40;
|
|
286
|
+
/** whole-block ceiling — the block is a prompt ingredient, like the survey */
|
|
287
|
+
export const LISTING_BYTE_CAP: 12000;
|
|
288
|
+
/** how many bare file NAMES survive the deepest tier of a flat directory */
|
|
289
|
+
export const COUNTS_ONLY_NAMES: 12;
|
|
290
|
+
export function cleanEntry(e: any): string;
|
|
291
|
+
export type SurveyMeta = {
|
|
292
|
+
bytes: number;
|
|
293
|
+
rounds: number;
|
|
294
|
+
bounded: boolean;
|
|
295
|
+
recovered: boolean;
|
|
296
|
+
error: string | null;
|
|
297
|
+
attempts?: number;
|
|
298
|
+
attemptsAllowed?: number;
|
|
299
|
+
};
|
|
300
|
+
export type Survey = {
|
|
301
|
+
state: "PRESENT" | "ABSENT";
|
|
302
|
+
facts: any;
|
|
303
|
+
reason: string | null;
|
|
304
|
+
cause: string | null;
|
|
305
|
+
meta: SurveyMeta;
|
|
306
|
+
};
|
|
307
|
+
import { scrubRaw } from './text.js';
|