bareloop 0.25.1 → 0.27.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 +525 -0
- package/bareloop.context.md +384 -84
- package/package.json +4 -3
- package/src/authorflow.js +947 -104
- package/src/authoring.js +33 -15
- package/src/authorjob.js +161 -5
- package/src/authorscout.js +16 -11
- package/src/cardauthor.js +28 -18
- package/src/cli.js +10 -1
- package/src/detectlang.js +200 -0
- package/src/index.js +26 -30
- package/src/job.js +45 -28
- package/src/planrun.js +81 -2
- package/src/promptregisters.js +14 -0
- package/src/providers.js +51 -3
- package/src/source.js +290 -33
- package/src/text.js +45 -0
- package/src/validate.js +25 -1
- package/types/authorflow.d.ts +476 -105
- package/types/authoring.d.ts +23 -8
- package/types/authorjob.d.ts +22 -4
- package/types/cardauthor.d.ts +7 -3
- package/types/detectlang.d.ts +38 -0
- package/types/index.d.ts +16 -16
- package/types/job.d.ts +24 -2
- package/types/planrun.d.ts +41 -1
- package/types/providers.d.ts +37 -2
- package/types/source.d.ts +87 -0
- package/types/text.d.ts +35 -0
- package/types/validate.d.ts +22 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// LANGUAGE DETECTION (PRD item 33/M3, ruling 3 — `docs/product/ITEM33-BUILD.md`,
|
|
2
|
+
// "Five detection points"): a code job's checks always run the repo's OWN
|
|
3
|
+
// tools in the repo's own language, so language is a FACT of the repository,
|
|
4
|
+
// never a question asked of the person. This module is that fact-finder —
|
|
5
|
+
// $0, no provider, no model — and it replaces the `--lang` flag (default
|
|
6
|
+
// `js`) `scripts/run-interview.mjs` and `scripts/run-author.mjs` used to
|
|
7
|
+
// carry.
|
|
8
|
+
//
|
|
9
|
+
// ONE DATA TABLE (`MANIFEST_RULES`), not a code path per language: M3b's job
|
|
10
|
+
// is to flip a language from "known but no genre data" to "known and
|
|
11
|
+
// supported" by adding it to `GENRE_LANGUAGES` (`src/authoring.js`) — this
|
|
12
|
+
// module never hardcodes the supported set a second time, it reads
|
|
13
|
+
// `GENRE_LANGUAGES` live, so a language landing in M3b needs no edit here.
|
|
14
|
+
//
|
|
15
|
+
// FOUR OUTCOMES, discriminated by `kind`, never a throw for the ones a caller
|
|
16
|
+
// must act on in the ordinary course of a run:
|
|
17
|
+
// - `resolved` — a known, SUPPORTED manifest: 'js' or 'python'.
|
|
18
|
+
// - `language-unsupported` — a known manifest this catalogue has no genre
|
|
19
|
+
// data for yet (go, rust, java, csharp, php...).
|
|
20
|
+
// Never a silent fallback to js. Carries a
|
|
21
|
+
// `refusal` shaped exactly like the `Refusal`
|
|
22
|
+
// `src/authorjob.js` already returns for its own
|
|
23
|
+
// language refusal (same `REFUSAL_LIB`,
|
|
24
|
+
// `REFUSAL_CATEGORY`, `request-red` shape) —
|
|
25
|
+
// reusing that pattern rather than inventing a
|
|
26
|
+
// second one, so a caller with a spine can emit
|
|
27
|
+
// it through the same `refusalEvents()` either
|
|
28
|
+
// already imports.
|
|
29
|
+
// - `no-code-job` — no manifest anywhere in the walk. NOT an
|
|
30
|
+
// error: PRD item 33 M3 ruling 7 says a
|
|
31
|
+
// plain-folder job gets the form and an honest
|
|
32
|
+
// "no checks yet" stop later (M4), so this is a
|
|
33
|
+
// normal reading, not a refusal.
|
|
34
|
+
// - `ambiguous` — two DIFFERENT supported languages' manifests
|
|
35
|
+
// at the same (nearest) directory level. The
|
|
36
|
+
// confirm-turn UI that would ask a person to
|
|
37
|
+
// pick is a later M3 piece; for now this is
|
|
38
|
+
// reported so a caller can stop rather than
|
|
39
|
+
// guess.
|
|
40
|
+
|
|
41
|
+
import { readdirSync, statSync } from 'node:fs';
|
|
42
|
+
import { dirname, resolve } from 'node:path';
|
|
43
|
+
import { GENRE_LANGUAGES } from './authoring.js';
|
|
44
|
+
import { GENRE, REFUSAL_CATEGORY, REFUSAL_LIB } from './authorjob.js';
|
|
45
|
+
import { nearestGitAncestor } from './source.js';
|
|
46
|
+
|
|
47
|
+
/** @typedef {{code: string, path: string, detail: string, [k: string]: any}} Red */
|
|
48
|
+
/** @typedef {{kind: 'request-red', verb: string|null, path: string, detail: string,
|
|
49
|
+
* options: string[], red: Red}} LangRefusal */
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* THE MANIFEST TABLE, as data — the whole language vocabulary this module
|
|
53
|
+
* knows, one entry per manifest shape. `supported` is DERIVED from
|
|
54
|
+
* `GENRE_LANGUAGES` at lookup time (never stored), so a language M3b adds
|
|
55
|
+
* there flips from "unsupported stop" to "resolved" by that one edit, never
|
|
56
|
+
* by adding a new branch here.
|
|
57
|
+
* @type {ReadonlyArray<{kind: 'exact'|'ext', name?: string, ext?: string, language: string}>}
|
|
58
|
+
*/
|
|
59
|
+
const MANIFEST_RULES = Object.freeze([
|
|
60
|
+
Object.freeze({ kind: 'exact', name: 'package.json', language: 'js' }),
|
|
61
|
+
Object.freeze({ kind: 'exact', name: 'pyproject.toml', language: 'python' }),
|
|
62
|
+
Object.freeze({ kind: 'exact', name: 'setup.py', language: 'python' }),
|
|
63
|
+
Object.freeze({ kind: 'exact', name: 'go.mod', language: 'go' }),
|
|
64
|
+
Object.freeze({ kind: 'exact', name: 'Cargo.toml', language: 'rust' }),
|
|
65
|
+
Object.freeze({ kind: 'exact', name: 'pom.xml', language: 'java' }),
|
|
66
|
+
Object.freeze({ kind: 'exact', name: 'build.gradle', language: 'java' }),
|
|
67
|
+
Object.freeze({ kind: 'exact', name: 'build.gradle.kts', language: 'java' }),
|
|
68
|
+
Object.freeze({ kind: 'exact', name: 'composer.json', language: 'php' }),
|
|
69
|
+
Object.freeze({ kind: 'ext', ext: '.csproj', language: 'csharp' }),
|
|
70
|
+
Object.freeze({ kind: 'ext', ext: '.sln', language: 'csharp' }),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A human label for a matched rule, for messages — `package.json` for an
|
|
75
|
+
* exact match, `*.csproj` for an extension match.
|
|
76
|
+
* @param {{kind: 'exact'|'ext', name?: string, ext?: string}} rule
|
|
77
|
+
* @returns {string}
|
|
78
|
+
*/
|
|
79
|
+
function ruleLabel(rule) {
|
|
80
|
+
return rule.kind === 'exact' ? /** @type {string} */ (rule.name) : `*${rule.ext}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The directory chain to walk, NEAREST first, stopping at the repo root (the
|
|
85
|
+
* nearest ancestor carrying a `.git` entry, directory OR file — a submodule's
|
|
86
|
+
* `.git` is a file). When no `.git` exists anywhere above `start`, the walk
|
|
87
|
+
* is exactly `[start]` — this module never reads above the folder it was
|
|
88
|
+
* asked about when there is no repo boundary to stop it honestly.
|
|
89
|
+
*
|
|
90
|
+
* The boundary itself is `nearestGitAncestor` (`src/source.js`) — the ONE
|
|
91
|
+
* walk-up-for-`.git` rule, shared with `looksLikeRepoSource`/`prepareSource`'s
|
|
92
|
+
* own repo-root routing (PRD item 33 M3 ruling 2 addendum) — this function
|
|
93
|
+
* only turns that single boundary into the full chain of directories a
|
|
94
|
+
* manifest search walks.
|
|
95
|
+
* @param {string} start
|
|
96
|
+
* @returns {string[]}
|
|
97
|
+
*/
|
|
98
|
+
function walkChain(start) {
|
|
99
|
+
/** @type {string[]} */
|
|
100
|
+
const chain = [];
|
|
101
|
+
let d = resolve(start);
|
|
102
|
+
for (;;) {
|
|
103
|
+
chain.push(d);
|
|
104
|
+
const parent = dirname(d);
|
|
105
|
+
if (parent === d) break; // filesystem root
|
|
106
|
+
d = parent;
|
|
107
|
+
}
|
|
108
|
+
const boundary = nearestGitAncestor(chain[0]);
|
|
109
|
+
const repoRootIdx = boundary === null ? -1 : chain.indexOf(boundary.dir);
|
|
110
|
+
return repoRootIdx === -1 ? [chain[0]] : chain.slice(0, repoRootIdx + 1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Every manifest rule matched inside `dir`, reading its entries once.
|
|
115
|
+
* @param {string} dir
|
|
116
|
+
* @returns {{kind: 'exact'|'ext', name?: string, ext?: string, language: string}[]}
|
|
117
|
+
*/
|
|
118
|
+
function matchesIn(dir) {
|
|
119
|
+
/** @type {string[]} */
|
|
120
|
+
let entries;
|
|
121
|
+
try {
|
|
122
|
+
entries = readdirSync(dir);
|
|
123
|
+
} catch {
|
|
124
|
+
return []; // an unreadable directory has no manifest, not an error this module raises
|
|
125
|
+
}
|
|
126
|
+
return MANIFEST_RULES.filter((r) => (
|
|
127
|
+
r.kind === 'exact' ? entries.includes(/** @type {string} */ (r.name)) : entries.some((e) => e.endsWith(/** @type {string} */ (r.ext)))
|
|
128
|
+
));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The `language-unsupported` refusal, shaped exactly like `src/authorjob.js`'s
|
|
133
|
+
* own `Refusal` — same `REFUSAL_LIB`, same `request-red` code, so a caller
|
|
134
|
+
* holding a spine can hand it straight to `refusalEvents()` without this
|
|
135
|
+
* module inventing a second recording shape.
|
|
136
|
+
* @param {string} language @param {string} manifest
|
|
137
|
+
* @returns {LangRefusal}
|
|
138
|
+
*/
|
|
139
|
+
function unsupportedRefusal(language, manifest) {
|
|
140
|
+
const detail = `We can't run this kind of job yet: the nearest manifest is ${manifest}, which names "${language}" `
|
|
141
|
+
+ `as this repository's language. The catalogue only has ${GENRE} genre data for ${GENRE_LANGUAGES.join(' and ')} `
|
|
142
|
+
+ `today — a language it has no data for must refuse rather than author a close without the guards it cannot `
|
|
143
|
+
+ 'supply.';
|
|
144
|
+
return {
|
|
145
|
+
kind: 'request-red',
|
|
146
|
+
verb: 'language-unsupported',
|
|
147
|
+
path: 'lang',
|
|
148
|
+
detail,
|
|
149
|
+
options: [
|
|
150
|
+
`wait for a later build to add ${language} (PRD item 33 M3b)`,
|
|
151
|
+
`restate the job for ${GENRE_LANGUAGES.join(' or ')}, the languages this catalogue currently measures`,
|
|
152
|
+
],
|
|
153
|
+
red: {
|
|
154
|
+
code: 'request-red', path: 'lang', detail, verb: 'language-unsupported', lib: REFUSAL_LIB, category: REFUSAL_CATEGORY,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Detect a code job's language from its repository, $0, no provider.
|
|
161
|
+
*
|
|
162
|
+
* @param {string} sourcePath the Source path (a folder, or a file inside one)
|
|
163
|
+
* @returns {{kind: 'resolved', lang: string, dir: string, manifest: string} | {kind: 'language-unsupported', language: string, dir: string, manifest: string, refusal: LangRefusal} | {kind: 'no-code-job'} | {kind: 'ambiguous', candidates: string[], dir: string}}
|
|
164
|
+
*/
|
|
165
|
+
export function detectLanguage(sourcePath) {
|
|
166
|
+
const st = statSync(sourcePath);
|
|
167
|
+
const start = st.isDirectory() ? sourcePath : dirname(sourcePath);
|
|
168
|
+
const chain = walkChain(start);
|
|
169
|
+
|
|
170
|
+
for (const dir of chain) {
|
|
171
|
+
const matched = matchesIn(dir);
|
|
172
|
+
if (!matched.length) continue;
|
|
173
|
+
|
|
174
|
+
const languages = [...new Set(matched.map((m) => m.language))];
|
|
175
|
+
if (languages.length > 1) {
|
|
176
|
+
// Ruling 3, point (3): two manifests at the same level. Named literally
|
|
177
|
+
// rather than the spec's minimal "two supported languages" example —
|
|
178
|
+
// ANY two different languages at the nearest level is an unresolved
|
|
179
|
+
// pick, listed rather than silently narrowed to the supported ones.
|
|
180
|
+
return { kind: 'ambiguous', candidates: languages.sort(), dir };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const language = languages[0];
|
|
184
|
+
const matchedRule = matched.find((m) => m.language === language);
|
|
185
|
+
// `matched` was just built from `languages`, so this find always succeeds —
|
|
186
|
+
// narrowed with a real runtime check rather than an `any`/`!` cast, so a
|
|
187
|
+
// future refactor that breaks the invariant reds loudly instead of typing
|
|
188
|
+
// past it.
|
|
189
|
+
if (!matchedRule) throw new Error(`unreachable: "${language}" matched from its own manifest list`);
|
|
190
|
+
const manifest = ruleLabel(matchedRule);
|
|
191
|
+
if (GENRE_LANGUAGES.includes(language)) {
|
|
192
|
+
return { kind: 'resolved', lang: language, dir, manifest };
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
kind: 'language-unsupported', language, dir, manifest, refusal: unsupportedRefusal(language, manifest),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { kind: 'no-code-job' };
|
|
200
|
+
}
|
package/src/index.js
CHANGED
|
@@ -52,7 +52,7 @@ export {
|
|
|
52
52
|
BRIDGE_SCHEMA, validateBridge, deriveStatus, listingRow, loadGate,
|
|
53
53
|
mintBridge, appendGreen, appendRed,
|
|
54
54
|
loadBridge, loadRegistry, saveBridge, makeRegistry, registryExists,
|
|
55
|
-
QUARANTINED_VERDICTS,
|
|
55
|
+
QUARANTINED_VERDICTS, quarantinesCredit, newestEligibleVersion, reuseEligibility, recordDoor,
|
|
56
56
|
} from './bridges.js';
|
|
57
57
|
export { renderListing, selectionPrompt } from './selection.js';
|
|
58
58
|
// Layer 3 modules 4+5 — the D7 envelope and the reuse runner. `runReuse` composes
|
|
@@ -81,7 +81,7 @@ export { renderListing, selectionPrompt } from './selection.js';
|
|
|
81
81
|
// at a review door, and a door with no row to act on can only describe a held learning
|
|
82
82
|
// credit it has no way to release. STORAGE ONLY — nothing here selects, promotes or reuses
|
|
83
83
|
// a bridge; those stay parked on `layer-3-reuse`.
|
|
84
|
-
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow
|
|
84
|
+
export { validateEnvelope, resolveTrySpec, resolveReuse, reuseSpecHash, selectBridge, runReuse, REUSE_GRADED_RED, readResume, resumeTreeGate, CHECKPOINT_OUTCOMES, PAUSE_TTL_MS, checkpointAgeGate, applyDoorDecision, writeRunGreenRow } from './reuse.js';
|
|
85
85
|
// BA-21 pricing provenance rides out with the ledger because it is the same job: reading
|
|
86
86
|
// a run's own spend record honestly. `rateProvenance`/`spendProvenance` answer "how much of
|
|
87
87
|
// this was priced by a rate nobody vouched for" — REPORTING ONLY, no halt, no refusal.
|
|
@@ -95,7 +95,7 @@ export { classifyIncidents, foldLedger, ledgerDeltas, updateLedger, LEDGER_CLASS
|
|
|
95
95
|
// runner that resumes reads the branch off the dead spine and may want to verify it is
|
|
96
96
|
// still there before spending anything. The runner applies the rule itself either way —
|
|
97
97
|
// neither export is a way to opt out of it.
|
|
98
|
-
export { workBranchName
|
|
98
|
+
export { workBranchName } from './workbranch.js';
|
|
99
99
|
// THE READ SHIM (arm-gated, OFF by default via `runJob`'s `readShim`). The cap is exported
|
|
100
100
|
// because bareloop.context.md names it, and documented-but-unexported is a false contract;
|
|
101
101
|
// the wrapper goes with it so a caller building its own worker seam wraps the SAME ledger
|
|
@@ -105,7 +105,7 @@ export { workBranchName, prepareWorkBranch, WORK_BRANCH_PREFIX, WORK_BRANCH_RE }
|
|
|
105
105
|
// `readShimArm` is exported for the same reason the cap is: a battery driver has to be able
|
|
106
106
|
// to name and check the arm it is about to run BEFORE it launches, and the legal set living
|
|
107
107
|
// only inside a runner would leave the driver guessing at spellings the guard will reject.
|
|
108
|
-
export {
|
|
108
|
+
export { readShimArm, READ_SHIM_CAP, READ_SHIM_STRATEGY, READ_SHIM_DIFF_STRATEGY } from './readshim.js';
|
|
109
109
|
// ── CLOSE AUTHORING v1 (gate 4) — the user declares what done means ──────────
|
|
110
110
|
// The public surface is settled ONCE, here, at M4 (M2's header flagged the
|
|
111
111
|
// naming collision and deferred it rather than exporting piecemeal).
|
|
@@ -123,12 +123,12 @@ export { createReadShim, wrapReadTool, readShimArm, readShimStrategy, READ_SHIM_
|
|
|
123
123
|
// gate (that is what `prepareSigning` does with them), and `seedAtHead` because
|
|
124
124
|
// D8's seed is READ at run start rather than typed into a spec.
|
|
125
125
|
export {
|
|
126
|
-
runDeclaredClose, runStage, seedRead, seedAtHead,
|
|
127
|
-
LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER,
|
|
126
|
+
runDeclaredClose, runStage, seedRead, seedAtHead,
|
|
127
|
+
LIVE_KINDS, STOP_FAULTS, JUDGED_MARKER,
|
|
128
128
|
// N4 slice 1 — the hitl surface an adopting runner needs: the three doors, the
|
|
129
129
|
// gate that reads a signer's answer (and refuses an empty one), and the seed
|
|
130
130
|
// exemption ruling 8 states.
|
|
131
|
-
HUMAN_DECISIONS, SEED_EXEMPT_KINDS,
|
|
131
|
+
HUMAN_DECISIONS, SEED_EXEMPT_KINDS, normalizeHumanRuling, resolveHumanRuling,
|
|
132
132
|
// SOFTGREEN module 2 — the judged stage's own arbiter-owned bounds. An adopter
|
|
133
133
|
// wiring `judgeProvider` needs both numbers to reason about what a judged close
|
|
134
134
|
// can cost: one paid call per artifact, at most one retry each.
|
|
@@ -136,7 +136,7 @@ export {
|
|
|
136
136
|
// SOFTGREEN module 8 — the REVIEW DOOR's vocabulary: the record type a run
|
|
137
137
|
// writes when it ends at the door, which terminals open one, which classes open
|
|
138
138
|
// one unasked, and the mechanical-only rule an `accept` re-proves against.
|
|
139
|
-
|
|
139
|
+
DOOR_OPEN_OUTCOMES, REVIEW_DOOR_CLASSES, doorOpens, mechanicalStages,
|
|
140
140
|
} from './kinds.js';
|
|
141
141
|
// SOFTGREEN module 8 — the door's ANSWERING half. A run OPENS the door on its own
|
|
142
142
|
// spine and ends; a person answers minutes or days later, from another process, so
|
|
@@ -144,7 +144,7 @@ export {
|
|
|
144
144
|
// it re-proves the tree for an `accept`, releases a held judged green through
|
|
145
145
|
// module 6's registry half, and refuses an expired door under the same 60-day TTL a
|
|
146
146
|
// hitl checkpoint keeps. It never returns, writes or implies a verdict.
|
|
147
|
-
export { answerReviewDoor
|
|
147
|
+
export { answerReviewDoor } from './reviewdoor.js';
|
|
148
148
|
// SOFTGREEN module 1 — the judged floor's core. The pieces an adopter (and an
|
|
149
149
|
// integrating UI) genuinely needs: `resolveJudge` (PRD item 32.1 — WHICH model
|
|
150
150
|
// grades a job: the spec's signed `judge` override, else the job's own worker
|
|
@@ -157,13 +157,13 @@ export { answerReviewDoor, doorRecordOf, doorAgeGate } from './reviewdoor.js';
|
|
|
157
157
|
// runner reaches it through `runPlan`'s `judgeProvider`, and it is exported so a
|
|
158
158
|
// caller building its own harness does not spell a second one.
|
|
159
159
|
export {
|
|
160
|
-
JUDGE_MODEL, resolveJudge,
|
|
161
|
-
|
|
160
|
+
JUDGE_MODEL, resolveJudge, JUDGE_RULES, JUDGE_RULE_IDS,
|
|
161
|
+
runLocate, decide,
|
|
162
162
|
// SOFTGREEN module 4 — what a legal SIGNED calibration set is. The SIZE is
|
|
163
163
|
// hamr's own ruling and a size change is a spec-level threshold change;
|
|
164
164
|
// `expectedOf` is the ONE reduction of a `decide()` result to the shape a case
|
|
165
165
|
// stores, so a calibration harness compares one shape rather than two.
|
|
166
|
-
CALIBRATION_SIZE,
|
|
166
|
+
CALIBRATION_SIZE, validateCalibrationSet, expectedOf,
|
|
167
167
|
} from './judged.js';
|
|
168
168
|
// SOFTGREEN module 4 — the COMPILE. Q6 becomes the rubric card and Q7 becomes the
|
|
169
169
|
// frozen calibration set, both on the D5 shown-and-fixed path: the LLM proposes
|
|
@@ -173,8 +173,6 @@ export {
|
|
|
173
173
|
// line or a case is a re-sign like every other spec edit.
|
|
174
174
|
export {
|
|
175
175
|
proposeJudgedArtifacts, signJudgedArtifacts, foldJudgedArtifacts,
|
|
176
|
-
proposalSchema, proposalTool, cardCasesPrompt,
|
|
177
|
-
PROPOSAL_TOOL_NAME, PROPOSAL_LABEL, MAX_PROPOSAL_RETRIES, COMPILE_SYSTEM,
|
|
178
176
|
} from './cardauthor.js';
|
|
179
177
|
// SOFTGREEN module 5 — THE CALIBRATION GATE. `runCalibration` grades the whole
|
|
180
178
|
// pipe (locate + decide) over the signed ten and the five arbiter-owned injection
|
|
@@ -185,22 +183,21 @@ export {
|
|
|
185
183
|
// on trust. `INJECTION_LOCATE_BATTERY` and `INJECTION_CARD` are ARBITER-OWNED
|
|
186
184
|
// constants: no signer authors them and nothing stores them in a spec.
|
|
187
185
|
export {
|
|
188
|
-
runCalibration,
|
|
189
|
-
INJECTION_LOCATE_BATTERY,
|
|
186
|
+
runCalibration,
|
|
187
|
+
INJECTION_LOCATE_BATTERY, CASUALTY_AXES,
|
|
190
188
|
} from './calibrate.js';
|
|
191
189
|
// M2 — what a declaration may SAY, and whether one said it legally. The
|
|
192
190
|
// catalogue and the genre are DATA an integrating UI renders (the kind menu, the
|
|
193
191
|
// guard batteries it must show the user under D5 and cannot let them remove).
|
|
194
192
|
export {
|
|
195
|
-
validateDeclaration,
|
|
196
|
-
LOCKED_KINDS,
|
|
193
|
+
validateDeclaration, KIND_CATALOGUE,
|
|
194
|
+
LOCKED_KINDS, genreEnv,
|
|
197
195
|
// how the genre's own tools PRINT — genre property for the same reason the
|
|
198
196
|
// suppression battery is (2026-08-09, run msmbpjk6)
|
|
199
197
|
genreInstruments,
|
|
200
|
-
DIRECTIONS, BASELINES, MAX_STAGES,
|
|
201
198
|
// the verdict-class surface (PRD v1.57 §1–§2): the radio's own menu, the guard
|
|
202
199
|
// battery keyed off it, and the ceiling that makes the pick a promise
|
|
203
|
-
|
|
200
|
+
LOCKED_CLASSES, CLASS_BATTERIES, classGuards, closeCeiling,
|
|
204
201
|
// ruling 5 as data: the kinds no agent may ever be offered as an in-run ruler
|
|
205
202
|
NEVER_OFFERED_KINDS,
|
|
206
203
|
} from './authoring.js';
|
|
@@ -208,27 +205,26 @@ export {
|
|
|
208
205
|
// loop; `runAuthorScout`/`buildSeedListing` are exported because a caller that
|
|
209
206
|
// already paid for a survey passes it back in rather than buying a second one.
|
|
210
207
|
export {
|
|
211
|
-
runAuthorScout, buildSeedListing,
|
|
208
|
+
runAuthorScout, buildSeedListing, AUTHOR_SCOUT_VERBS,
|
|
212
209
|
// the malformed-class retry ladder: the hardcoded ceiling, the typed causes it
|
|
213
210
|
// reads, and the closed set of causes that may fire it (2026-08-09, PRD v1.58)
|
|
214
|
-
SCOUT_ATTEMPTS,
|
|
211
|
+
SCOUT_ATTEMPTS, SCOUT_RETRY_CAUSES,
|
|
215
212
|
} from './authorscout.js';
|
|
216
213
|
/** the ONE scrubbed-persist boundary for a model's raw output, and its announced
|
|
217
214
|
* bound — exported because every future writer of a raw must reach for this one
|
|
218
215
|
* rather than spell a second scrub (PRD v1.58) */
|
|
219
|
-
export { scrubRaw, RAW_PERSIST_MAX
|
|
216
|
+
export { scrubRaw, RAW_PERSIST_MAX } from './text.js';
|
|
220
217
|
export {
|
|
221
|
-
authorClose,
|
|
222
|
-
|
|
218
|
+
authorClose, MAX_REVISIONS,
|
|
219
|
+
questionsFor, requiredAnswersFor,
|
|
223
220
|
} from './authorflow.js';
|
|
224
221
|
// M4a — the runtime bridge. `validateCloseDecl` is the spec-level gate (the job
|
|
225
222
|
// validator's own branch calls it); `runDeclaredStages` is the executor seam a
|
|
226
223
|
// runner other than `runPlan` would need; `closeStagesOf` is the ONE staging
|
|
227
224
|
// every close consumer reads, widened to both fields.
|
|
228
225
|
export {
|
|
229
|
-
validateCloseDecl, runDeclaredStages, declaredStages,
|
|
230
|
-
|
|
231
|
-
HUMAN_PAUSE, HITL_PAUSE, HITL_DECISION_RED, HUMAN_CHECKPOINTS,
|
|
226
|
+
validateCloseDecl, runDeclaredStages, declaredStages,
|
|
227
|
+
HUMAN_PAUSE,
|
|
232
228
|
} from './declaredclose.js';
|
|
233
229
|
export { closeStagesOf } from './plan.js';
|
|
234
230
|
// Build-list item 2 (2026-08-23) — the run behaviour summary: a report-only
|
|
@@ -247,7 +243,7 @@ export { replayRun, formatReplay } from './replay.js';
|
|
|
247
243
|
// never signs — the approvals array and the human's word are unchanged.
|
|
248
244
|
export {
|
|
249
245
|
runInterview, authorCloseForJob, assembleSpec, prepareSigning, refusalEvents, refuseLockedKind,
|
|
250
|
-
|
|
246
|
+
AUTHORED_SPEC_FIELDS,
|
|
251
247
|
} from './authorjob.js';
|
|
252
248
|
// PRD build-list item 5 (TODO #8) — the prompt-register inventory: every file
|
|
253
249
|
// known to carry model-facing prompt/instruction text, and the predicate a
|
|
@@ -295,5 +291,5 @@ export { main as cliMain } from './cli.js';
|
|
|
295
291
|
// module, not a re-derivation of it.
|
|
296
292
|
export {
|
|
297
293
|
prepareSource, proveDestination, copyOut, readSourceManifest, frontDoorFromManifest,
|
|
298
|
-
datedDestination, pickDelivery,
|
|
294
|
+
datedDestination, pickDelivery, missingDependencies,
|
|
299
295
|
} from './source.js';
|
package/src/job.js
CHANGED
|
@@ -135,8 +135,9 @@ export const CADENCE_UNITS = Object.freeze(['hour', 'day', 'week']);
|
|
|
135
135
|
* rows on the cost axis, and budgets sized for one do not transfer. */
|
|
136
136
|
/** PRD item 28 (2026-09-06/09-09): `openai-api` — an OpenAI-shaped provider,
|
|
137
137
|
* constructed via `src/providers.js`'s factory. hamr's ruling (PRD 30.7):
|
|
138
|
-
* `deepseek-
|
|
139
|
-
* models
|
|
138
|
+
* `deepseek-flash` is THE secondary provider (one, not a menu of half-tested
|
|
139
|
+
* models; F171, item 34 L16: swapped from the retired `deepseek-chat`); the
|
|
140
|
+
* factory's tier table is where that lives, not this menu.
|
|
140
141
|
*
|
|
141
142
|
* PRD item 31.3 (hamr, 2026-09-09: *"anthropic, openai, gemini drop ollama for
|
|
142
143
|
* now"*): `gemini-api` joins. That is bareloop matching bare-agent's own
|
|
@@ -191,6 +192,42 @@ const SLUG_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
|
191
192
|
* bareloop's OWN menu. Inferring it downstream is how a bareloop refusal gets
|
|
192
193
|
* filed as an upstream bug (the BA-2 misattribution class). */
|
|
193
194
|
|
|
195
|
+
/**
|
|
196
|
+
* The `baseUrl` SHAPE rule, standalone (PRD item 28 ruling (d); PRD item 33
|
|
197
|
+
* close-out, "authoring provider selectable" — a bad `--base-url` used to
|
|
198
|
+
* refuse only at the end of the interview, through `validateJob`, after every
|
|
199
|
+
* question had already been asked). ONE spelling of the rule: `validateJob`
|
|
200
|
+
* below calls this, and so does `scripts/run-interview.mjs`, at flag-parse
|
|
201
|
+
* time, before it asks anything.
|
|
202
|
+
*
|
|
203
|
+
* No network access, so this checks SHAPE only: `https://` for a real
|
|
204
|
+
* endpoint, or `http://` restricted to a loopback host (a local
|
|
205
|
+
* OpenAI-compatible server, the only legitimate reason to skip TLS) — and
|
|
206
|
+
* never a credential embedded IN the URL itself. A URL travels into logs and
|
|
207
|
+
* spine records far more casually than a header ever would, and secrets never
|
|
208
|
+
* enter the tree/spine (hard line #3) — a baseUrl carrying `user:pass@host`
|
|
209
|
+
* is exactly that leak, just spelled differently. The returned message never
|
|
210
|
+
* echoes the value it judged — it is printed as-is by both callers, and a
|
|
211
|
+
* value that failed for carrying credentials must not repeat them back.
|
|
212
|
+
* @param {unknown} value
|
|
213
|
+
* @returns {string|null} a red `detail`, or `null` when the value is admitted
|
|
214
|
+
*/
|
|
215
|
+
export function validateBaseUrl(value) {
|
|
216
|
+
if (!isNonEmptyString(value)) {
|
|
217
|
+
return 'non-empty string — an https:// URL (http:// admitted only to 127.0.0.1/localhost/::1)';
|
|
218
|
+
}
|
|
219
|
+
/** @type {URL|null} */
|
|
220
|
+
let u = null;
|
|
221
|
+
try { u = new URL(value); } catch { /* u stays null — caught below */ }
|
|
222
|
+
if (!u) return 'must be a parseable URL';
|
|
223
|
+
if (u.username !== '' || u.password !== '') return 'no embedded credentials (user:pass@host) — secrets never enter a signed spec';
|
|
224
|
+
if (u.protocol === 'https:') return null; // admitted
|
|
225
|
+
if (u.protocol === 'http:' && (u.hostname === '127.0.0.1' || u.hostname === 'localhost' || u.hostname === '::1')) {
|
|
226
|
+
return null; // admitted — a local OpenAI-compatible endpoint, the one case that legitimately skips TLS
|
|
227
|
+
}
|
|
228
|
+
return 'https:// required (http:// admitted only to 127.0.0.1/localhost/::1)';
|
|
229
|
+
}
|
|
230
|
+
|
|
194
231
|
/**
|
|
195
232
|
* Validate an operator-owned job spec (`schema: "job-v1"`). Never throws on
|
|
196
233
|
* JSON text or plain parsed data — the ingest contract; a live object with a
|
|
@@ -328,33 +365,13 @@ export function validateJob(input, { shellCapUsd = 2 } = {}) {
|
|
|
328
365
|
|
|
329
366
|
// BASE URL — PRD item 28, ruling (d), 2026-09-09: admitted in v1 of
|
|
330
367
|
// openai-api (an OpenAI-compatible gateway/endpoint), OPTIONAL and part of
|
|
331
|
-
// the signed hash like `model` above.
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
// credential embedded IN the URL itself. A URL travels into logs and
|
|
336
|
-
// spine records far more casually than a header ever would, and secrets
|
|
337
|
-
// never enter the tree/spine (hard line #3) — a bareUrl carrying
|
|
338
|
-
// `user:pass@host` is exactly that leak, just spelled differently.
|
|
368
|
+
// the signed hash like `model` above. `validateBaseUrl` (below) is the ONE
|
|
369
|
+
// spelling of the shape rule — `scripts/run-interview.mjs` calls it too, at
|
|
370
|
+
// flag-parse time, before the interview asks a single question, rather than
|
|
371
|
+
// this file's copy being the only one that ever ran it.
|
|
339
372
|
if (spec.baseUrl !== undefined) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
} else {
|
|
343
|
-
/** @type {URL|null} */
|
|
344
|
-
let u = null;
|
|
345
|
-
try { u = new URL(spec.baseUrl); } catch { /* u stays null — caught below */ }
|
|
346
|
-
if (!u) {
|
|
347
|
-
red('invalid-value', 'baseUrl', 'must be a parseable URL');
|
|
348
|
-
} else if (u.username !== '' || u.password !== '') {
|
|
349
|
-
red('invalid-value', 'baseUrl', 'no embedded credentials (user:pass@host) — secrets never enter a signed spec');
|
|
350
|
-
} else if (u.protocol === 'https:') {
|
|
351
|
-
// admitted
|
|
352
|
-
} else if (u.protocol === 'http:' && (u.hostname === '127.0.0.1' || u.hostname === 'localhost' || u.hostname === '::1')) {
|
|
353
|
-
// admitted — a local OpenAI-compatible endpoint, the one case that legitimately skips TLS
|
|
354
|
-
} else {
|
|
355
|
-
red('invalid-value', 'baseUrl', 'https:// required (http:// admitted only to 127.0.0.1/localhost/::1)');
|
|
356
|
-
}
|
|
357
|
-
}
|
|
373
|
+
const err = validateBaseUrl(spec.baseUrl);
|
|
374
|
+
if (err) red('invalid-value', 'baseUrl', err);
|
|
358
375
|
}
|
|
359
376
|
|
|
360
377
|
// 3. the outer write fence — operator law (interview decision #4), same
|
package/src/planrun.js
CHANGED
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
// absolute repo root, its target, prior steps' artifacts labeled by id, its
|
|
12
12
|
// gap, and a cut-off notice. It NEVER sees the budget, the close command, a
|
|
13
13
|
// check's command, the validator, other steps' grants, or the arbiter's books
|
|
14
|
-
// (fs.deny on the gate audit / .smoke / .litectx, unchanged).
|
|
14
|
+
// (fs.deny on the gate audit / .smoke / .litectx, unchanged). `.git` and
|
|
15
|
+
// `node_modules` are a DIFFERENT law (F178/F177): WRITES there are blocked
|
|
16
|
+
// (tools.denyArgPatterns on write/edit, FORBIDDEN_WRITE_SEGMENT_PATTERN,
|
|
17
|
+
// below), but READS are not — fs.deny blocks ALL fs actions including reads
|
|
18
|
+
// (bareguard node_modules/bareguard/src/primitives/fs.js:43,62-66), and
|
|
19
|
+
// hamr's ruling was to block WRITING, never reading; the worker's persona
|
|
20
|
+
// also never names `.git`/`node_modules` as denied books (tests/tools.test.js),
|
|
21
|
+
// which putting either in fs.deny would silently contradict.
|
|
15
22
|
|
|
16
23
|
import { createRequire } from 'node:module';
|
|
17
24
|
import { readFileSync, readdirSync } from 'node:fs';
|
|
@@ -465,7 +472,6 @@ const REPLAN_GAP_KEEP = '\\S';
|
|
|
465
472
|
* No gap → the empty string, so the brief renders byte-identically to the pre-F86
|
|
466
473
|
* one. A labelled empty section would be an invitation to explain an absence the
|
|
467
474
|
* run never observed (a stall never judged its exits at all).
|
|
468
|
-
*
|
|
469
475
|
* @param {string | null | undefined} gap the step's last exit gap text
|
|
470
476
|
* @returns {string} the labelled block, or '' when there is nothing to show
|
|
471
477
|
*/
|
|
@@ -480,6 +486,48 @@ export function closeGapBlock(gap) {
|
|
|
480
486
|
+ (scrubbed.length > CHECK_GAP_MAX ? boundGap(scrubbed, REPLAN_GAP_KEEP) : scrubbed);
|
|
481
487
|
}
|
|
482
488
|
|
|
489
|
+
/**
|
|
490
|
+
* The runtime belt for F178 (`.git`) and F177's follow-up (`node_modules`) —
|
|
491
|
+
* hamr's option B ruling (2026-09-14): block WRITING to either, never reading.
|
|
492
|
+
* bareguard's `fs.deny` cannot be used for this: it blocks ALL fs actions,
|
|
493
|
+
* reads included (node_modules/bareguard/src/primitives/fs.js:43 "paths/
|
|
494
|
+
* prefixes denied for all fs actions", checked at :62-66 before the read/write
|
|
495
|
+
* split) — putting `.git` there (the first version of this fix) silently also
|
|
496
|
+
* blocked READING it, which was never ruled, and the worker's persona never
|
|
497
|
+
* names `.git`/`node_modules` as a denied book the way it does the gate audit
|
|
498
|
+
* / `.smoke` / `.litectx` (`tests/tools.test.js`'s persona/fence-drift guard
|
|
499
|
+
* caught exactly this in the full gate). `fs.deny` is exact prefix-containment
|
|
500
|
+
* only anyway — no glob, no path-segment match — confirmed by reading
|
|
501
|
+
* `within()` (node_modules/bareguard/src/primitives/fs.js:26-31): `p === b ||
|
|
502
|
+
* p.startsWith(b + '/')` against each configured entry, nothing else; it
|
|
503
|
+
* could not express `node_modules`-at-any-depth even if reads were not a
|
|
504
|
+
* problem (`node_modules/bareguard/src/glob.js`'s `matchAny`/`globToRegex`
|
|
505
|
+
* exist in the same package but `fs.js` never imports them).
|
|
506
|
+
*
|
|
507
|
+
* `tools.denyArgPatterns` (`node_modules/bareguard/src/primitives/tools.js`,
|
|
508
|
+
* step 3 of `Gate#_stepEval` — the SAME step `fsCheck` runs at, checked right
|
|
509
|
+
* after it in the `??` chain, `node_modules/bareguard/src/gate.js`) is the
|
|
510
|
+
* supported hook used instead: a RegExp tested against
|
|
511
|
+
* `JSON.stringify(action)`, keyed by `action.type` so it is scoped to
|
|
512
|
+
* `write`/`edit` ONLY — a `read` action never reaches this pattern at all.
|
|
513
|
+
* Because `fsCheck` runs first in the same `??` chain, this pattern is
|
|
514
|
+
* reached only when the path already passed `writeScope`/`deny` — exactly the
|
|
515
|
+
* residual case (an in-fence write/edit whose path still carries a `.git` or
|
|
516
|
+
* `node_modules` segment; `.git` itself is never IN a signed fence in the
|
|
517
|
+
* first place, `scopeContained` in src/validate.js refuses that at
|
|
518
|
+
* declaration time — this is the belt for a validator regression). Matched
|
|
519
|
+
* whole-segment (a look-alike like `.github`/`.gitignore`/`my.git`/
|
|
520
|
+
* `node_modules_util` stays admitted). Safe to match the WHOLE serialized
|
|
521
|
+
* action (not just the `path` field in isolation) because bareloop's own
|
|
522
|
+
* action shape (`toolAction`, src/tools.js) never carries a write's byte
|
|
523
|
+
* CONTENT in the action object — `args.bytes` is a length, not the text — so
|
|
524
|
+
* there is no written-file-content collision risk the way there would be for
|
|
525
|
+
* bareguard's own `content.denyPatterns` primitive (which explicitly strips
|
|
526
|
+
* payload fields for exactly that reason).
|
|
527
|
+
* @type {RegExp}
|
|
528
|
+
*/
|
|
529
|
+
export const FORBIDDEN_WRITE_SEGMENT_PATTERN = /"path":"(?:(?:[^"\\]|\\.)*\/)?(?:\.git|node_modules)(?:\/|")/;
|
|
530
|
+
|
|
483
531
|
/**
|
|
484
532
|
* The recorded bound-reason's ceiling in the WORKER-facing note. The reason is a
|
|
485
533
|
* provider string (`denied:<tool>` today, ~20 bytes), not a gap, so this is a
|
|
@@ -2250,6 +2298,13 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
2250
2298
|
readScope: [workdir],
|
|
2251
2299
|
deny: [auditPath, ...ARBITER_BOOK_STORES.map((s) => join(workdir, s))],
|
|
2252
2300
|
},
|
|
2301
|
+
// F178/F177's runtime belt: `.git` and `node_modules` WRITES only — never
|
|
2302
|
+
// fs.deny, which blocks reads too (see FORBIDDEN_WRITE_SEGMENT_PATTERN's
|
|
2303
|
+
// own doc). Reached only when a write/edit path already cleared
|
|
2304
|
+
// fs.writeScope/deny above (step 3's fsCheck runs first in the SAME `??`
|
|
2305
|
+
// chain, node_modules/bareguard/src/gate.js) — i.e. only the residual
|
|
2306
|
+
// case of an in-fence path that still carries one of these segments.
|
|
2307
|
+
tools: { denyArgPatterns: { write: [FORBIDDEN_WRITE_SEGMENT_PATTERN], edit: [FORBIDDEN_WRITE_SEGMENT_PATTERN] } },
|
|
2253
2308
|
budget: { maxCostUsd: Math.max(remainingUsd(), 0.0001) },
|
|
2254
2309
|
limits: { maxTurns },
|
|
2255
2310
|
audit: { path: auditPath },
|
|
@@ -2756,6 +2811,30 @@ export async function runPlan(job, { workdir, provider, nativeProvider, provider
|
|
|
2756
2811
|
err.lib = 'bare-agent';
|
|
2757
2812
|
throw err;
|
|
2758
2813
|
}
|
|
2814
|
+
// F184 — bare-agent 0.43.0 (BA-27) returns a malformed final-turn tool
|
|
2815
|
+
// call PRICED rather than throwing: `toolCalls: []`, `text: ''`,
|
|
2816
|
+
// `error: null`, and its own `malformedToolCall: {name, error}`
|
|
2817
|
+
// marker. Nothing below this point can tell that apart from the model
|
|
2818
|
+
// genuinely producing no useful work — `middle()` reads `r.text` into
|
|
2819
|
+
// `lastText`, which comes back empty either way, and the ordinary
|
|
2820
|
+
// `needs_revision`/`exit-eval` gap is what reaches the spine. That is
|
|
2821
|
+
// NOT changed here — no retry, no new cause on `attemptBounded`, no
|
|
2822
|
+
// effect on strike/ladder/attempt counting or verdict routing (all
|
|
2823
|
+
// arbiter-adjacent, none of this finding's scope). What was missing is
|
|
2824
|
+
// VISIBILITY: this is the one place bareloop code ever sees the
|
|
2825
|
+
// marker before `ask()` returns it unexamined, so a distinct spine
|
|
2826
|
+
// record names the transport class here — the same honesty bar
|
|
2827
|
+
// F179/F180 set for the authoring path, applied to the worker path.
|
|
2828
|
+
if (r.malformedToolCall) {
|
|
2829
|
+
const rawErr = String(r.malformedToolCall.error ?? '');
|
|
2830
|
+
const scrubbedErr = scrub(rawErr);
|
|
2831
|
+
const shownErr = scrubbedErr.length > BOUND_REASON_MAX
|
|
2832
|
+
? `${scrubbedErr.slice(0, BOUND_REASON_MAX)} [${GAP_TRIM_MARKER} ${scrubbedErr.length - BOUND_REASON_MAX} of ${scrubbedErr.length} characters withheld — the cap is ${BOUND_REASON_MAX}]`
|
|
2833
|
+
: scrubbedErr;
|
|
2834
|
+
emit('worker-malformed-tool-call', {
|
|
2835
|
+
phase, iteration: roundIteration, name: scrub(String(r.malformedToolCall.name ?? '')), error: shownErr,
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2759
2838
|
return r;
|
|
2760
2839
|
};
|
|
2761
2840
|
return { ask, askFrom, workerWrites, writeCount, setIteration, wasBounded: () => attemptBounded };
|
package/src/promptregisters.js
CHANGED
|
@@ -45,6 +45,12 @@ export const PROMPT_REGISTERS = Object.freeze([
|
|
|
45
45
|
Object.freeze({ file: 'src/authorflow.js', name: 'STRUCTURE_INSTRUCTION_TOOL' }),
|
|
46
46
|
Object.freeze({ file: 'src/authorflow.js', name: 'STRUCTURE_INSTRUCTION_TEXT' }),
|
|
47
47
|
|
|
48
|
+
// src/authorflow.js — the confirm turn (PRD item 33 M3 piece 4)
|
|
49
|
+
Object.freeze({ file: 'src/authorflow.js', name: 'CONFIRM_SYSTEM' }),
|
|
50
|
+
Object.freeze({ file: 'src/authorflow.js', name: 'confirmPrompt() (inline template, not a const)' }),
|
|
51
|
+
Object.freeze({ file: 'src/authorflow.js', name: 'CONFIRM_STRUCTURE_INSTRUCTION' }),
|
|
52
|
+
Object.freeze({ file: 'src/authorflow.js', name: 'confirmedBlock() (inline template, not a const — feeds authorPrompt)' }),
|
|
53
|
+
|
|
48
54
|
// src/judged.js — the softgreen judge's locate prompt (spine + per-rule asks)
|
|
49
55
|
Object.freeze({ file: 'src/judged.js', name: 'PROMPT_HEAD' }),
|
|
50
56
|
Object.freeze({ file: 'src/judged.js', name: 'PROMPT_TAIL' }),
|
|
@@ -70,6 +76,14 @@ export const PROMPT_REGISTERS = Object.freeze([
|
|
|
70
76
|
|
|
71
77
|
// src/planrun.js — the native-surface read-truncation strategy line
|
|
72
78
|
Object.freeze({ file: 'src/planrun.js', name: 'NATIVE_READ_STRATEGY' }),
|
|
79
|
+
|
|
80
|
+
// src/authoring.js — the types-genre close template, interpolated verbatim
|
|
81
|
+
// into the authoring prompt (src/authorflow.js's assembled system message,
|
|
82
|
+
// "THE GENRE TEMPLATE" block) — model-facing law for a types-genre
|
|
83
|
+
// declaration, not merely internal genre-table data. Found missing from
|
|
84
|
+
// this inventory 2026-09-15 (item 6 close-out); no other exported const or
|
|
85
|
+
// inline template literal in this file carries prompt/instruction text.
|
|
86
|
+
Object.freeze({ file: 'src/authoring.js', name: 'TYPES_GENRE_TEMPLATE' }),
|
|
73
87
|
]);
|
|
74
88
|
|
|
75
89
|
/** The distinct file paths carrying prompt registers, repo-root-relative with
|