bareloop 0.41.0 → 0.42.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/README.md +13 -21
  3. package/bareloop.context.md +44 -4
  4. package/package.json +4 -3
  5. package/src/authorflow.js +337 -29
  6. package/src/authoring.js +92 -7
  7. package/src/authorjob.js +78 -16
  8. package/src/authorreadout.js +89 -5
  9. package/src/authorrun.js +14 -5
  10. package/src/authorscout.js +108 -0
  11. package/src/declaredclose.js +23 -12
  12. package/src/facts.js +317 -0
  13. package/src/factwords.js +129 -0
  14. package/src/inputlook.js +301 -0
  15. package/src/job.js +2 -1
  16. package/src/jobblock.js +7 -2
  17. package/src/kinds.js +84 -3
  18. package/src/labelscan.js +170 -0
  19. package/src/pagefetch.js +125 -0
  20. package/src/panel/authorroutes.js +15 -2
  21. package/src/panel/authorsession.js +99 -22
  22. package/src/panel/index.html +33 -7
  23. package/src/panel/jobplan.js +34 -11
  24. package/src/panel/server.js +19 -3
  25. package/src/planrun.js +3 -2
  26. package/src/rubricstep.js +804 -0
  27. package/src/source.js +166 -1
  28. package/src/u-readout.js +1 -1
  29. package/src/userrun.js +14 -2
  30. package/types/authorflow.d.ts +267 -13
  31. package/types/authoring.d.ts +29 -1
  32. package/types/authorjob.d.ts +7 -5
  33. package/types/authorreadout.d.ts +27 -2
  34. package/types/authorscout.d.ts +70 -0
  35. package/types/declaredclose.d.ts +4 -2
  36. package/types/facts.d.ts +71 -0
  37. package/types/factwords.d.ts +42 -0
  38. package/types/inputlook.d.ts +238 -0
  39. package/types/jobblock.d.ts +8 -1
  40. package/types/labelscan.d.ts +38 -0
  41. package/types/pagefetch.d.ts +71 -0
  42. package/types/panel/authorsession.d.ts +18 -2
  43. package/types/panel/jobplan.d.ts +12 -4
  44. package/types/panel/server.d.ts +4 -2
  45. package/types/rubricstep.d.ts +286 -0
  46. package/types/source.d.ts +94 -0
  47. package/types/userrun.d.ts +11 -0
package/CHANGELOG.md CHANGED
@@ -5,6 +5,68 @@ All notable changes to bareloop are documented here. Format:
5
5
  [SemVer](https://semver.org/spec/v2.0.0.html). Pre-1.0: **minor** = a ladder rung or
6
6
  feature lands, **patch** = docs, fixes, scaffolding.
7
7
 
8
+ ## [0.42.0] - 2026-10-14
9
+
10
+ ### Added
11
+
12
+ - **Rubric jobs on local files, no judge yet (Part 1a).** A Rubric job whose first Input is the full path of a text file (`label: /abs/file`) no longer stops at "not a code project". `proveRubricInputs` (`src/source.js`) freezes each file into the hidden-git tree and records `inputs: [{ label, rel, sha256 }]` in `source.json`; a web address, search line, relative path, symlink, env file, over-5-MiB or non-UTF-8 file is refused at $0 in plain words ("line N: web inputs (URLs, search) arrive in a later build. Nothing spent."). There is no scout and no confirm turn: the worker's goal is written by code from the numbered job lines (`src/rubricstep.js`).
13
+ - **One output per job line.** The worker writes `output/steps/<n>.json` (`{ text, cites: { <label>: [{ claim, quote }] } }`); `output/steps/` is delivered with the output.
14
+ - **New close kind `rubric-step`** (text genre only, `closeDecl.lang: "text"`; a code job never sees it): one stage per job line, graded by bareguard's rubric primitive rebuilt every run from the input shas the source door recorded. Checks: the step file exists and is a JSON object (`happened`), `text` present, `cited` per input the step cites, and `maxWords` / `minWords` / `maxLines` / `sections` / `sectionWords` on `text`. A missing or cut-off file is red; a changed input, an unreadable record or a rubric that cannot be built is an instrument stop (97).
15
+ - **Every part of a job line is sorted at drafting** (countable, judged, guard, none). A judged part ("this rule needs the judge, which arrives in the next build. Nothing spent."), an unsortable part, a guard with no web input, contradicting limits (both parts and numbers named), a stage on the wrong line and a quote from an unknown input are plain-words reds; the drafting and signing stops print them whole.
16
+ - Signing such a job makes zero judge-round records (the calibration gate runs only for a judged stage).
17
+ - **The confirm turn is back for these jobs** (form -> confirm turn -> sign, scout still off). Its protections come from code: the text genre's guard, the write fence and one line per job line saying its step file must exist, be complete JSON and carry text; the model is told what a check can be here and shown the same list as already covered.
18
+ - **A red count for the fix-loop governor.** A red `rubric-step` stage reports failing claims plus failing checks, lower is better, through the same `closeGrade` path numeric stages use.
19
+ - **The panel words a rubric-step check** as "line N — does its output pass the line's checks?" instead of the raw stage name.
20
+ - **Worker brief:** copy every number, time and price in a claim exactly as the input writes it.
21
+ - **The panel topbar shows the version:** `bareloop · v<version> · automate a job, verified`, read from bareloop's own package.json when the page is served (never written into the HTML).
22
+ - **Each `~` guardrail is shown on its own marked row** under its numbered job line on the plan card and the Job tab (fwdloop's shape).
23
+ - **A refused drafting try records why** in `draft-log.jsonl` (`try-refused`) and on the drafting step's progress line.
24
+ - **Required facts (Part 1b): a job line that finds, lists or looks something up is checked by what the person asked for, not what the worker chose to write.** The drafter turns the line and its `~` rules into a signed list of facts (name, shape money / number / date / time / phrase / text, a rule only where asked, the input they come from); the worker's file for that line is `{ results: [{ quote, values }] }` with no free text, and bareguard's `cited` + `decides` checks every result in code: the quote is really in the frozen input, every value is really in the quote (compared by shape), every value meets its rule. The claim text is written by code; "N results" is a count code checks. (`src/facts.js`, `src/rubricstep.js`)
25
+ - **Records. (Removed in Part 1c, below: no record cutting.)** The drafter names where a record opens and where the quoted slice ends; code splits the input and counts the records. The drafter reads the input at drafting (a big one as a said-so sample: the first 15000 characters, cut at a line end, shown on the card).
26
+ - **Three early tests at drafting, on the real input (removed in Part 1c, below: code now only proves the words are in the source),** before a fact check can be signed (`src/factcheck.js`): every word a check depends on is really in the input; the check finds its value in each record (count shown); a rule has one value per record or an anchor; a value changed by code makes the check go red; the pass / fail split of a rule is shown. A run repeats the words / found / one-per-record tests on the source it reads and stops in plain words if it no longer fits.
27
+ - **No silently dropped rule.** Every `~` rule must become a check (its number in a part's `rules`) or be listed under Not checked (`refused` entries may now name a `rule`); a number the AI made out of the person's words is marked "AI's proposal — check it", and an AI time limit must be a range.
28
+ - **Prose after facts.** Every number in a text step must appear in a checked quote of an earlier fact line (bareguard `numbersInQuote`); the card says "what the text says" is not checked and is judged at Accept.
29
+ - **Plan card and worker brief.** One code-worded row per fact (with the early-test evidence) on the confirm card and the Job tab, covered by the sign-time shown == run check; the worker's goal briefs each line by what its stage checks.
30
+ - **A refused drafting try keeps its declaration (Part 1b live fix A3).** The refused proposal rides `author-try-failed` and the panel writes it to `draft-log.jsonl` beside `try-refused` (secrets masked, cut at 60000 characters with `declarationCut`), so a failed draft can be read and replayed at $0.
31
+ - **One rulebook for the confirm step and the drafter (B1).** `RUBRIC_RULES` is the single text both `RUBRIC_CONFIRM_NOTE` and the drafter brief print: a limit the person wrote is a checkable rule; a number made from their words is the AI's proposal and a range; names, labels and units are the input's own words, and a code like `SFO` the person did not write is never added.
32
+ - **The drafter is shown which fact shape takes which fields (B2).** `factShapeGuide` is read off bareguard's `rubricVocabulary` (`factShapeFields` throws at load if bareguard's wording changes) and carried in `REQUIRED_FACTS_BRIEF`, which also says one stage holds all of a job line's facts.
33
+ - **The look, then say what can be checked (Part 1c, docs/product/ITEM33-BUILD.md; PRD item 33 RF18-RF22).** Drafting no longer grades and no longer cuts records. Before the drafter writes a check, a read-only, bounded look (`runInputLook`, `src/authorscout.js`; tools in `src/inputlook.js`) searches the person's frozen input text, reads the spots it finds, and reports per job line the facts it can check - name, shape, a label, one example value it saw - and what it could not find. Fixed grant (search and read over in-memory text, nothing to write), `LOOK_ROUNDS` 8, a reserved final round whose only tool is the strict answer form (`report_look`; never free prose with JSON inside), spend metered into the drafting book like the repo scout's. Every place, date or name in the job line is a fact too, bound to the label before it (a direction is `Leaves` / `arrives at` as a phrase fact); prose lines and their `~` limits are not looked up. The drafter is shown the verified report.
34
+ - **Labels are picked, never typed (`src/labelscan.js`).** Code scans the source at $0 and lists, per shape (money, number, date, time, text), the words right before each value with how often they occur (`Departure time:` x43, `From` x44, `Leaves`, `arrives at` on the saved flights page; `## [` and `] -` on this changelog). The look and the drafter pick a label only from that list, by id or exact words; one scan for every page or file, no per-site table.
35
+ - **Code proves the words are in the source (`src/factwords.js`).** Every anchor must be on the input's list for its shape, and every `until`, phrase and unit must be in the source once, compared white-space blind (Google writes U+202F before AM/PM). A word not found is a red naming the word and the input (`rubric-fact-label-off-list`, `rubric-fact-word-missing`, `rubric-fact-unread`); the look's phrase facts are proved by the same counter.
36
+ - **Web addresses as inputs (`src/pagefetch.js`, `src/source.js`).** An Inputs line may be `label: https://...`. barebrowse (`^0.21.0`, now a production dependency) fetches it read only (`connect` with `incognito`, `blockPrivateNetwork`; `goto`, then a bounded wait for the network to go quiet so the page's own scripts finish filling the results, then an `act` snapshot); an empty, near-empty (under 200 characters), header-and-menus-only (nothing outside the banner / navigation landmarks), bot-challenged or unreachable page is a plain-words stop. Drafting fetches it once for the look; run start (`refreezeUrlInputs`, called from `run-u --spec`) fetches it again, writes that text into the prepared tree as the run's frozen input and commits it as the new seed before the worker starts, so the close checks quotes against the run's copy and the worker reads it, never the live page. A resume keeps the copy its first start froze.
37
+ - **Ask at once about a `~` rule the look could not capture (I1).** When the look names a `~` rule whose words or meaning are not in the source, the person is asked about that rule, by line and rule, before the plan and the draft (the existing question gate: answer required, no skip, the answer becomes a `~` rule on that line; at most `MAX_QUESTIONS`). Any other drafting failure of a rubric job on files asks nothing piecemeal: the stop says to review the whole job and submit it again, with the reasons in plain words.
38
+
39
+ ### Fixed
40
+
41
+ - **A missing earlier step file is a normal red; a rubric job's broken check no longer blames install/build/test commands (Part 1c M2).** When a fact line ties its numbers to an earlier line's answer and that line's step file is not there yet, the grade says "line N's answer is not there yet" instead of raising an instrument fault; a `broken-close` on a rubric job reads "A check of the close could not run" instead of the code-job wording about install, build and test commands.
42
+ - **After Sign & run the Runs list is refreshed until the new run is listed (Part 1c M3).** The list re-fetched only every 10 seconds and the runner writes its row a few seconds after the click, so a signed run showed only after a page refresh; the panel now forces bounded refreshes until a run not listed at the click appears (as a resume already did).
43
+ - **A header-only page stops before the look, and an empty input is never asked about (Part 1c L2).** A fetched page whose text outside the banner, navigation and footer landmarks is under 100 characters is refused in plain words (a 719-character fetch of a site's header and sign-in used to pass the 200-character floor); an input with no content makes the survey absent before the look and before any question, so nothing is asked and nothing is spent.
44
+ - **A fact's words must repeat as often as results were asked for (Part 1c M1, M4).** On a line asking for N results, an anchor label (M1) or a "must say" phrase (M4) that the input holds fewer than N times sits in no single result (a sort tab, a page header), so no one-result quote can hold it. `checkFactWords` refuses it at $0 in plain words naming the words, the input, the count and N (`rubric-fact-label-rare`, `rubric-fact-phrase-rare`); count 1 or no count keeps a once-only one legal. The one rulebook (`RUBRIC_RULES`) tells the drafter. Live runs mv73swnx and mv76jhj1.
45
+ - **Plain words for every rubric red (B3).** `rubric-step-twice` and every `rubric-*` code read as a whole sentence on the proposal door, the signing door and the stop text; `invalid-value` / `missing-field` on a rubric-step's parameters say where (stage, part, fact, field). A test reads the rubric red codes from source and fails on any unmapped one.
46
+ - **The long-record refusal says what to do (A1; removed with record cutting in Part 1c).** `rubric-fact-record-long` names the record, its length, the end marker given (or "no end given") and an example taken from that record.
47
+ - **An end marker the records do not hold is its own red (A2; removed with record cutting in Part 1c).** `rubric-fact-record-end` names the marker and how many records lack it; before, a partial miss fell back to the whole record and read as "too long". A marker that is the first thing after a record's start (it opens the next record too) counts as found.
48
+ - **Drafting wording.** The drafting line says `drafting (retry N of M)` only after a try that was actually refused, and `drafting (look N of M)` after one that passed (the loop drafts again after a passed try too). A refused try's reason, and the drafting stop texts, no longer end "Nothing spent." — a drafting try always paid for its model call.
49
+ - **The plan card showed the drafter's prose (run mv5e1ktb).** The confirm turn's bubble stayed above the code-worded card and was the one read. After drafting the earlier plan bubbles are dropped, so the card is the signed close's own sentences. "at most 1 lines" now reads "1 line" (and "1 word").
50
+ - **The review door's held line said "rendered by the judged floor" for a rubric close where no judge ran.** Wording only: the hold is by verdict class and is unchanged.
51
+
52
+ - **A check shown at the confirm turn could be dropped from the signed close without a word (run mv5bk400).** The composer put "lists 3 releases" in the close's notes and wrote no stage for it. Now `minClaims` / `maxClaims` count the claims listed under an input (the code counts, bareguard's `min`/`max` compares), parts say which confirmed checks they carry out (`covers`), the plan card after drafting is built from the signed close itself, every confirmed check no part carries out is moved to Not checked in code, and signing refuses when the checks shown differ from the checks of the close.
53
+ - **Wrong labels on a rubric job on files:** the close carries genre `TEXT` (not `TYPES`), its records carry no `judgedCount` (no judge ran), and the progress list says "copying input files" / "reading input files" instead of "making worktree" / "reading repo".
54
+ - **The panel could not start a rubric job with two or more local-file Inputs:** `POST /api/author/start` asked the repo proof of every card and reded line 1 ("is not a folder"). A rubric card whose first input is a local file now skips it; the session proves each line.
55
+
56
+ - **The panel shows a refused Start (Part 1c item f).** The $0 refusal that `/api/author/start` returns (an Inputs line, a Destination) was drawn as a faint 11px hint under the cap fields and never scrolled into view, so it read as nothing happening; it is now red, 13px and brought into view, like the progress list's failure line. The failed-draft refusal of a pasted web address that cannot be read no longer ends in two full stops.
57
+ - **A fact line's `~` rule is carried by its fact part, and `covers` stays on the part (Part 1c L4).** Live drafts of the flights job (panel session smv710w8c3wdh, 3 of 3 tries) were refused on form: `covers` written on every fact, and "not a red-eye" / "nonstop" sent as a judged or unsortable part beside the fact part. The one rulebook (`RUBRIC_RULES`, `REQUIRED_FACTS_BRIEF`) now says a limit the AI turns into a number goes on the time the input holds, as a `between` range marked AI's proposal, inside the fact part; a rule it cannot express goes in `refused`, never a part; and the refusals (`src/facts.js`, `src/rubricstep.js`) say where each belongs.
58
+ - **The plan card keeps the person's words (Part 1c L3).** Code settles it before anything is validated or signed (`settlePersonsWords`, `src/rubricstep.js`): a limit made only of the person's own numbers is not marked AI's proposal; a time window the AI proposed is replaced by the one explicit positive range the person wrote or answered ("between 07:00 and 23:00"; a "not between ..." rule is never read as a range); a `~` rule that a part carries is not also listed under Not checked. The look carries a rule it can number from its own knowledge ("not a red-eye") as a fact with `forRule` marked AI's proposal instead of reporting it not found, so it is not asked; and the early question's reason is cut only at a sentence end (`wholeSentences`), never mid-sentence.
59
+
60
+ ### Changed
61
+
62
+ - **The plan card legend and fold wording** (chat plan card and Job tab): the legend reads "Done when every › check passes" and the mandatory-guards fold is "Always checked" (was "= the approved plan" and "Always on"). Words only.
63
+
64
+ - **barebrowse `^0.21.0` is a production dependency** (bare suite, Node 22). bare-agent keeps its own optional nested `barebrowse ^0.5.0`; the two do not conflict in a clean install.
65
+ - **bareguard `^0.21.0` -> `^0.23.0`** (`cited` decides with typed values, bind-only and `text` decides, anchors match on word edges only, a `cited` claim carrying `values` with no `decides` is red).
66
+ - **bareguard `^0.23.0` -> `^0.24.0`** (a money fact's `currency` may be one symbol or a list of 1-3 and `unit` one string or a list of 1-10, at least one; tokens with the same amount count as one price; a revoked-Proxy claim value is red `claim-value-malformed`; `rubricVocabulary` currency/unit descriptors carry `list:{max}`). bareloop still sends one `unit` or one `currency` per money fact (`src/facts.js`), so the lists are available but not yet used.
67
+ - **`~ PASS:` / `~ FAIL:` examples are required of a rubric job only when it holds a judged part** (`jobBlockFitsCheck(lines, rubric, { judged })`, hamr's ruling P1=B). A rubric on a local file holds none in this build; a rubric on a repo still needs them.
68
+ - **Node 22 or newer is required** (`engines.node` `>=22`, CI on Node 22). barebrowse, which the coming rubric `search` step drives, needs Node 22; Node 20 is past end-of-life.
69
+
8
70
  ## [0.41.0] - 2026-10-12
9
71
 
10
72
  ### Added
@@ -47,7 +109,7 @@ feature lands, **patch** = docs, fixes, scaffolding.
47
109
  - **Try-3 questions.** On the last drafting try, after two failed ones (validation red, seed-read failure or vacuous close, or an output cut-off), the drafter may ask up to 2 questions, each naming a job line. The panel asks each through the ordinary ask path (answer required, no Skip), adds the answer word for word as a `~` rule on that line, and drafts once more with no asking. Off unless the caller wires an asker (the CLI never does).
48
110
  - **Thinking switch for drafting and the judge.** `thinkingMode` ('on' | 'off', anything else throws) is asked of bare-agent; `off` is sent only for models bare-agent's `thinkingSupport` says are switchable, and nothing is sent for a not-switchable or unverified model. The draft log records `thinking` (off / on / not-switchable / unverified) and bare-agent's `thinkingApplied` verdict per call.
49
111
  - **Panel.**
50
- - One shared plan renderer draws the chat bubble and the Job tab: numbered lines, rules, checks under the line they serve, with *Not checked* and *Always on* folds; an "AI's summary" row, and an "AI's open questions" row (each question its own item, then "Revise if you need to.") shown only when there are questions.
112
+ - One shared plan renderer draws the chat bubble and the Job tab: numbered lines, rules, checks under the line they serve, with *Not checked* and *Always checked* folds; an "AI's summary" row, and an "AI's open questions" row (each question its own item, then "Revise if you need to.") shown only when there are questions.
51
113
  - The Job tab runs The job, Inputs, Destination, Tools, Cap, Signed, with the job at full height; older jobs show an older-job note and keep their goal as line 1.
52
114
  - The job boxes grow to 8 lines then scroll, with the number gutter scrolling alongside; the left pane widens to 630px from 1200px wide.
53
115
  - The drafting list opens with a code-owned *thinking* row; if the model reports it thought anyway after `off` was sent, it reads "thinking on (the model thought anyway)".
package/README.md CHANGED
@@ -18,33 +18,25 @@
18
18
 
19
19
  **"Automate this job — I don't know the best workflow."**
20
20
 
21
- For work that is repeated, long, and verifiable: an agent authors the workflow scaffolding
22
- itself, as a constrained and validated config rather than freeform code. Every run executes
23
- under an outer gate the agent cannot reach or argue with. And the workflow gets better across
24
- runs, because what actually worked is kept with receipts and carried into the next run.
21
+ The best workflow is the shortest proven path that gets the job done, and that heals itself when a different model works a different way.
25
22
 
26
- Agents got good at doing the work. The open problem is knowing when the work is actually
27
- done. Three of the strongest results in long-running agent work landed on the same answer
28
- independently: the ralph loop, code machine MCP, and recursive language models all trust the
29
- agent to do the work and verify it with something the agent cannot reach. Trust, but verify.
23
+ bareloop finds that path for you. Write your problem in plain language, as steps. Under each step, say what counts as success and what it must never do. The agent builds the bridge it will walk on, and the run is green only when the output passes checks the agent never touches.
30
24
 
31
- bareloop takes that principle and makes it the product. You describe a repeated, verifiable
32
- job and what *done* means for it. The agent handles the rest, under verification it never
33
- gets to touch.
25
+ The harness (budget, time, what it may touch) and the judge both sit **outside** the agent loop. The agent can't judge itself. Every run is recorded, so you can audit it and replay it at $0 and see exactly what happened.
34
26
 
35
- ## Two kinds of done
27
+ ## Two kinds of jobs
36
28
 
37
- Jobs differ by what decides the result is good. That difference is the product's spine, so you
38
- choose it when you create the job.
29
+ **Deterministic (code)**: code decides. Tests pass, the linter is quiet, the build exits clean. If no check exists yet, say what you need and the agent builds that check first.
30
+ > *"Fix the lint errors."* · *"Add doc comments to every function."* · *"Split this 3,000-line file without breaking a test."*
39
31
 
40
- **green** — a predicate decides. Tests pass, the build exits clean, the type checker is
41
- quiet.
42
- > *"Fix the failing suite."* · *"Migrate this package to strict types."*
32
+ **Rubric (research)**: it searches, collects, compares and hands you a report where every fact is quoted from a saved copy of its source. A judge only points at the quotes, and fixed rules decide.
33
+ > *"Find flights LAX→SFO under $400, nonstop, no red-eye."* · *"Compare these 5 laptops on price, weight and warranty."*
43
34
 
44
- **softgreen** — an assessment decides, against a rubric you wrote and signed. You say what
45
- separates a pass from a fail, and give one example of each; the judge only quotes back what it
46
- finds in the real work, and fixed rules — not the judge — render the verdict.
47
- > *"Reserve a ticket from SF to LA under $400."* · *"Improve my resume."*
35
+ ## What it won't do
36
+
37
+ bareloop reads, searches and writes. It never acts on the world: no paying, booking, sending forms or posting. When a job needs a person partway through, or needs to *do* something, like book the flight bareloop found, that's **[fwdloop](https://npmjs.com/package/fwdloop)**.
38
+
39
+ **Small tools, all local.** Built on the bare suite (bareagent, bareguard, litectx), always LLM-agnostic, always on your machine, with a simple panel UI.
48
40
 
49
41
  ## You have the last word
50
42
 
@@ -721,7 +721,7 @@ attempt PLUS its close. Unpriced is never free: a null cost is a `pricing-red` s
721
721
  never retried, and the ladder is otherwise ONE retry (`JUDGE_ATTEMPTS`) — after that a broken
722
722
  judge is an instrument stop, never a red about the tree.
723
723
 
724
- **The rulebook, and what each rule can raise** (`JUDGE_RULES` in `src/judged.js`; every rule carries a plain-words `means` the case-proposal prompt renders, so a proposed practice case never expects a red the rule cannot raise). The judge only POINTS (quotes lines verbatim); code does every check; unsure is red; the judge never says pass/fail. Quotes are matched by bareguard's `quoteIn(..., {wholeLines:true})` (bareguard `^0.21.0`): a contiguous run of whole artifact lines, so a bare fragment cannot pass by hiding in a longer line, while a dropped leading `* ` of a JSDoc tag line still matches. `has-doc` checks only that a JSDoc block sits directly above the function (read off the artifact by `docBlockAbove`; the judge's quote is a pointer that must lie inside that block), so a documented function never breaks it. **`says-what`** (the doc's description must say more than the function's own name) reds when the described prose, minus a fixed list of function words and the name's own words (camel/snake/kebab, light inflections, and a prefix echo such as `init`/`initialize` when the shorter word is at least 4 letters), leaves nothing; no description, or an unlocatable quote, reds. It catches a pure name echo only ("Returns the total." for `getTotal` passes), and with NO doc block at all it stays silent and leaves the red to `has-doc` (the card is not required to carry both). `params` reds a declared parameter with no tag, an `@param` naming no parameter (each destructured slot absorbs one unmatched root tag), and, when the doc names any field of a simple destructured parameter, a missing field; `@param options` alone for `{ x, y }` passes. `returns` reds a returning function with no `@returns`. A practice case that expects a `has-doc` red on a function that has a block is refused as a legality red before any call (`validateCalibrationSet`).
724
+ **The rulebook, and what each rule can raise** (`JUDGE_RULES` in `src/judged.js`; every rule carries a plain-words `means` the case-proposal prompt renders, so a proposed practice case never expects a red the rule cannot raise). The judge only POINTS (quotes lines verbatim); code does every check; unsure is red; the judge never says pass/fail. Quotes are matched by bareguard's `quoteIn(..., {wholeLines:true})` (`wholeLines` arrived in bareguard 0.21; bareloop now needs `^0.24.0`): a contiguous run of whole artifact lines, so a bare fragment cannot pass by hiding in a longer line, while a dropped leading `* ` of a JSDoc tag line still matches. `has-doc` checks only that a JSDoc block sits directly above the function (read off the artifact by `docBlockAbove`; the judge's quote is a pointer that must lie inside that block), so a documented function never breaks it. **`says-what`** (the doc's description must say more than the function's own name) reds when the described prose, minus a fixed list of function words and the name's own words (camel/snake/kebab, light inflections, and a prefix echo such as `init`/`initialize` when the shorter word is at least 4 letters), leaves nothing; no description, or an unlocatable quote, reds. It catches a pure name echo only ("Returns the total." for `getTotal` passes), and with NO doc block at all it stays silent and leaves the red to `has-doc` (the card is not required to carry both). `params` reds a declared parameter with no tag, an `@param` naming no parameter (each destructured slot absorbs one unmatched root tag), and, when the doc names any field of a simple destructured parameter, a missing field; `@param options` alone for `{ x, y }` passes. `returns` reds a returning function with no `@returns`. A practice case that expects a `has-doc` red on a function that has a block is refused as a legality red before any call (`validateCalibrationSet`).
725
725
 
726
726
  **A judged stage may only judge files inside the job's write fence** (`judgedOutsideFence`, `src/authoring.js`, called from `validateDeclaration`, which every door reaches: the authoring revise loop, `validateCloseDecl` at signing and runner pre-flight, and `validateJob` for hand-written or reused specs). A path outside `writeScope` reds `judged-outside-write-scope` at draft time, because a finding on a file the run is fenced out of can never be fixed. With no fence to compare against (the plain-folder deferral) the rule is not run.
727
727
 
@@ -3668,7 +3668,7 @@ key you want first (the panel picks the row by the Model menu).
3668
3668
  **The progress list (2026-10-04)** — `GET /api/author/:id` state carries `steps: [{id, label, status:
3669
3669
  'running'|'done'|'failed', detail}]`, one entry per pipeline step in first-seen order, updated in place (ids/labels:
3670
3670
  `STEP_LABELS`, `src/panel/authorsession.js`); `progressLabel` is the running step's label. A drafting call after the first
3671
- relabels the ONE drafting line `drafting (retry N of M)` (same line, never a second one). A refusal sets
3671
+ relabels the ONE drafting line `drafting (retry N of M)` after a refused try, `drafting (look N of M)` after a passed one (same line, never a second one). A refusal sets
3672
3672
  `state.error` and fails exactly one step with the same text as its `detail`; pipeline text is never a chat message
3673
3673
  (`messages` holds person/model turns only). Two last steps, `hash` ("generating hash", detail `spec hash <full hash>`, done at sign-prepare) and `signed` ("signed hash", pushed by `signRun` when the sign is accepted); the old "SIGNING PREPARED" / "signed — … run starting detached" thread bubbles are gone. The page draws each step's `detail` on its own `> ` line under the step. Destination follows the Source: for a repo source it is the write
3674
3674
  fence (relative `writeScope` globs, never proven as a directory); for a folder source an absolute directory. Both
@@ -4319,7 +4319,7 @@ blast radius.
4319
4319
 
4320
4320
  ## Constraints
4321
4321
 
4322
- - **Node >= 20** (bareguard's floor governs the suite).
4322
+ - **Node >= 22** (`engines.node`; barebrowse, a production dependency since Part 1c, needs it).
4323
4323
  - **Pure ESM + JSDoc**; generated `.d.ts` ship, never hand-written (LIBRARY_CONVENTIONS §2).
4324
4324
  - **Secrets load from the environment** and never enter the spine, configs, or ledger —
4325
4325
  an append-only record that captures a key captures it forever.
@@ -4330,7 +4330,47 @@ blast radius.
4330
4330
 
4331
4331
  Goal, Success, Guardrails and Judge examples are one box, **The job**, and Source is **Inputs** (CLI interview and panel card alike).
4332
4332
 
4333
- - **The job:** each plain line is numbered 1, 2, 3 (the number sits in a gutter outside the box). A `~` line has no number and belongs to the numbered line above; consecutive `~` lines join into ONE rule with `'; '`. A `~` line starting `PASS:` / `FAIL:` is a judge example (a rubric check needs at least one of each; a deterministic check refuses them); any other `~` line is a guardrail. A `~` before line 1 is a $0 red. Empty lines are ignored. `parseJobBlock(text)` → `{ ok, lines: [{ n, text, rule, examples }] }` or `{ ok:false, error }`; `jobBlockFitsCheck(lines, rubric)` is the Check-type cross-check.
4333
+ - **The job:** each plain line is numbered 1, 2, 3 (the number sits in a gutter outside the box). A `~` line has no number and belongs to the numbered line above; consecutive `~` lines join into ONE rule with `'; '`. A `~` line starting `PASS:` / `FAIL:` is a judge example (a rubric check that holds a judged part needs at least one of each; a rubric on a local file does not; a deterministic check refuses them); any other `~` line is a guardrail. A `~` before line 1 is a $0 red. Empty lines are ignored. `parseJobBlock(text)` → `{ ok, lines: [{ n, text, rule, examples }] }` or `{ ok:false, error }`; `jobBlockFitsCheck(lines, rubric, { judged? })` is the Check-type cross-check.
4334
4334
  - **Inputs:** one `label: value` per line, numbered. Line 1 = the repo (absolute path). Lines 2+ = files or folders inside that repo, repo-relative; `proveInputFiles(inputs)` (`src/source.js`) proves them at $0 before any token — exists, inside the repo, tracked, and (files) not an environment file by name, not a symlink, text, under the size ceiling, a known secret shape in a file's content is MASKED wherever bareloop records it, never refused (the P6 worktree door ruling; `.env` by name and symlinks stay refused). A miss is `Inputs, line N: ...`. Search lines and files outside the repo are `only files inside the repo for now`. The panel proves them in `POST /api/author/start` (400 naming the line); the CLI re-asks.
4335
4335
  - **Drafter:** every stage carries `fromLine`, or the line is listed in `closeDecl.refused` with a reason (see `closeDecl` above); the confirm turn's checks are `{ text, fromLine: [N, ...] }`; the plan draws a multi-line check under EACH line it serves.
4336
4336
  - **Panel:** the card is `{ jobName, checkType, provider, model, thinking, jobText, inputs, destination, capUsd, maxWallMs }` (`provider` = the picked key's env name, `thinking` = the "Thinking on" tick). The confirm plan is a structured chat message `{ role: 'bot', kind: 'plan', plan }` and the Job tab reads the SIGNED spec through the same model (`src/panel/jobplan.js`: `planFromConfirm`, `planFromSpec`); the page draws both with one `renderJobPlan()`. `GET /api/runs/:id/job` returns `plan`, `jobLines`, `inputs`, `specHash`, `signedAt` (the run's FIRST `job-start`) and `signed` (`YYYY-MM-DD HH:MM · <first 6 of specHash>`, local time). An older spec (no `jobLines`) renders the signed goal as line 1 with its stage names as checks and the note `older job — numbered lines were not saved`; Reuse / Edit / Resume prefill the same fallback.
4337
+
4338
+ ### A rubric job on your own local files — no judge yet (Part 1a, `src/rubricstep.js`, `docs/product/ITEM33-BUILD.md` "Addendum 2026-10-12")
4339
+
4340
+ A **Rubric** job whose first Input is the full path of a FILE (not a repo folder) works on your local text files; there is no repo, no language and no scout. Each Inputs line is `label: /absolute/file`.
4341
+
4342
+ - **Door.** `proveRubricInputs({ inputs, into, destination? })` (`src/source.js`) proves every line at $0 and freezes the files under `input/` of the same hidden-git tree a plain folder gets; `source.json` also records `inputs: [{ label, rel, sha256 }]` (hashed once, there). Refused in plain words naming the line and ending "Nothing spent.": a web address or a `search` line ("line N: web inputs (URLs, search) arrive in a later build. Nothing spent."), a relative path, a missing file, a folder, a symlink, an environment file, a file over 5 MiB (`RUBRIC_INPUT_MAX_BYTES`), text that is not valid UTF-8, a repeated label, a known secret shape. `isRubricFilesManifest(manifest)` tells this manifest from a repo's. Destination is the full path of a folder (the finished files are copied there on a minted green); a relative one is refused at $0.
4343
+ - **No examples needed.** `jobBlockFitsCheck(lines, rubric, { judged })`: the `~ PASS:` / `~ FAIL:` lines are required of a rubric job ONLY when it holds a judged part. A rubric on a file holds none in this build, so a job without examples is fine (written examples are kept and unused until the judge arrives). A rubric on a repo still needs them.
4344
+ - **One output per job line.** The worker writes `output/steps/<n>.json` for job line n: `{ "text": "...", "cites": { "<input label>": [ { "claim": "...", "quote": "..." } ] } }`. The worker's goal is written by code (`rubricGoal`) from the numbered lines and the frozen input paths, and states this contract (`stepFileContract`, one text shared with the composer's prompt). `output/steps/` is delivered with the rest of `output/` (`copyOut`: `steps/<n>-YYYY-MM-DD.json` under Destination).
4345
+ - **The close.** The text genre (`closeDecl.lang: "text"`, `RUBRIC_LANG`; `DECLARABLE_LANGUAGES` = the code languages plus `text`) has two kinds: the mandatory `changed-from-seed` guard and `rubric-step`, one stage per job line (`params: { step: n, parts: [...] }`, `fromLine: [n]`). A code job never sees `rubric-step` (`catalogueForLang`). `rubric-step` builds bareguard's rubric at every run (input shas from `source.json`) and grades the step file: `happened` (it exists and is a JSON object), `text` present, then the countable limits on `text` (`maxWords`, `minWords`, `maxLines`, `sections`, `sectionWords`) and `cited` per input the step cites (every quote found word for word in that input, spacing and `**` ignored; every number in a claim in its quote). A missing, cut-off or non-object file is a RED; an input that no longer matches its frozen sha, an unreadable `source.json`, a file over 5 MiB or a rubric that cannot be built is an INSTRUMENT STOP (exit 97). A step file cut inside valid JSON cannot be told from a short answer; that limit is stated, not hidden. A symlinked step file is red, never followed.
4346
+ - **Every part is sorted at drafting** (`class`: `countable` | `judged` | `guard` | `none`), and `checkRubricParts` reds in the person's words: `line N: this rule needs the judge, which arrives in the next build. Nothing spent.`; `line N: '<part>' can't be counted or checked against a quote. Say it as a count (words, sections, rows) or as something a quote must show.`; a guard (`line N: '<part>' is about web sources, and this job has no web input. Nothing spent.`); contradictions name both parts and both numbers; a stage must serve exactly its own line; a `cited` part must name one of the job's input labels. These codes are `rubric-*` and print whole in the drafting and signing stops (`draftStopText`, `signingStopText`).
4347
+ - **Signing is $0.** `prepareSigning` runs its paid calibration gate only for a close with a judged stage; a rubric-step close has none, so signing makes zero judge-round records. Not in this build: the judge, the final human accept, web inputs and search, chips (see the addendum's deferred list).
4348
+ - **Confirm turn (ruling 1A).** A rubric job on local files runs the same form -> confirm turn -> sign flow a repo job has (one confirm turn, two rounds at most; the scout stays off, the survey is the numbered inputs). `authorCloseForJob` runs it when `isRepo` or `lang === 'text'`. The protections shown are code's, never the model's: `confirmProtections({ verdictType, lang, writeScope, jobLines })` lists the text genre's guard (`changed-from-seed`), the write fence and, for every numbered line, `line N file — output/steps/N.json must exist, be complete JSON, and carry text`; the same list is fed to the model as ALREADY COVERED, and `RUBRIC_CONFIRM_NOTE` tells it a check here is a counted limit or a claim backed by a quote (anything needing judgement goes in `notChecked`). The confirmed plan reaches the composer, and its goal sentence rides in the worker goal as "The plan the person confirmed". `POST /api/author/start` skips the repo proof of Inputs for a rubric card whose first input is a local file (`looksLikeLocalFile`); the session proves each line.
4349
+ - **Fix-loop number.** A red `rubric-step` stage puts `detail.redCount` on its result: failing claims under a `cited` check (bareguard's `measured`) plus 1 per other failing check; `trendValueOf` hands it to the governor as `closeGrade` `{ stage, value }`, lower is better (no prose is parsed).
4350
+ - **Panel wording.** `stageQuestionText` words a rubric-step stage "line N — does its output pass the line's checks?". The worker brief also tells the worker to copy every number, time and price in a claim exactly as the input writes it ("9:27 AM", not "09:27").
4351
+ - **Shown == run (run mv5bk400).** The plan card after drafting is built from the signed close (`checksOfClose`, code-written sentences), not from the confirm turn's prose. A countable part may list the confirmed checks it carries out in `covers` (the composer prompt numbers them `#n`); `reconcileRubricPlan` moves every confirmed check no part covers to Not checked ("... — nothing in this close counts it or checks it against a quote"). `signRun` calls `planMatchesClose(session.state.finalPlan.checkItems, closeDecl)` and refuses, spawning nothing, when the two differ. `minClaims` / `maxClaims` (parts with `value` + `source`) count the claims listed under an input: `flattenStep` takes the count from the file's own `cites.<label>` list as the field `claims:<label>` and bareguard's `min`/`max` compares it. A free-text note is never a check.
4352
+ - **Labels.** A text close carries `genre: "TEXT"` (`genreForLang`; `TYPES` is for code languages, and a genre must match its language); its spine records carry no `judgedCount` (no judge ran); the progress list words a file job's steps from `FILE_JOB_STEP_LABELS` ("copying input files", "reading input files").
4353
+ - **Panel topbar.** `bareloop · v<version> · automate a job, verified`: the version is bareloop's own package.json, templated into the page at serve time (`__BARELOOP_PANEL_VERSION__`).
4354
+ - **After drafting, the code-worded card is the only plan on screen (run mv5e1ktb).** The confirm turn's plan bubble is the drafter's own prose (written before any close existed) and used to stay in the thread above the code-worded card; when the rubric card is ready the earlier `kind:'plan'` messages are dropped. `partSentence` now reads "1 line" / "1 word" for a count of one.
4355
+ - **Each `~` guardrail is its own row on the plan card.** `planFromSpec` / `planFromConfirm` lines carry `rules: string[]` (the line's own `rules`, else the joined `rule` split on "; "); `renderJobPlan` draws one `~ rule` row per rule under its numbered line, in the plan card and the Job tab alike (one renderer). Gutter numbers stay on job lines only.
4356
+ - **The review door's held line is true for a no-judge close.** Credit is held by VERDICT CLASS (`quarantinesCredit`: soft-green), whether or not a judge ran; the door now says "this is a rubric (soft-green) job, so its green earns NO reuse and NO learning credit until you accept it, whether or not a judge ran". Wording only; `bridges.js`/`reuse.js`/the Reuse refusal still say "rendered by the judged floor".
4357
+ - **A refused drafting try records why.** `authorClose` emits `onPhase('author-try-failed', { call, i, of, reds | reason, declaration? })` for a try refused by validation (the scrubbed reds), by a vacuous or stopped seed read (a `reason`), or by an all-cut-off reply; the panel writes `{kind:'try-refused', no, call, reason, reds?, declaration?}` to `draft-log.jsonl` (`tryRefusedText`, `src/authorreadout.js`, the code's own red details) and shows `<call>: refused — <reason>` on the drafting step's detail line. The CLI prints it via `phaseLine`. The refused declaration itself rides beside it (`declaration`, secrets masked; `declarationText` + `declarationCut` when over 60000 characters), so a failed draft can be read and replayed at $0 (`declarationRecord`).
4358
+
4359
+ ### Required facts — a line that finds, lists or looks something up (Part 1b, `src/facts.js`, `docs/product/ITEM33-BUILD.md` "Addendum 2026-10-13" and "Part 1c", PRD item 33 RF1–RF22)
4360
+
4361
+ Check what the person asked for, not what the worker chose to write. bareloop needs bareguard `^0.24.0` (`cited` + `decides`, bind-only and `text` decides, word-edge anchors; 0.24 lets money currency/unit be lists, ruling #65, but bareloop facts still carry one form).
4362
+
4363
+ - **The part.** A `rubric-step` stage's `params.parts` may hold ONE part of `class: "fact"` (a line that finds, lists or looks up): `{ text, class: "fact", source: <input label>, facts: [{ name, shape, unit? | currency?, phrase?, anchor?, until?, rule?, aiProposal? }], count?: { min?, max? }, rules?: [n], covers?: [n] }`. `shape` is `money | number | date | time | phrase | text`; `rule` is `{ op: lt | lte | gt | gte, value }` or `{ op: "between", lo, hi }` (not on `text` or `phrase`); `money` names exactly one of `unit` (the input's own words, e.g. `"US dollars"`) or `currency` (`$ € £`); `phrase` carries the fixed `phrase` the quote must contain; `anchor` / `until` are labels copied VERBATIM from the input. A fact name is letters, spaces, `_` or `-` only (it is written into the claim text bareguard scans for numbers). A line with a fact part has no word, section or claim-count parts (a drafting red). Code fills `tested` (the early-test evidence) and `sample` (what the drafter saw of a big input) into the signed part; a model-written one is replaced.
4364
+ - **The step file.** `output/steps/<n>.json` of a facts line is `{ "results": [ { "quote": "...", "values": { "<fact name>": "<value as the quote writes it>" } } ] }` — no free text, no `cites`. ONE quote per result holds all its facts, so a worker cannot mix a price from one result with a time from another. `buildStepSpec` makes one bareguard `cited` check with `decides` (the fact's index is its `d<j>` key; a phrase fact is a presence decide and takes no value). The claim text is written by CODE from the values (`name: value; ...`); a `count` becomes `min` / `max` on the number of results. Every value is compared by shape and exactly (`8:15 AM` equals `08:15`, `389` equals `389.00`); a red names the result and the fact in plain words (`plainFactItem`). A JSON number in `values` is read as the string it prints as.
4365
+ - **No record cutting (Part 1c).** A fact part carries no `record`, `tested` or `sample` (a `record` field is an unknown-field red). The worker's brief says "one quote per result, copied from that result's own text" (bareguard's quote cap of 4000 characters stays its own). Drafting does not grade: it only proves each fact CAN be checked (below).
4366
+ - **Repeat rule (Part 1c M1, M4).** On a fact line asking for N results (`count.min`), `checkFactWords` (`src/factwords.js`, the one owner) refuses at $0 an anchor label the label list counts fewer than N times (`rubric-fact-label-rare`) and a `phrase` fact whose words `countPhrase` finds in the input fewer than N times (`rubric-fact-phrase-rare`); count 1 or none keeps a once-only one legal. `RUBRIC_RULES` states both.
4367
+ - **The look, then the draft (Part 1c).** For a rubric job on files, `authorCloseForJob` runs `runInputLook` (`src/authorscout.js`; pure parts in `src/inputlook.js`) on the frozen input text BEFORE the confirm turn and the draft, when a `provider` is present (`lookFn: null` = no look; a panel test that replaces `generate` gets none unless it passes `lookFn`). Read-only by construction: tools `search_input` / `read_input` over in-memory text, `LOOK_ROUNDS` 8, a reserved final round whose only tool is the strict answer form `report_look`, spend metered into the drafting book (`calls` absorbed like the repo scout's). Its report (facts with a label picked from the list, prose lines, not-found entries) is verified by code (`verifyLookReport`: a label off the list, an unknown shape/line/input, or a phrase not in the source is moved to not-found, named) and rendered into the drafter's prompt (`renderLookReport`); a failed or reportless look is an ABSENT survey and stops drafting at $0 (`scout-absent`, "the first look at your repo or input did not complete"). Every place, date or name in the job line is a fact too, bound to the label before it (a direction = phrase facts `Leaves <place>` / `arrives at <place>`); prose lines and their `~` limits are not looked up.
4368
+ - **Labels are picked, never typed (`src/labelscan.js`).** `scanLabels(text)` lists, per shape (money, number, date, time, text), the words right before each value with counts (top 40 per shape; one generic scan, no per-site table; a page reader's `[ref=N]` tags are ignored). `labelOnList(scan, shape, idOrText)` is the one test (id or exact words, white-space blind). The look and the drafter pick labels only from the list shown in the prompt (`renderLabelList`). Fact-line reds are plain sentences; the confirm step and the drafter share one rule text (`RUBRIC_RULES`), and the drafter's per-shape field guide is generated from bareguard's `rubricVocabulary`.
4369
+ - **Found-once check (`src/factwords.js`, `checkDeclarationWords`, run after validation in `authorClose`).** Every anchor must be on the input's label list for its shape, and every `until`, phrase and unit must be in the source once (white-space blind: U+202F, U+00A0; phrases whole-word, case-blind). A miss is a red naming the word and the input: `rubric-fact-label-off-list`, `rubric-fact-word-missing`, `rubric-fact-unread` (an input that cannot be read is a red, never a skipped check). Nothing is graded; finding and grading are the run's and the close's job.
4370
+ - **No silently dropped rule (RF16).** When a close holds a fact part or any part with `rules`, every `~` rule of every job line must be carried out by a part (its number in the part's `rules`, 1 = the first `~` rule) or listed in `refused` as `{ line, rule, reason }`, or its whole line refused; a rule that is none is a $0 red (`rubric-rule-dropped`). `refused` entries may now carry `rule`. A number the person did not write (a bound not found in the line's own words) must be marked `aiProposal: true` (the card says "AI's proposal — check it"), and an AI-proposed time limit must be `between`, never one-sided (RF17).
4371
+ - **The prose step.** A line that writes keeps its word / section / line counts. When any fact line comes BEFORE it, code puts one `numbersFromFacts` part on it (`withFactBindings`; the model never writes it, and a missing or wrong one is a drafting red): every number in `text` must appear in a quote of those fact lines' step files (bareguard's `numbersInQuote`: token match, not canonical, so `v0.41.0` is one token). The card says, under Not checked, `line N: what the text says — you judge it at Accept`. At the close, a tied earlier line whose step file does not exist yet (the seed read, before any worker wrote) is a normal red ("line N's answer is not there yet"), never an instrument stop; only an unreadable file is a stop.
4372
+ - **The plan card.** One code-worded row per fact under its line, read by the one reader (`checksOfClose` / `factRows`) the confirm card, the Job tab (`planFromSpec`) and the sign-time `planMatchesClose` share, e.g. `fact · price — money in US dollars, at most 450 US dollars, after "Price", quoted from "Flights"`, then the count row. A `~` rule the drafter listed as not checked reads `line N: "<rule as written>" — <reason>`.
4373
+ - **The worker brief.** `rubricGoal({ ..., closeDecl })` adds a code-written brief per line (`lineBriefs`): a facts line gets its facts and its count; a text line that follows gets "every number must be copied exactly as written in a quote you wrote for job line N". `stepFileContract()` names both shapes.
4374
+ - **Web addresses as inputs (Part 1c, `src/pagefetch.js`, `src/source.js`).** An Inputs line may be `label: https://...` (also a rubric card's FIRST line; `looksLikeRubricInput`). `fetchPageText(url)` calls barebrowse `connect({ incognito: true, blockPrivateNetwork: true })` (`CONNECT_OPTIONS`) → `goto` → `waitForNetworkIdle({ idle: 1500, timeout: 20000 })` (bounded; a page that never settles is read as it stands) → `snapshot({ mode: 'act' })`, always closing the browser, with our own 90 s deadline (L1: a load-time `browse()` snapshot gave 77,951, 8,801 or 719 characters of the same results page; L2: a page whose text outside the banner / navigation / contentinfo landmarks is under `MIN_CONTENT_CHARS` = 100 (`pageContentChars`) is a stop too, "only the site's own header and menus", and an input with no content never reaches the look or an early ask: the survey is ABSENT, "holds nothing to read", nothing spent); an empty or under-200-character page (`MIN_PAGE_CHARS`), a bot challenge, an unreachable address, a credentialed or secret-shaped address, or a page carrying a secret shape is a plain-words $0 stop on its line. `proveRubricInputs({ ..., fetchUrl })` freezes the page text under `input/<label>.txt` and records `url` on the manifest input row. A FRESH `run-u --spec` start (not `--resume` / `--door` / `--decide`) calls `refreezeUrlInputs`: it fetches every recorded address again, writes that text into the prepared tree, commits it as the new seed and rewrites `source.json` (new `sha256` and `seed`, `draftSha256` keeps what the look saw) BEFORE the worker starts, so quotes are checked against the run's copy and the worker reads it, never the live page. A failed or empty fetch stops the run, "The run did not start. Nothing spent." Plan card (L3): `settlePersonsWords` (called in `authorClose` before validation) unmarks AI's proposal on a limit made of the person's own numbers, swaps in the one explicit positive time range the person wrote, and drops a `refused` entry for a `~` rule a part of its line carries; the look's fact may carry `forRule` (a rule it numbered from its own knowledge, marked AI's proposal), which removes that rule from the look's not-found list so it is not asked. Test seams: `fetchPageText(url, { connect })`, `deps.fetchUrl` (`run-u`), `deps.fetchUrl` / `deps.lookFn` (`createSession`).
4375
+ - **Ask at once, only about intent (I1).** A look not-found entry that names a `~` rule becomes a question worded by code (`ruleQuestions`: line and rule named), asked before the confirm turn through the existing question gate (`answerQuestions`; `normalizeQuestions` caps it at `MAX_QUESTIONS`); the answer is a new `~` rule on that line. A rubric job on files never offers the piecemeal try-3 question; its failed-draft stop (`draftStopText({ wholeJob: true })`) says to review the whole job and submit it again.
4376
+ - **Known limits (documented, not built around).** Airport codes like SFO have no stable label on a flight page: direction is checkable only through the full airport names the page uses. An unanchored `text` fact cannot tell which field a value sits in. `am` / `pm` in lower case and `9:27AM` are malformed times; `v0.41.0` is one token. A fact step's quote proves the value is in a real record, not that it is the RIGHT record: "the cheapest" and "the last 3" are Not checked. Verified on real files only; the live proof (DeepSeek, both jobs through the panel) has not been run.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bareloop",
3
- "version": "0.41.0",
3
+ "version": "0.42.0",
4
4
  "description": "Workflows that earn their own design, with receipts — agent-authored workflows for repeated, long, verifiable jobs that improve across runs under an un-gameable gate.",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -17,7 +17,7 @@
17
17
  "license": "Apache-2.0",
18
18
  "author": "hamr0",
19
19
  "engines": {
20
- "node": ">=20"
20
+ "node": ">=22"
21
21
  },
22
22
  "scripts": {
23
23
  "test": "node scripts/hermetic.mjs && node scripts/prompt-commit-check.mjs --range origin/main..HEAD",
@@ -28,7 +28,8 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "bare-agent": "^0.52.0",
31
- "bareguard": "^0.21.0",
31
+ "barebrowse": "^0.21.0",
32
+ "bareguard": "^0.24.0",
32
33
  "litectx": "^0.32.0"
33
34
  },
34
35
  "devDependencies": {