bareloop 0.45.0 → 0.46.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 +22 -0
- package/bareloop.context.md +27 -2
- package/package.json +1 -1
- package/src/attemptdiff.js +116 -0
- package/src/authorreadout.js +8 -2
- package/src/authorscout.js +1 -1
- package/src/bundle.js +90 -6
- package/src/close-integrity.js +39 -5
- package/src/config.js +5 -2
- package/src/inputlook.js +1 -47
- package/src/judged.js +3 -3
- package/src/labelscan.js +13 -4
- package/src/ladder.js +2 -2
- package/src/learn.js +943 -0
- package/src/learnstore.js +154 -0
- package/src/modellist.js +1 -1
- package/src/pagedry.js +2 -3
- package/src/pagefetch.js +2 -1
- package/src/pagerules.js +27 -4
- package/src/panel/authorroutes.js +1 -1
- package/src/panel/index.html +96 -0
- package/src/panel/server.js +7 -125
- package/src/planrun.js +40 -6
- package/src/providerrows.js +3 -3
- package/src/run.js +4 -2
- package/src/schedulewords.js +1 -1
- package/src/source.js +1 -1
- package/src/stagewords.js +144 -0
- package/src/text.js +4 -2
- package/src/userrun.js +28 -0
- package/src/validate.js +10 -5
- package/src/worktree.js +8 -2
- package/types/attemptdiff.d.ts +38 -0
- package/types/config.d.ts +1 -1
- package/types/inputlook.d.ts +0 -23
- package/types/labelscan.d.ts +6 -0
- package/types/learn.d.ts +575 -0
- package/types/learnstore.d.ts +107 -0
- package/types/pagerules.d.ts +7 -1
- package/types/planrun.d.ts +13 -3
- package/types/reuse.d.ts +1 -1
- package/types/run.d.ts +8 -2
- package/types/stagewords.d.ts +90 -0
package/src/planrun.js
CHANGED
|
@@ -39,6 +39,7 @@ import { TOOL_BY_VERB, CTX_TOOLS, createCtxTools, toolAction, PERSONA_TOOLS, str
|
|
|
39
39
|
import { anySuppressionWarning } from './authoring.js';
|
|
40
40
|
import { createReadShim, readShimArm, readShimStrategy } from './readshim.js';
|
|
41
41
|
import { globToPrefix, redactSecrets, SECRET_PATTERNS } from './validate.js';
|
|
42
|
+
import { startAttemptDiff } from './attemptdiff.js';
|
|
42
43
|
import { validateBridge, loadGate, newestEligibleVersion, reuseEligibility, quarantinesCredit, QUARANTINED_CODE } from './bridges.js';
|
|
43
44
|
import { extractArtifact, rateSourceFields } from './text.js';
|
|
44
45
|
import { defaultJudgeLoop } from './judged.js';
|
|
@@ -338,6 +339,17 @@ function materialsBlock(m) {
|
|
|
338
339
|
return `\nWhat this run has left to spend — plan within it:\n${lines.join('\n')}\nThese are totals for the run, not per-step allowances. Nothing here is a rate: a step's cost depends on what it does.\n`;
|
|
339
340
|
}
|
|
340
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Cross-run learnings (ITEM33-BUILD.md rev 2, RULING 7): the block `renderHints` (src/learn.js) made, rendered verbatim into
|
|
344
|
+
* the PLANNER's prompt and nowhere else — a worker (scout, step, close-fix) is never handed ledger text. Absent or empty
|
|
345
|
+
* renders '' so every prompt without hints is byte-identical to what it was.
|
|
346
|
+
* @param {string|null|undefined} text
|
|
347
|
+
*/
|
|
348
|
+
function hintsBlock(text) {
|
|
349
|
+
if (typeof text !== 'string' || !text.trim()) return '';
|
|
350
|
+
return `\n${text}\n`;
|
|
351
|
+
}
|
|
352
|
+
|
|
341
353
|
/**
|
|
342
354
|
* MODULE 8 — the signer's words, when this leg is a RERUN taken at a previous run's
|
|
343
355
|
* review door (PRD v1.71 §3.4: *"redo/rerun comes with new authoring"*).
|
|
@@ -632,8 +644,10 @@ export function boundedNote(bounded, rounds) {
|
|
|
632
644
|
* is STATED as well as enforced (the mailbox precedent again: a law only the validator
|
|
633
645
|
* knows costs a redraft per draft). The arms without it — `false` and `'diff'` — render
|
|
634
646
|
* byte-identically to the pre-shim prompt.
|
|
647
|
+
* @param {string|null} [hints] the cross-run learnings block (src/learn.js `renderHints`), verbatim; null/empty
|
|
648
|
+
* renders byte-identically to the prompt without it. Only ever passed for the draft and the first replan.
|
|
635
649
|
*/
|
|
636
|
-
export function planPrompt(job, scoutBlob, reds, maxStepRounds, failure, scopes, materials, startingDraft = null, checkFacts = {}, doorNote = null, readShim = false) {
|
|
650
|
+
export function planPrompt(job, scoutBlob, reds, maxStepRounds, failure, scopes, materials, startingDraft = null, checkFacts = {}, doorNote = null, readShim = false, hints = null) {
|
|
637
651
|
const scopeMenu = Array.isArray(scopes) && scopes.length ? scopes : legalScopes(job.writeScope ?? []);
|
|
638
652
|
const ceiling = Array.isArray(job.tools) ? job.tools : [...TOOL_MENU];
|
|
639
653
|
// The shape-lottery laws, stated where the check menu is offered (the mailbox
|
|
@@ -723,7 +737,7 @@ ${scopeMenu.map((s) => ` ${JSON.stringify(s)}`).join('\n')}
|
|
|
723
737
|
|
|
724
738
|
Goal:
|
|
725
739
|
${job.goal}
|
|
726
|
-
${doorNoteBlock(doorNote)}${materialsBlock(materials)}
|
|
740
|
+
${doorNoteBlock(doorNote)}${materialsBlock(materials)}${hintsBlock(hints)}
|
|
727
741
|
Repository survey (from a read-only scout):
|
|
728
742
|
${scoutBlob || '(no scout notes)'}`;
|
|
729
743
|
let p = doc;
|
|
@@ -982,12 +996,16 @@ ${scoutBlob || '(no scout notes)'}`;
|
|
|
982
996
|
* the file, end the Loop after that round, and end the leg `stopped` (a checkpoint outcome, filed
|
|
983
997
|
* the way a mid-step cap-halt is; resume re-enters that step, or continues the phase). Nothing is in flight at that
|
|
984
998
|
* point, so the spend is exact. `null` = no stop surface (the default).
|
|
999
|
+
* @param {{text: string, ids: string[]}|null} [opts.learnHints=null] cross-run learnings (src/learn.js `renderHints`, made once at
|
|
1000
|
+
* run start from the ledger as it stood). Handed to the PLANNER only: the first draft and the FIRST replan. No worker ever
|
|
1001
|
+
* receives it, and a later replan gets nothing. Each injection is recorded on the spine as a `learnings` record (the exact
|
|
1002
|
+
* text) so the Audit can show it and the byte check can compare it with a fresh render. `null` = no hints (the default).
|
|
985
1003
|
* @returns {Promise<string>} 'green' | 'already-green' | 'escalated' | 'plan-red' |
|
|
986
1004
|
* 'check-red' | 'close-red' | 'close-unsupported' | 'recipe-stale' | 'pricing-red' |
|
|
987
1005
|
* 'branch-red' | 'cap-halt' | 'wall-halt' | 'stopped' | 'provider-red' | 'interpreter-red' |
|
|
988
1006
|
* 'step-stalled' | 'hitl-pause' | 'hitl-decision-red' | `step-red:<id>`
|
|
989
1007
|
*/
|
|
990
|
-
export async function runPlan(job, { workdir, provider, nativeProvider, providerFor, judgeProvider = null, judgeModel = null, rates = null, judgeRates = null, emit, remainingUsd, isUnpriced = () => false, spendComplete = () => true, capRuns = 3, strikeLimit = STRIKE_LIMIT, closeTimeoutMs, closeDir = null, maxStepRounds = 40, layerRoot = false, readShim = false, scout = true, scoutRounds = SCOUT_ROUNDS, bridge = null, now, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, priorSpentUsd = 0, reviewDoor = null, doorRerun = null, resumable = true, stopFile = null }) {
|
|
1008
|
+
export async function runPlan(job, { workdir, provider, nativeProvider, providerFor, judgeProvider = null, judgeModel = null, rates = null, judgeRates = null, emit, remainingUsd, isUnpriced = () => false, spendComplete = () => true, capRuns = 3, strikeLimit = STRIKE_LIMIT, closeTimeoutMs, closeDir = null, maxStepRounds = 40, layerRoot = false, readShim = false, scout = true, scoutRounds = SCOUT_ROUNDS, bridge = null, now, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, priorSpentUsd = 0, reviewDoor = null, doorRerun = null, resumable = true, stopFile = null, learnHints = null }) {
|
|
991
1009
|
// MEMORY-CACHE: what the read shim (src/readshim.js) saved THIS run, summed across
|
|
992
1010
|
// every mkWorker's own shim instance (scout, drafter, each step's worker, the fix
|
|
993
1011
|
// worker) — one accumulator closed over by all of them, because the shim's ledger
|
|
@@ -3068,6 +3086,10 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
3068
3086
|
// is actually left, which is the adaptation channel.
|
|
3069
3087
|
const materials = { balanceUsd: remainingUsd(), remainingMs: clock.bounded ? clock.remainingMs() : null, ...(progress ? { progress } : {}) };
|
|
3070
3088
|
emit('materials', { phase, ...materials });
|
|
3089
|
+
// Cross-run learnings: the planner at the draft and at the FIRST replan, nobody else. The spine keeps the exact text it was
|
|
3090
|
+
// handed (a record, never a verdict), so the Audit can show it and the byte check can compare it with a fresh render.
|
|
3091
|
+
const hints = learnHints && learnHints.text && (phase === 'draft' || (phase === 'replan' && replans === 1)) ? learnHints : null;
|
|
3092
|
+
if (hints) emit('learnings', { phase, text: hints.text, ids: hints.ids, chars: hints.text.length });
|
|
3071
3093
|
// D5 — the bridge rides into the FIRST draft phase ONLY. A replan drafts from the
|
|
3072
3094
|
// RUN'S OWN STATE (what failed, what is left): re-handing the recipe there would
|
|
3073
3095
|
// answer the replan's question with the material the run just demonstrated does not
|
|
@@ -3092,7 +3114,7 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
3092
3114
|
const draftPlan = async (/** @type {any[]|null} */ reds) => {
|
|
3093
3115
|
drafter.setIteration(reds ? 'redraft' : 'draft');
|
|
3094
3116
|
replanCut = false;
|
|
3095
|
-
const r = await drafter.ask(planPrompt(job, scoutBlob, reds, maxStepRounds, failure, scopeMenu, materials, starting, checkFacts, doorWords?.text ?? null, readShim), []);
|
|
3117
|
+
const r = await drafter.ask(planPrompt(job, scoutBlob, reds, maxStepRounds, failure, scopeMenu, materials, starting, checkFacts, doorWords?.text ?? null, readShim, hints?.text ?? null), []);
|
|
3096
3118
|
if (phase === 'replan' && typeof r.error === 'string' && r.error.startsWith('truncated:')) {
|
|
3097
3119
|
replanCut = true;
|
|
3098
3120
|
return '';
|
|
@@ -3425,7 +3447,10 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
3425
3447
|
// read ONCE: the note and the "was it the attempt just before this one" test
|
|
3426
3448
|
// are two questions about the same record, and two reads is how they drift
|
|
3427
3449
|
const bound = w.wasBounded();
|
|
3428
|
-
|
|
3450
|
+
// attempt-diff: what THIS try changed, recorded to the spine only (never a prompt)
|
|
3451
|
+
const tryDiff = await startAttemptDiff({ workdir, fence: step.scope !== undefined ? [resolve(workdir, globToPrefix(step.scope))] : fencePrefixes, redact: scrub });
|
|
3452
|
+
let r;
|
|
3453
|
+
try { r = await w.ask([
|
|
3429
3454
|
step.action,
|
|
3430
3455
|
`Repository root (absolute): ${workdir}\nEvery path you pass to a tool MUST be absolute and inside this root — a relative path resolves against a different directory and will be denied by the gate.`,
|
|
3431
3456
|
step.target && `Write your deliverable to: ${resolve(workdir, step.target)}`,
|
|
@@ -3437,6 +3462,10 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
3437
3462
|
bound?.iteration === iteration - 1 && boundedNote(bound, step.rounds),
|
|
3438
3463
|
rootInj && rootInj.note,
|
|
3439
3464
|
].filter(Boolean).join('\n\n'));
|
|
3465
|
+
} finally {
|
|
3466
|
+
const d = await tryDiff.finish();
|
|
3467
|
+
if (d) emit('attempt-diff', { phase: `step:${step.id}`, step: step.id, iteration, ...d });
|
|
3468
|
+
}
|
|
3440
3469
|
lastText = scrub(r.text ?? '').slice(0, ARTIFACT_MAX);
|
|
3441
3470
|
};
|
|
3442
3471
|
let lastGap = '';
|
|
@@ -4172,7 +4201,8 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
4172
4201
|
// same iteration numbering as the step loop: `w.setIteration(iteration)` above
|
|
4173
4202
|
// stamps the bound, so "the attempt just before this one" is `iteration - 1`
|
|
4174
4203
|
const bound = w.wasBounded();
|
|
4175
|
-
await
|
|
4204
|
+
const tryDiff = await startAttemptDiff({ workdir, fence: fencePrefixes, redact: scrub });
|
|
4205
|
+
try { await w.ask([
|
|
4176
4206
|
'The job\'s final verification is failing. Fix the repository so it passes.',
|
|
4177
4207
|
`Repository root (absolute): ${workdir}\nEvery path you pass to a tool MUST be absolute and inside this root.`,
|
|
4178
4208
|
artifacts.length > 0 && `Working context (read-only) — the plan's steps produced:\n${artifacts.map((a) => `[${a.id}] ${a.text}`).join('\n\n')}`,
|
|
@@ -4182,6 +4212,10 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
4182
4212
|
bound?.iteration === iteration - 1 && boundedNote(bound, maxStepRounds),
|
|
4183
4213
|
rootInj && rootInj.note,
|
|
4184
4214
|
].filter(Boolean).join('\n\n'));
|
|
4215
|
+
} finally {
|
|
4216
|
+
const d = await tryDiff.finish();
|
|
4217
|
+
if (d) emit('attempt-diff', { phase: 'fix', step: null, iteration, ...d });
|
|
4218
|
+
}
|
|
4185
4219
|
};
|
|
4186
4220
|
fixOutcome = await ralph({
|
|
4187
4221
|
// the staged close rides in as the JUDGE seam (one verdict, every stage in
|
package/src/providerrows.js
CHANGED
|
@@ -170,8 +170,8 @@ export function endpointOf(provider, baseUrl) {
|
|
|
170
170
|
if (raw === '') return '';
|
|
171
171
|
try {
|
|
172
172
|
const u = new URL(raw);
|
|
173
|
-
return `${u.protocol}//${u.host}${u.pathname.replace(
|
|
174
|
-
} catch { return raw.replace(
|
|
173
|
+
return `${u.protocol}//${u.host}${u.pathname.replace(/(?<!\/)\/+$/, '')}`;
|
|
174
|
+
} catch { return raw.replace(/(?<!\/)\/+$/, ''); }
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
@@ -318,7 +318,7 @@ export function keyValueOf(env, envName) {
|
|
|
318
318
|
function perMoney(n) {
|
|
319
319
|
const shown = String(n);
|
|
320
320
|
if (!shown.includes('e') && (shown.split('.')[1] ?? '').length >= 2) return `$${shown}`;
|
|
321
|
-
if (shown.includes('e')) return `$${n.toFixed(12).replace(/0+$/, '').replace(/\.$/, '')}`;
|
|
321
|
+
if (shown.includes('e')) return `$${n.toFixed(12).replace(/(?<!0)0+$/, '').replace(/\.$/, '')}`;
|
|
322
322
|
return `$${n.toFixed(2)}`;
|
|
323
323
|
}
|
|
324
324
|
|
package/src/run.js
CHANGED
|
@@ -259,6 +259,8 @@ async function primitiveSmoke(workdir) {
|
|
|
259
259
|
* @param {string|null} [opts.stopFile=null] P5 item 5 — the run's STOP REQUEST file
|
|
260
260
|
* (`stopFilePath(spine)`, src/legs.js), forwarded verbatim to the plan flow, which reads it at the
|
|
261
261
|
* round-boundary seam (where the money cap binds) and ends the leg `stopped`. `null` = no stop surface.
|
|
262
|
+
* @param {{text: string, ids: string[]}|null} [opts.learnHints=null] cross-run learnings for the PLANNER only (the draft and the
|
|
263
|
+
* first replan); forwarded verbatim to the plan flow, which records what it handed over as a `learnings` spine record.
|
|
262
264
|
* @returns {Promise<string>} outcome: 'green' | 'already-green' | 'escalated' |
|
|
263
265
|
* 'unapproved-spec' | 'job-red' | 'smoke-red' | 'plan-red' | 'check-red' |
|
|
264
266
|
* 'close-red' | 'close-unsupported' | 'recipe-stale' | 'branch-red' | 'pricing-red' | 'provider-red' |
|
|
@@ -273,7 +275,7 @@ async function primitiveSmoke(workdir) {
|
|
|
273
275
|
* deliberately NOT in the resumable/checkpoint set (`src/reuse.js`) — an
|
|
274
276
|
* unknown in-flight state is not a known-safe resume point.
|
|
275
277
|
*/
|
|
276
|
-
export async function runJob(rawSpec, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider = null, judgeModel = null, rates = null, judgeRates = null, emit, capRuns = 3, strikeLimit, shellCapUsd = 2, closeTimeoutMs, closeDir = null, layerRoot = false, readShim = false, scout = true, bridge = null, draftSpentUsd = 0, draftSpendComplete = true, priorSpentUsd = 0, priorSpendComplete = true, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, reviewDoor = null, doorRerun = null, resumable = true, stopFile = null }) {
|
|
278
|
+
export async function runJob(rawSpec, { approvals, workdir, provider, nativeProvider, providerFor, judgeProvider = null, judgeModel = null, rates = null, judgeRates = null, emit, capRuns = 3, strikeLimit, shellCapUsd = 2, closeTimeoutMs, closeDir = null, layerRoot = false, readShim = false, scout = true, bridge = null, draftSpentUsd = 0, draftSpendComplete = true, priorSpentUsd = 0, priorSpendComplete = true, priorWallMs = 0, resumeSeed = null, resumeGrades = [], resumeReplans = null, resumeBranch = null, humanRuling = null, heldRuling = null, reviewDoor = null, doorRerun = null, resumable = true, stopFile = null, learnHints = null }) {
|
|
277
279
|
// THE READ SHIM's ARM, resolved at the door — the FIRST thing this entry does,
|
|
278
280
|
// before the ledger, before the approval gate, before a byte of the spec is read.
|
|
279
281
|
// An unrecognised spelling throws here at zero cost instead of being coerced by
|
|
@@ -540,7 +542,7 @@ export async function runJob(rawSpec, { approvals, workdir, provider, nativeProv
|
|
|
540
542
|
// accounts it natively (F12) and the job-end money contract is unchanged.
|
|
541
543
|
{
|
|
542
544
|
const outcome = await runPlan(job, {
|
|
543
|
-
workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit: meter, capRuns, ...(strikeLimit !== undefined ? { strikeLimit } : {}), closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorWallMs: chainWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, priorSpentUsd: chainFoldUsd, resumable, stopFile,
|
|
545
|
+
workdir, provider, nativeProvider, providerFor, judgeProvider, judgeModel, rates, judgeRates, emit: meter, capRuns, ...(strikeLimit !== undefined ? { strikeLimit } : {}), closeTimeoutMs, closeDir, layerRoot, readShim, scout, bridge, priorWallMs: chainWallMs, resumeSeed, resumeGrades, resumeReplans, resumeBranch, humanRuling, heldRuling, reviewDoor, doorRerun, priorSpentUsd: chainFoldUsd, resumable, stopFile, learnHints,
|
|
544
546
|
// hamr's ruling 2026-09-28 — ONE cap covers drafting + run: the run's
|
|
545
547
|
// enforced ceiling is the signed `budgetUsd` (unchanged, still in the
|
|
546
548
|
// spec hash) MINUS whatever drafting already spent on this job before
|
package/src/schedulewords.js
CHANGED
|
@@ -232,7 +232,7 @@ export function startedAuditWords(rec, originNo) {
|
|
|
232
232
|
|
|
233
233
|
/** a refusal's own words without its trailing "Nothing started." @param {unknown} why */
|
|
234
234
|
function reasonOf(why) {
|
|
235
|
-
return String(why ?? '').replace(
|
|
235
|
+
return String(why ?? '').replace(/(?<!\s)\s*Nothing (started|spent)\.\s*$/, '').trim().replace(/\.$/, '');
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
/**
|
package/src/source.js
CHANGED
|
@@ -1273,7 +1273,7 @@ export async function proveRubricInputs({ inputs, into, destination, fetchUrl =
|
|
|
1273
1273
|
labels.add(label.toLowerCase());
|
|
1274
1274
|
if (isWebUrl(value)) {
|
|
1275
1275
|
const got = await fetchUrl(value.trim());
|
|
1276
|
-
if (!got.ok) return bad('input-url-unreadable', n, `${got.error.replace(/[.\s]+$/, '')}.`);
|
|
1276
|
+
if (!got.ok) return bad('input-url-unreadable', n, `${got.error.replace(/(?<![.\s])[.\s]+$/, '')}.`);
|
|
1277
1277
|
const pageBuf = Buffer.from(got.text, 'utf8');
|
|
1278
1278
|
const leak = secretPatternNames(got.text);
|
|
1279
1279
|
if (leak.length) return bad('input-url-secret', n, `the page at ${got.url} carries a known secret shape (${leak.join(', ')}) - refused, secrets never enter a job.`);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// The ONE owner of a stage's plain question ("did it change any file?", "no failing tests?"). Moved out of
|
|
2
|
+
// src/panel/server.js (verbatim) so the Audit/step map AND the learnings cards read the same sentence from the same
|
|
3
|
+
// code — a second spelling is how two screens come to disagree about one check. Pure; no file IO, no model.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* THE PLAIN-QUESTION OWNER (plain-checks build, 2026-09-28, hamr's ruling:
|
|
7
|
+
* the "checks N/M" headline is WITHDRAWN — "confusing, reads like 6 failed
|
|
8
|
+
* when 5 never ran"). ONE pure function mapping a declared stage's own
|
|
9
|
+
* signed `kind`+`params` (plus the close's own `genre`, also a signed field)
|
|
10
|
+
* to a short plain-English question a person can read without knowing the
|
|
11
|
+
* catalogue — never a second spelling on the client, and never anything the
|
|
12
|
+
* model authored (the catalogue's kinds/params are the whole vocabulary this
|
|
13
|
+
* reads).
|
|
14
|
+
*
|
|
15
|
+
* Every branch is traced straight off `src/kinds.js`'s runner semantics and
|
|
16
|
+
* the TYPES genre template (`src/authoring.js` `TYPES_GENRE_TEMPLATE`,
|
|
17
|
+
* `classGuards`'s `MECHANICAL_GUARDS`) — never guessed:
|
|
18
|
+
* - `files-changed` is always the `changed-from-seed` guard: "did it
|
|
19
|
+
* change any file?"
|
|
20
|
+
* - `pattern-absent-in-diff` is always the `no-suppressions` guard: "no
|
|
21
|
+
* casts or silencers added?"
|
|
22
|
+
* - `rubric-step` (Part 1a) is one job line's output file: "line N — does its output pass the line's checks?"
|
|
23
|
+
* - `command-exit` is the genre's `suite-green` stage (the only shipped use
|
|
24
|
+
* of this kind): "does the test suite pass?"
|
|
25
|
+
* - `count-not-worse` splits three ways on its own declared
|
|
26
|
+
* `direction`/`baseline` (never the runtime number — see
|
|
27
|
+
* {@link stageKindMetaFromSpec}'s own doc for why the baseline KIND must
|
|
28
|
+
* travel separately from the measured figure):
|
|
29
|
+
* - `higher-is-better` + `baseline: 'seed'` is the genre's `tests-kept`
|
|
30
|
+
* floor: "did all the old tests still exist?"
|
|
31
|
+
* - `lower-is-better` + `baseline: 0` is the genre's in-scope
|
|
32
|
+
* `typecheck` stage — UNLESS its own `cmd`+`args` is the SAME
|
|
33
|
+
* command a `command-exit` stage in the same close already runs
|
|
34
|
+
* (`suiteCmdKeys`, below): the TYPES template's `suite-green` is "the
|
|
35
|
+
* suite exits clean AND reports zero failing tests — TWO assertions"
|
|
36
|
+
* over ONE population (the same `npm test`/`pytest` invocation), so a
|
|
37
|
+
* `count-not-worse` stage sharing that exact command is the failing-
|
|
38
|
+
* test-count half of that pair, never a typecheck stage: "no failing
|
|
39
|
+
* tests?" — derived from the same cmd+args correlation (the stage
|
|
40
|
+
* counts the suite's own failing tests), never from the stage's own
|
|
41
|
+
* name string. The live check that found this (run mul5fofw's own
|
|
42
|
+
* `suite-zero-failing-tests`, cmd `npm test`, same as its sibling
|
|
43
|
+
* `suite-green` command-exit stage) would otherwise have read "test/
|
|
44
|
+
* has 0 type errors?", which is false: its parser counts FAILING
|
|
45
|
+
* TESTS (`^# fail (\d+)$`), not type-checker output. hamr's ruling:
|
|
46
|
+
* name this half honestly rather than reusing "type errors" for a
|
|
47
|
+
* population it was never about, or falling back to the stage's raw
|
|
48
|
+
* name. When the command differs from every sibling `command-exit`
|
|
49
|
+
* stage, the declared `scope.includePrefixes` naming exactly ONE path
|
|
50
|
+
* is quoted verbatim (a signed param, never invented): "<path> has 0
|
|
51
|
+
* type errors?" — otherwise the generic "type errors is 0?", never a
|
|
52
|
+
* guessed filename.
|
|
53
|
+
* - `lower-is-better` + `baseline: 'seed'` with a non-empty
|
|
54
|
+
* `scope.excludePrefixes` is the genre's `typecheck-outside` ceiling:
|
|
55
|
+
* "other files didn't get more type errors?"
|
|
56
|
+
* The "type errors" wording is genre-owned (`GENRE_WHAT`), because TYPES is
|
|
57
|
+
* the only genre this catalogue admits today (doc-genre kinds are not yet
|
|
58
|
+
* built) — an unrecognised genre, or a `count-not-worse` shape none of the
|
|
59
|
+
* three above matches, returns `null` rather than invent a fourth shape.
|
|
60
|
+
*
|
|
61
|
+
* `null` means "no honest plain question" — the caller (buildAttemptsList,
|
|
62
|
+
* partLine1Text's `attemptChecksLine`) falls back to the stage's OWN name,
|
|
63
|
+
* exactly as it already does for a stage with no resolvable kind at all.
|
|
64
|
+
* @param {{kind: string|null, params: any, genre: string|null, suiteCmdKeys: Set<string>}} o
|
|
65
|
+
* @returns {string|null}
|
|
66
|
+
*/
|
|
67
|
+
export const GENRE_WHAT = Object.freeze({ TYPES: 'type errors' });
|
|
68
|
+
|
|
69
|
+
/** `cmd`+`args`, as a comparable key — the one signal (besides `kind`
|
|
70
|
+
* itself) that two stages measure the SAME command's output, used to tell a
|
|
71
|
+
* genuine typecheck stage apart from the TYPES genre's other `lower-is-
|
|
72
|
+
* better`/`baseline: 0` stage (the failing-test-count half of `suite-green`'s
|
|
73
|
+
* "two assertions"), which shares that stage's own shape but not its
|
|
74
|
+
* population. `null` for a stage whose `cmd` isn't a non-empty string —
|
|
75
|
+
* never matches anything, so it never falsely suppresses a real typecheck
|
|
76
|
+
* question.
|
|
77
|
+
* @param {any} params @returns {string|null} */
|
|
78
|
+
export function cmdKey(params) {
|
|
79
|
+
if (!params || typeof params.cmd !== 'string' || params.cmd.length === 0) return null;
|
|
80
|
+
const args = Array.isArray(params.args) ? params.args : [];
|
|
81
|
+
return JSON.stringify([params.cmd, args]);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function stageQuestionText({
|
|
85
|
+
kind, params, genre, suiteCmdKeys,
|
|
86
|
+
}) {
|
|
87
|
+
if (kind === 'files-changed') return 'did it change any file?';
|
|
88
|
+
if (kind === 'pattern-absent-in-diff') return 'no casts or silencers added?';
|
|
89
|
+
if (kind === 'command-exit') return 'does the test suite pass?';
|
|
90
|
+
// Part 1a: one rubric-step stage = one job line's output file
|
|
91
|
+
if (kind === 'rubric-step') return params && Number.isInteger(params.step) ? `line ${params.step} — does its output pass the line's checks?` : null;
|
|
92
|
+
if (kind !== 'count-not-worse' || !params || typeof params !== 'object') return null;
|
|
93
|
+
const what = Object.hasOwn(GENRE_WHAT, String(genre)) ? GENRE_WHAT[String(genre)] : null;
|
|
94
|
+
if (params.direction === 'higher-is-better' && params.baseline === 'seed') {
|
|
95
|
+
return 'did all the old tests still exist?';
|
|
96
|
+
}
|
|
97
|
+
if (!what) return null;
|
|
98
|
+
const scope = params.scope && typeof params.scope === 'object' ? params.scope : null;
|
|
99
|
+
if (params.direction === 'lower-is-better' && params.baseline === 0) {
|
|
100
|
+
const key = cmdKey(params);
|
|
101
|
+
if (key !== null && suiteCmdKeys instanceof Set && suiteCmdKeys.has(key)) return 'no failing tests?';
|
|
102
|
+
const include = scope && Array.isArray(scope.includePrefixes) ? scope.includePrefixes : null;
|
|
103
|
+
if (include && include.length === 1 && typeof include[0] === 'string' && include[0].length > 0) {
|
|
104
|
+
return `${include[0]} has 0 ${what}?`;
|
|
105
|
+
}
|
|
106
|
+
return `${what} is 0?`;
|
|
107
|
+
}
|
|
108
|
+
if (params.direction === 'lower-is-better' && params.baseline === 'seed') {
|
|
109
|
+
const exclude = scope && Array.isArray(scope.excludePrefixes) ? scope.excludePrefixes : null;
|
|
110
|
+
if (exclude && exclude.length > 0) return `other files didn't get more ${what}?`;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Stage name to its plain question, off a signed spec's `closeDecl.stages` — the one pass that gathers every
|
|
118
|
+
* `command-exit` stage's cmd+args FIRST (so the failing-test-count half of `suite-green`'s pair can be told from a
|
|
119
|
+
* genuine typecheck stage) and then asks {@link stageQuestionText} per stage. `null` for a stage with no honest
|
|
120
|
+
* question; an empty map for a spec with no declared stages.
|
|
121
|
+
* @param {any} spec
|
|
122
|
+
* @returns {Map<string, string|null>}
|
|
123
|
+
*/
|
|
124
|
+
export function stageQuestionsByName(spec) {
|
|
125
|
+
/** @type {Map<string, string|null>} */
|
|
126
|
+
const out = new Map();
|
|
127
|
+
if (!spec || typeof spec !== 'object' || !spec.closeDecl || !Array.isArray(spec.closeDecl.stages)) return out;
|
|
128
|
+
const genre = typeof spec.closeDecl.genre === 'string' ? spec.closeDecl.genre : null;
|
|
129
|
+
/** @type {Set<string>} */
|
|
130
|
+
const suiteCmdKeys = new Set();
|
|
131
|
+
for (const s of spec.closeDecl.stages) {
|
|
132
|
+
if (s && s.kind === 'command-exit') {
|
|
133
|
+
const key = cmdKey(s.params);
|
|
134
|
+
if (key !== null) suiteCmdKeys.add(key);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
for (const s of spec.closeDecl.stages) {
|
|
138
|
+
if (!s || typeof s.name !== 'string' || s.name.length === 0) continue;
|
|
139
|
+
const kind = typeof s.kind === 'string' ? s.kind : null;
|
|
140
|
+
const params = s.params && typeof s.params === 'object' ? s.params : null;
|
|
141
|
+
out.set(s.name, stageQuestionText({ kind, params, genre, suiteCmdKeys }));
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
package/src/text.js
CHANGED
|
@@ -42,10 +42,12 @@ const FENCE_PREAMBLE_LINES = 5;
|
|
|
42
42
|
*/
|
|
43
43
|
export function extractArtifact(t) {
|
|
44
44
|
const text = String(t ?? '');
|
|
45
|
-
|
|
45
|
+
// (?=([^\n`]*))\1 reads the info string atomically: giving characters back can never find a closing fence the full read missed (a fence starts with a
|
|
46
|
+
// backtick, which the info string excludes), so an unclosed "```aaaa..." fails once instead of once per giveback (quadratic). The body is group 2.
|
|
47
|
+
const m = text.match(/```(?=([^\n`]*))\1\n?([\s\S]*?)```/);
|
|
46
48
|
if (m) {
|
|
47
49
|
if (text.slice(0, /** @type {number} */ (m.index)).split('\n').length - 1 < FENCE_PREAMBLE_LINES) {
|
|
48
|
-
const code = m[
|
|
50
|
+
const code = m[2].trim();
|
|
49
51
|
return code ? { code, red: null } : { code: null, red: 'empty fenced block' };
|
|
50
52
|
}
|
|
51
53
|
// deep fence = the artifact's own content: verbatim, NO fence strips — the
|
package/src/userrun.js
CHANGED
|
@@ -82,6 +82,7 @@ import { keysForDoor } from './keysfile.js';
|
|
|
82
82
|
import { parseJsonl } from './replayio.js';
|
|
83
83
|
import { claimRun, monthlyRefusalText, legSpend, chainSpend } from './monthly.js';
|
|
84
84
|
import { ConfigError } from './config.js';
|
|
85
|
+
import { learnAtRunEnd, hintsForRun, releaseRun } from './learnstore.js';
|
|
85
86
|
import { commandFor } from './invoke.js';
|
|
86
87
|
import { judgeRatesFor, keyNameFor, keyValueOf, priceReadout, ratesFor, ratesOfLine, resolveWorker, judgeRowFor, rowsForHome, REFUSE_NAME_MODEL } from './providerrows.js';
|
|
87
88
|
// the banner's wall arithmetic, extracted so it is reachable by a test (F83): the
|
|
@@ -1306,6 +1307,11 @@ async function execute(ctx) {
|
|
|
1306
1307
|
if (RULING.decision !== 'rerun') {
|
|
1307
1308
|
out(`\n${RULING.decision === 'accept' ? 'ACCEPTED' : 'PAUSED'} — the verdict this run minted (${doorRecord.outcome}) is UNTOUCHED; what you answered is what happens to the work.`);
|
|
1308
1309
|
if (RULING.decision === 'accept') {
|
|
1310
|
+
// Cross-run learnings: the human Accept is what releases a held rubric run's lessons so they count (forward only; a rerun or a
|
|
1311
|
+
// pause never does). Its own try/catch: a record, never a verdict.
|
|
1312
|
+
if (cfgOn && DOOR !== null) {
|
|
1313
|
+
try { releaseRun({ home: cfgHome, runid: DOOR.includes('/') || DOOR.endsWith('.jsonl') ? basename(DOOR).replace(/^u-/, '').replace(/\.jsonl$/, '') : DOOR }); } catch (/** @type {any} */ e) { err(`WARNING: could not release this run's lessons in the learnings ledger (${redactSecrets(String(e?.message ?? e)).split('\n')[0]}).`); }
|
|
1314
|
+
}
|
|
1309
1315
|
out(` proved ${ans.mechanical?.ran ?? 0} mechanical stage(s) re-ran on the tree as it stands, and passed — an accept is not a rubber stamp`);
|
|
1310
1316
|
out(` credit ${ans.released ? 'RELEASED — this workflow is now eligible for reuse' : ((ctx.registry ?? null) === null ? 'not released: no --registry was named, so there is no entry to release (the answer is on the spine)' : (quarantinesCredit(spec.verdictType) ? 'nothing left to release — this row was already released by an earlier accept (a repeat answer records nothing new)' : 'nothing was held to release — a green-class row is minted born RELEASED (hamr, 2026-08-30): a machine proved it, so there was never a credit to quarantine'))}`);
|
|
1311
1317
|
if (ans.note) out(` note ${ans.note}`);
|
|
@@ -1895,10 +1901,18 @@ async function execute(ctx) {
|
|
|
1895
1901
|
// honours it, and the `finally` below clears whatever is left when the leg ends.
|
|
1896
1902
|
const stopFile = stopFilePath(spineFile);
|
|
1897
1903
|
try { rmSync(stopFile, { force: true }); } catch { /* best-effort — a stop surface must never kill the run */ }
|
|
1904
|
+
// Cross-run learnings (src/learn.js): the PLANNER's block, made once from the ledger as it stood — empty when the one setting
|
|
1905
|
+
// (`learningHints` in config.json) is off. A record read, never a verdict: a failure here is no hints, never a failed run.
|
|
1906
|
+
/** @type {{text: string, ids: string[]}|null} */
|
|
1907
|
+
let learnHints = null;
|
|
1908
|
+
if (cfgOn) {
|
|
1909
|
+
try { learnHints = hintsForRun({ home: cfgHome, spec, specHash }); } catch { learnHints = null; }
|
|
1910
|
+
}
|
|
1898
1911
|
let outcome;
|
|
1899
1912
|
try {
|
|
1900
1913
|
outcome = await runJob(spec, {
|
|
1901
1914
|
stopFile,
|
|
1915
|
+
...(learnHints === null ? {} : { learnHints }),
|
|
1902
1916
|
// PRD item 27/M3 — the SAME `emit` the pre-run close-timing reading above
|
|
1903
1917
|
// used (never a second `makeSpine(spineFile)` here: two independent
|
|
1904
1918
|
// emitters against one file would both start their seq counter at 0 and
|
|
@@ -2010,6 +2024,20 @@ async function execute(ctx) {
|
|
|
2010
2024
|
appendRunEvent({ runid, type: 'settled', by: runid, spentUsd: fin.usd, spendComplete: fin.complete, at: new Date().toISOString(), ...(LEG > 1 ? { leg: LEG } : {}) }, { home: cfgHome });
|
|
2011
2025
|
} catch (/** @type {any} */ e) { err(`WARNING: could not settle this run's claim in ~/.config/bareloop/runs.jsonl (${e.message}) — the next run settles it once this process is gone.`); }
|
|
2012
2026
|
}
|
|
2027
|
+
// The run-end tally of cross-run learnings ($0, code only, no model): the whole spine (all legs) and the gate audit become cards
|
|
2028
|
+
// folded into the ONE ledger (a rubric job's are held until the human Accept). Its own try/catch, like the settle above: a
|
|
2029
|
+
// record, never a verdict — a failure prints one WARNING line and can never change the outcome, the exit code or the spend.
|
|
2030
|
+
// `execute` is shared by the panel, the CLI and the bundle runner, so this is the ONE call site. It runs with the hints setting
|
|
2031
|
+
// on or off (OFF only means the planner is handed nothing). With no config home (a test with an injected env) it writes nothing.
|
|
2032
|
+
if (je && cfgOn) {
|
|
2033
|
+
try {
|
|
2034
|
+
const filedAudit = ctx.bundle ? join(spineDir, 'gate-audit.jsonl') : join(spineDir, `u-${runid}-gate-audit.jsonl`);
|
|
2035
|
+
learnAtRunEnd({
|
|
2036
|
+
home: cfgHome, runid, events: allEvents, auditFiles: [filedAudit, join(wd, 'gate-audit.jsonl')], spec, specHash, workdir: wd,
|
|
2037
|
+
trust: quarantinesCredit(spec.verdictType) ? 'rubric' : 'deterministic',
|
|
2038
|
+
});
|
|
2039
|
+
} catch (/** @type {any} */ e) { err(`WARNING: could not record this run's lessons in the learnings ledger (${redactSecrets(String(e?.message ?? e)).split('\n')[0]}) — the run itself is unaffected.`); }
|
|
2040
|
+
}
|
|
2013
2041
|
// the ONE spelling of the text-side scan (src/validate.js) — a hand-rolled copy
|
|
2014
2042
|
// here would be the ninth, and one that misses a shape leaks on the very output
|
|
2015
2043
|
// it guards. Only the COUNT is ever read out; the matches themselves stay here.
|
package/src/validate.js
CHANGED
|
@@ -167,14 +167,19 @@ export function closeScriptCandidateToken(cmd) {
|
|
|
167
167
|
// redactor passes is a leak on the very output the validator was guarding.
|
|
168
168
|
export const SECRET_PATTERNS = [
|
|
169
169
|
/(?<![A-Za-z0-9_-])sk-[A-Za-z0-9_-]{16,}/,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
170
|
+
// The prefixes below are distinctive (an ordinary hyphenated word never starts with `ghp_`, `AKIA`, ...), so their left bound
|
|
171
|
+
// drops only the `-`: a key right after a hyphen (`step-ghp_...`) is still a key. `sk-` keeps the full bound above, because
|
|
172
|
+
// `sk-` ends real words (`task-`, `desk-`, `risk-`...). KNOWN LIMIT: an `sk-` key directly after a hyphen or word character
|
|
173
|
+
// (`step-sk-...`) is not found by THIS inventory, so the scan (validator, source door, pagefetch) misses it. (Measured: the
|
|
174
|
+
// redactor still masks it today, but through bareguard's own built-in `sk-` shape, not through this list.)
|
|
175
|
+
/(?<![A-Za-z0-9_])ghp_[A-Za-z0-9]{20,}/,
|
|
176
|
+
/(?<![A-Za-z0-9_])github_pat_[A-Za-z0-9_]{20,}/,
|
|
177
|
+
/(?<![A-Za-z0-9_])AKIA[0-9A-Z]{16}/,
|
|
178
|
+
/(?<![A-Za-z0-9_])xox[bap]-[A-Za-z0-9-]{10,}/,
|
|
174
179
|
// Google/Gemini API key: `AIza` + 35 chars of [0-9A-Za-z_-] (39 total, fixed
|
|
175
180
|
// length by Google's own issuance format). Admitted with the gemini-api
|
|
176
181
|
// provider (PRD item 31.3, F160) — was missing on this branch until then.
|
|
177
|
-
/(?<![A-Za-z0-9_
|
|
182
|
+
/(?<![A-Za-z0-9_])AIza[0-9A-Za-z_-]{35}/,
|
|
178
183
|
];
|
|
179
184
|
const SECRET_RE = new RegExp(SECRET_PATTERNS.map((r) => r.source).join('|'));
|
|
180
185
|
|
package/src/worktree.js
CHANGED
|
@@ -101,7 +101,8 @@ const COMMIT_CONFIG = [
|
|
|
101
101
|
];
|
|
102
102
|
|
|
103
103
|
/** what never goes into the work commit: the arbiter's own books and installed packages */
|
|
104
|
-
const
|
|
104
|
+
const NOT_WORK_PATHS = ['.smoke', '.litectx', 'gate-audit.jsonl'];
|
|
105
|
+
const NOT_WORK_GLOB = ':(exclude,glob)**/node_modules/**';
|
|
105
106
|
|
|
106
107
|
/**
|
|
107
108
|
* The run's final commit, on whatever branch the worktree stands on: everything the run changed except the
|
|
@@ -113,7 +114,12 @@ const NOT_WORK = [':(exclude).smoke', ':(exclude).litectx', ':(exclude)gate-audi
|
|
|
113
114
|
*/
|
|
114
115
|
export function commitWork(dir, message) {
|
|
115
116
|
const run = (/** @type {string[]} */ args) => execFileSync('git', ['-C', dir, ...COMMIT_CONFIG, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
116
|
-
|
|
117
|
+
// `git add` exits 1 when an exclude names a path that exists AND is gitignored (it prints "paths are ignored").
|
|
118
|
+
// An ignored path is never staged without -f, so its exclude is redundant: only exclude what git would otherwise add.
|
|
119
|
+
// check-ignore: 0 = ignored, 1 = not ignored, anything else = a real failure (execFileSync throws on 128).
|
|
120
|
+
const ignored = (/** @type {string} */ p) => { try { run(['check-ignore', '-q', '--', p]); return true; } catch (e) { if (/** @type {any} */ (e)?.status === 1) return false; throw e; } };
|
|
121
|
+
const excludes = NOT_WORK_PATHS.filter((p) => !ignored(p)).map((p) => `:(exclude)${p}`);
|
|
122
|
+
run(['add', '-A', '--', '.', ...excludes, NOT_WORK_GLOB]);
|
|
117
123
|
const staged = run(['diff', '--cached', '--name-only']).trim();
|
|
118
124
|
if (staged !== '') run(['commit', '-q', '-m', message]);
|
|
119
125
|
return { committed: staged !== '', sha: run(['rev-parse', '--short', 'HEAD']).trim() };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cut `text` to at most `max` bytes at a LINE boundary (never mid-line).
|
|
3
|
+
* @param {string} text @param {number} max
|
|
4
|
+
* @returns {{text: string, truncated: boolean, cutLines: number}}
|
|
5
|
+
*/
|
|
6
|
+
export function cutAtLine(text: string, max: number): {
|
|
7
|
+
text: string;
|
|
8
|
+
truncated: boolean;
|
|
9
|
+
cutLines: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Begin recording one worker try. Call BEFORE the worker's turn; call `finish()` after
|
|
13
|
+
* (in a `finally`). Never throws.
|
|
14
|
+
* @param {{workdir: string, fence: string[], redact?: (s: string) => string}} o
|
|
15
|
+
* `fence` = the ABSOLUTE write-scope prefixes the gate enforces for this try.
|
|
16
|
+
* @returns {Promise<{finish: () => Promise<null | {files: {path: string, diff: string, truncated: boolean, cutLines: number}[], binary: string[], truncated: boolean, gitError?: string}>}>}
|
|
17
|
+
* `finish()` resolves null when the try changed nothing in scope.
|
|
18
|
+
*/
|
|
19
|
+
export function startAttemptDiff({ workdir, fence, redact }: {
|
|
20
|
+
workdir: string;
|
|
21
|
+
fence: string[];
|
|
22
|
+
redact?: (s: string) => string;
|
|
23
|
+
}): Promise<{
|
|
24
|
+
finish: () => Promise<null | {
|
|
25
|
+
files: {
|
|
26
|
+
path: string;
|
|
27
|
+
diff: string;
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
cutLines: number;
|
|
30
|
+
}[];
|
|
31
|
+
binary: string[];
|
|
32
|
+
truncated: boolean;
|
|
33
|
+
gitError?: string;
|
|
34
|
+
}>;
|
|
35
|
+
}>;
|
|
36
|
+
/** per-file and per-event byte caps for the recorded diff text */
|
|
37
|
+
export const ATTEMPT_DIFF_FILE_MAX: 4096;
|
|
38
|
+
export const ATTEMPT_DIFF_EVENT_MAX: 16384;
|
package/types/config.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function modelsProblem(list: unknown): string | null;
|
|
|
36
36
|
* `keys` merges per key variable name, then per field; `models.<ENV>` is REPLACED WHOLE (`null`
|
|
37
37
|
* clears it). Validated before anything is written; nothing is written on a refusal.
|
|
38
38
|
*
|
|
39
|
-
* Patch fields understood: `monthlyLimitUsd` (finite > 0), `keys.<ENV>.{shape,baseUrl,legacyMaxTokens}`,
|
|
39
|
+
* Patch fields understood: `monthlyLimitUsd` (finite > 0), `learningHints` (true | false), `keys.<ENV>.{shape,baseUrl,legacyMaxTokens}`,
|
|
40
40
|
* `models.<ENV>` (a list judged by {@link modelsProblem}). The first write of a row's `models` list
|
|
41
41
|
* removes that row's older `name` / `priceInPerM` / `priceOutPerM` in the SAME write (never two
|
|
42
42
|
* sources); a retired top-level `anthropicBalanceNote` is dropped on any write. Any other field is
|
package/types/inputlook.d.ts
CHANGED
|
@@ -185,29 +185,6 @@ export function labelListsOf(texts: Record<string, string>): {
|
|
|
185
185
|
scans: Record<string, import("./labelscan.js").LabelScan>;
|
|
186
186
|
lists: Record<string, string>;
|
|
187
187
|
};
|
|
188
|
-
/**
|
|
189
|
-
* L3: a reason the AI gave, cut only at a sentence end (never mid-sentence): whole sentences while they fit in `max` characters,
|
|
190
|
-
* and nothing at all if the first one alone does not fit. A trailing full stop is dropped (the caller closes the clause).
|
|
191
|
-
* @param {string} text @param {number} max @returns {string}
|
|
192
|
-
*/
|
|
193
|
-
export function wholeSentences(text: string, max: number): string;
|
|
194
|
-
/**
|
|
195
|
-
* I1 (hamr, 2026-10-13, "Ruled by hamr" 2): when the look shows the person's intent on one `~` rule was NOT captured - its words or
|
|
196
|
-
* meaning are not in the source - the person is asked about THAT rule at once, named by its line and rule. Only a look entry that names
|
|
197
|
-
* a rule number counts; anything else the look could not find is not asked piecemeal (it is Not checked, or the whole job is reviewed).
|
|
198
|
-
* The wording is the code's own, never the model's. The keep-or-drop and the cap are `normalizeQuestions` (src/authorflow.js).
|
|
199
|
-
* @param {LookNotFound[]} notFound @param {{n: number, text?: string, rule?: string, rules?: string[]}[]} jobLines
|
|
200
|
-
* @returns {{line: number, question: string}[]} every candidate question (the caller applies the machine's caps)
|
|
201
|
-
*/
|
|
202
|
-
export function ruleQuestions(notFound: LookNotFound[], jobLines: {
|
|
203
|
-
n: number;
|
|
204
|
-
text?: string;
|
|
205
|
-
rule?: string;
|
|
206
|
-
rules?: string[];
|
|
207
|
-
}[]): {
|
|
208
|
-
line: number;
|
|
209
|
-
question: string;
|
|
210
|
-
}[];
|
|
211
188
|
/** the name of the answer tool - the one place the AI's report enters */
|
|
212
189
|
export const LOOK_REPORT_TOOL: "report_look";
|
|
213
190
|
/** how many hits one search returns at most, and how long a snippet or a read line is */
|
package/types/labelscan.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export const MIN_COUNT: 1;
|
|
|
29
29
|
export const MAX_PER_SHAPE: 40;
|
|
30
30
|
/** only this much of a source is scanned (a source over it is scanned up to a line end) */
|
|
31
31
|
export const SCAN_MAX_CHARS: 2000000;
|
|
32
|
+
/**
|
|
33
|
+
* The page reader's bracketed tag (`[ref=549]`), shared by the three places that strip it. The lookahead and lookbehind add no match and drop no
|
|
34
|
+
* match: a `[` that sits inside the run of an earlier `[letters=` (no `]` or white space between) is skipped, because that earlier start reads the same
|
|
35
|
+
* run to the same end - it matches (and consumes this one) or fails (and so does this one). Without them "[a=[a=[a=..." with no `]` is quadratic.
|
|
36
|
+
*/
|
|
37
|
+
export const TAG: RegExp;
|
|
32
38
|
export type LabelEntry = {
|
|
33
39
|
id: string;
|
|
34
40
|
label: string;
|