onto-mcp 0.4.11 → 0.4.13
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/.onto/authority/core-lexicon.yaml +1 -1
- package/.onto/authority/supported-models.yaml +153 -0
- package/.onto/domains/accounting-kr/competency_qs.md +153 -153
- package/.onto/processes/reconstruct/obligation-coverage-ledger.yaml +842 -0
- package/.onto/processes/reconstruct/obligation-coverage-recorded.yaml +225 -0
- package/.onto/processes/reconstruct/ontology-seeding-and-maturation-design.md +93 -54
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +202 -49
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +72 -1
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +35 -0
- package/.onto/processes/review/nesting-batch-worker-contract.md +1 -1
- package/.onto/processes/review/productized-live-path.md +9 -5
- package/.onto/processes/review/prompt-execution-runner-contract.md +32 -8
- package/.onto/processes/review/review-target-profile-contract.md +47 -4
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +11 -0
- package/.onto/processes/shared/target-material-kind-contract.md +1 -1
- package/README.md +64 -1
- package/dist/cli.js +11 -1
- package/dist/core-api/reconstruct-api.js +679 -18
- package/dist/core-api/review-api.js +70 -5
- package/dist/core-api/review-progress.js +7 -0
- package/dist/core-api/runtime-observation.js +7 -0
- package/dist/core-runtime/artifact-io.js +131 -0
- package/dist/core-runtime/cli/claude-code-review-unit-executor.js +21 -9
- package/dist/core-runtime/cli/claude-nesting-batch-worker.js +4 -2
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +68 -14
- package/dist/core-runtime/cli/prepare-review-session.js +34 -1
- package/dist/core-runtime/cli/render-review-final-output.js +2 -1
- package/dist/core-runtime/cli/run-review-prompt-execution.js +758 -48
- package/dist/core-runtime/cli/run-seats.js +18 -0
- package/dist/core-runtime/cli/runtime-submit-context.js +10 -2
- package/dist/core-runtime/cli/unit-resubmit.js +269 -0
- package/dist/core-runtime/cli/worker-structured-output.js +80 -2
- package/dist/core-runtime/discovery/host-detection.js +71 -22
- package/dist/core-runtime/discovery/review-cert-assemble.js +208 -0
- package/dist/core-runtime/discovery/review-cert-record.js +442 -0
- package/dist/core-runtime/discovery/review-execution-units.js +22 -0
- package/dist/core-runtime/discovery/seat-inventory.js +167 -0
- package/dist/core-runtime/discovery/settings-chain.js +311 -52
- package/dist/core-runtime/discovery/supported-models.js +289 -25
- package/dist/core-runtime/discovery/synthesize-cert-assemble.js +197 -0
- package/dist/core-runtime/discovery/synthesize-cert-capsule.js +399 -0
- package/dist/core-runtime/discovery/synthesize-cert-judge.js +340 -0
- package/dist/core-runtime/discovery/synthesize-cert-loop.js +234 -0
- package/dist/core-runtime/discovery/synthesize-cert-mutation.js +117 -0
- package/dist/core-runtime/discovery/synthesize-cert-packet.js +182 -0
- package/dist/core-runtime/discovery/synthesize-cert-record.js +831 -0
- package/dist/core-runtime/discovery/synthesize-cert-sampler.js +336 -0
- package/dist/core-runtime/effort-calibration-ingest.js +326 -0
- package/dist/core-runtime/effort-calibration-reconstruct.js +47 -0
- package/dist/core-runtime/effort-calibration-review.js +22 -0
- package/dist/core-runtime/effort-calibration-sweep.js +110 -0
- package/dist/core-runtime/effort-frontier.js +134 -0
- package/dist/core-runtime/llm/claude-bin.js +77 -0
- package/dist/core-runtime/llm/dispatch-breaker.js +412 -0
- package/dist/core-runtime/llm/dispatch-fallback-adapter-capabilities.js +33 -0
- package/dist/core-runtime/llm/llm-caller.js +316 -35
- package/dist/core-runtime/llm/mock-llm-realization.js +35 -4
- package/dist/core-runtime/llm/model-switcher.js +1 -1
- package/dist/core-runtime/llm/openai-responses-incomplete-error.js +46 -0
- package/dist/core-runtime/llm/sealed-dispatch-capability.js +359 -0
- package/dist/core-runtime/llm/structured-dispatch-error.js +15 -0
- package/dist/core-runtime/onboard/claude-profile-scan.js +65 -0
- package/dist/core-runtime/onboard/cli-host.js +5 -2
- package/dist/core-runtime/onboard/configure-provider.js +33 -1
- package/dist/core-runtime/onboard/host-target.js +28 -3
- package/dist/core-runtime/onboard/register.js +54 -12
- package/dist/core-runtime/pipeline-execution-ledger.js +10 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +37 -0
- package/dist/core-runtime/reconstruct/benchmark-evidence.js +16 -0
- package/dist/core-runtime/reconstruct/claim-projection-validation.js +19 -5
- package/dist/core-runtime/reconstruct/competency-projection-contract.js +63 -0
- package/dist/core-runtime/reconstruct/comprehension-artifact.js +321 -0
- package/dist/core-runtime/reconstruct/comprehension-reduce.js +367 -0
- package/dist/core-runtime/reconstruct/comprehension-semantic-map.js +771 -0
- package/dist/core-runtime/reconstruct/contract-registry.js +20 -0
- package/dist/core-runtime/reconstruct/directive-validation.js +4 -3
- package/dist/core-runtime/reconstruct/dispatch-fallback-artifacts.js +640 -0
- package/dist/core-runtime/reconstruct/execution-telemetry.js +84 -9
- package/dist/core-runtime/reconstruct/final-output-sections.js +89 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +36 -1
- package/dist/core-runtime/reconstruct/leaf-reader.js +305 -0
- package/dist/core-runtime/reconstruct/llm-dispatch-failure.js +270 -0
- package/dist/core-runtime/reconstruct/llm-touch-fingerprint.js +94 -0
- package/dist/core-runtime/reconstruct/material-admission-validation.js +20 -5
- package/dist/core-runtime/reconstruct/material-profile-validation.js +24 -6
- package/dist/core-runtime/reconstruct/materialize-preparation.js +380 -23
- package/dist/core-runtime/reconstruct/maturation-validation.js +1522 -67
- package/dist/core-runtime/reconstruct/mock-llm-realization.js +120 -1
- package/dist/core-runtime/reconstruct/obligation-assertion.js +18 -0
- package/dist/core-runtime/reconstruct/ontology-seed-validation.js +39 -7
- package/dist/core-runtime/reconstruct/output-budget.js +6 -0
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +112 -5
- package/dist/core-runtime/reconstruct/post-seed-validation.js +122 -11
- package/dist/core-runtime/reconstruct/proof-authority-validation.js +2 -6
- package/dist/core-runtime/reconstruct/purpose-authority-validation.js +119 -7
- package/dist/core-runtime/reconstruct/record.js +49 -3
- package/dist/core-runtime/reconstruct/registry-verification-validation.js +41 -5
- package/dist/core-runtime/reconstruct/run-control-validation.js +1019 -37
- package/dist/core-runtime/reconstruct/run.js +5409 -517
- package/dist/core-runtime/reconstruct/seed-authoring-readiness-validation.js +56 -8
- package/dist/core-runtime/reconstruct/semantic-quality-gate.js +112 -7
- package/dist/core-runtime/reconstruct/source-observation-delta-validation.js +53 -5
- package/dist/core-runtime/reconstruct/source-observations.js +166 -0
- package/dist/core-runtime/reconstruct/source-safety-validation.js +77 -188
- package/dist/core-runtime/reconstruct/source-scout-pack-validation.js +50 -6
- package/dist/core-runtime/reconstruct/terminal-validation.js +145 -13
- package/dist/core-runtime/review/continuation-plan.js +12 -5
- package/dist/core-runtime/review/issue-artifact-runtime.js +4 -1
- package/dist/core-runtime/review/materializers.js +100 -11
- package/dist/core-runtime/review/nesting-batch.js +6 -2
- package/dist/core-runtime/review/obligation-shardability.js +138 -0
- package/dist/core-runtime/review/pipeline-execution-ledger.js +42 -2
- package/dist/core-runtime/review/review-artifact-utils.js +219 -7
- package/dist/core-runtime/review/review-execution-steps.js +32 -8
- package/dist/core-runtime/review/review-progress-contract.js +8 -0
- package/dist/core-runtime/review/review-prompt-budget.js +97 -0
- package/dist/core-runtime/review/semantic-quality-gate.js +33 -7
- package/dist/core-runtime/review/spreadsheet-review-disposition.js +94 -0
- package/dist/core-runtime/route-identity.js +182 -0
- package/dist/core-runtime/spreadsheet-header-escalation.js +204 -0
- package/dist/core-runtime/spreadsheet-structure-observer.js +2689 -0
- package/dist/core-runtime/target-material-kind.js +58 -0
- package/dist/mcp/server.js +27 -5
- package/dist/mcp/tool-schemas.js +8 -0
- package/dist/tui/app.js +167 -0
- package/dist/tui/data/event-follower.js +116 -0
- package/dist/tui/data/node-detail.js +42 -0
- package/dist/tui/data/projection-poll.js +26 -0
- package/dist/tui/data/session-discovery.js +65 -0
- package/dist/tui/index.js +108 -0
- package/dist/tui/screens/log.js +25 -0
- package/dist/tui/screens/node-detail.js +17 -0
- package/dist/tui/screens/session-selector.js +26 -0
- package/dist/tui/screens/workflow-tree.js +90 -0
- package/dist/tui/view-model/reconstruct-adapter.js +162 -0
- package/dist/tui/view-model/review-adapter.js +270 -0
- package/dist/tui/view-model/tree-view-model.js +19 -0
- package/package.json +20 -3
- package/settings.example.json +77 -16
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { atomicWriteYamlDocument as writeYamlDocument } from "../artifact-io.js";
|
|
5
5
|
import { aggregateTargetMaterialDetections, detectTargetMaterialRefs, } from "../target-material-kind.js";
|
|
6
|
+
import { observeSpreadsheetSource, projectInventoryForAdmission, SPREADSHEET_CAPTURE_TRUNCATED_PHRASE, SPREADSHEET_MACRO_PRESENT_PHRASE, SPREADSHEET_OBSERVER_ADAPTER_ID, } from "../spreadsheet-structure-observer.js";
|
|
6
7
|
import { validateSourceObservationBoundary, } from "./source-observations.js";
|
|
8
|
+
import { buildDeterministicComprehensionArtifact } from "./comprehension-artifact.js";
|
|
7
9
|
import { loadReconstructSourceProfiles, } from "./source-profiles.js";
|
|
8
10
|
const CONCRETE_TARGET_MATERIAL_KINDS = new Set([
|
|
9
11
|
"code",
|
|
@@ -17,10 +19,6 @@ function isConcreteTargetMaterialKind(kind) {
|
|
|
17
19
|
function isoNow() {
|
|
18
20
|
return new Date().toISOString();
|
|
19
21
|
}
|
|
20
|
-
async function writeYamlDocument(filePath, value) {
|
|
21
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
22
|
-
await fs.writeFile(filePath, stringifyYaml(value), "utf8");
|
|
23
|
-
}
|
|
24
22
|
function stableObservationId(args) {
|
|
25
23
|
const digest = crypto
|
|
26
24
|
.createHash("sha256")
|
|
@@ -114,10 +112,199 @@ function defaultProfileForKind(profiles, kind) {
|
|
|
114
112
|
return matchingProfiles.find((candidate) => candidate.is_default_for_kind) ??
|
|
115
113
|
matchingProfiles[0];
|
|
116
114
|
}
|
|
117
|
-
|
|
115
|
+
/**
|
|
116
|
+
* Structural `content_excerpt` capture budgets (chars). Code source and text-readable
|
|
117
|
+
* document prose are captured whole so the file tail (later definitions, document
|
|
118
|
+
* goals/milestones/decisions) reaches seed authoring instead of being lost to a
|
|
119
|
+
* leading slice — both are projected whole at the seed stage, so a small capture
|
|
120
|
+
* would silently author the seed from a partial file. Other kinds (binary documents,
|
|
121
|
+
* spreadsheet/database structural inventories) keep a small leading sample — the
|
|
122
|
+
* observation is a structural sample there, not the whole file.
|
|
123
|
+
*
|
|
124
|
+
* Capture is MODEL-AGNOSTIC: it runs before the seed-stage (provider, model) is
|
|
125
|
+
* resolved, so it captures up to a fixed `DOCUMENT_CAPTURE_CEILING_CHARS` ceiling
|
|
126
|
+
* (a disk/pathological-input safety bound, NOT a window bound). The seed-stage
|
|
127
|
+
* prompt PROJECTION then slices that captured prose to a dynamic, model-aware
|
|
128
|
+
* budget (see deriveDocumentExcerptProjectionBudget + run.ts). The ceiling is set
|
|
129
|
+
* comfortably above any projection budget so projection only ever narrows, never
|
|
130
|
+
* needs more than was captured. A document longer than the ceiling is captured-
|
|
131
|
+
* truncated (`excerpt_truncated`); one longer than the projection budget is
|
|
132
|
+
* projection-truncated at the seed stage (recorded durably there). True
|
|
133
|
+
* window-overflow recovery (selecting the relevant tail) is a later stage (see
|
|
134
|
+
* development-records/design/20260616-large-input-observation).
|
|
135
|
+
*
|
|
136
|
+
* Material kind is detected by extension (target-material-kind.ts `DOCUMENT_EXTENSIONS`),
|
|
137
|
+
* and `document` includes binary formats (.pdf/.docx/.ppt/.rtf) that `textStats` still
|
|
138
|
+
* reads as UTF-8. Only text-readable document formats earn the whole-document budget —
|
|
139
|
+
* a binary document keeps the small sample so we never capture the ceiling in decoded
|
|
140
|
+
* binary bytes and spend the prompt on garbage; binary documents need an extraction step
|
|
141
|
+
* before reconstruct. This set must be the text-prose subset of `DOCUMENT_EXTENSIONS`: an
|
|
142
|
+
* extension that the classifier does not map to `document` (e.g. `.html`) would never
|
|
143
|
+
* reach this budget, so listing it here would be dead and misleading.
|
|
144
|
+
*/
|
|
145
|
+
const DEFAULT_STRUCTURAL_EXCERPT_CHAR_LIMIT = 6000;
|
|
146
|
+
/**
|
|
147
|
+
* Capture ceiling (chars) for whole-document prose. Static and model-agnostic —
|
|
148
|
+
* the upper bound for both capture AND the dynamic projection clamp, so the
|
|
149
|
+
* captured excerpt is always a superset of any projection. Set well above the
|
|
150
|
+
* largest projection budget (opus/gpt-5.5 ~1M-token windows derive ~0.5M-char
|
|
151
|
+
* budgets) to leave headroom; the absolute bound is disk, not a model window.
|
|
152
|
+
*/
|
|
153
|
+
export const DOCUMENT_CAPTURE_CEILING_CHARS = 5_000_000;
|
|
154
|
+
/**
|
|
155
|
+
* Floor (chars) for the seed-stage document projection budget. The static value
|
|
156
|
+
* used when the model window is unknown (mock realization, provider-only seat, an
|
|
157
|
+
* unregistered model, or a registry entry without context_window_tokens). Equal to
|
|
158
|
+
* the prior fixed document excerpt budget, so a model-unaware run is unchanged.
|
|
159
|
+
*/
|
|
160
|
+
export const DOCUMENT_EXCERPT_PROJECTION_FLOOR = 200_000;
|
|
161
|
+
/**
|
|
162
|
+
* Fraction of the model context window the projection budget may consume, leaving
|
|
163
|
+
* margin for prompt instructions, non-excerpt payload, output tokens, and
|
|
164
|
+
* tokenization variance. Window-proportional (not a fixed char margin) so the
|
|
165
|
+
* margin scales with the window (C1).
|
|
166
|
+
*/
|
|
167
|
+
const WINDOW_BUDGET_FRACTION = 0.5;
|
|
168
|
+
/**
|
|
169
|
+
* Conservative LOWER bound on chars-per-token used to convert a token window into
|
|
170
|
+
* a char budget. NOT the average (~4 for English): CJK-dense prose tokenizes to
|
|
171
|
+
* far fewer chars per token (≈1 or below), so a low bound keeps the char budget
|
|
172
|
+
* from implying more tokens than the window holds (C1). This is an approximation
|
|
173
|
+
* pending a real tokenizer; the CJK-dense live fixture (P6) validates/calibrates
|
|
174
|
+
* it, and the re-design trigger lowers it if even this conservative budget
|
|
175
|
+
* overflows.
|
|
176
|
+
*/
|
|
177
|
+
const CHARS_PER_TOKEN_LB = 1;
|
|
178
|
+
/**
|
|
179
|
+
* Chars reserved from the derived budget for the prompt's instructions, the
|
|
180
|
+
* non-excerpt structural payload, and the model's output allowance — all of which
|
|
181
|
+
* also consume the window alongside the document excerpt (C1).
|
|
182
|
+
*/
|
|
183
|
+
const PROMPT_OVERHEAD_RESERVE_CHARS = 50_000;
|
|
184
|
+
// Clamp integrity: the capture ceiling must be >= the projection floor so the
|
|
185
|
+
// derived budget's clamp(raw, FLOOR, CEILING) range is well-formed and the
|
|
186
|
+
// captured excerpt is always a superset of any projection (C4). A module-load
|
|
187
|
+
// assert fails fast on a future mis-edit of these constants.
|
|
188
|
+
if (DOCUMENT_EXCERPT_PROJECTION_FLOOR > DOCUMENT_CAPTURE_CEILING_CHARS) {
|
|
189
|
+
throw new Error("Invalid document excerpt budgets: DOCUMENT_EXCERPT_PROJECTION_FLOOR " +
|
|
190
|
+
`(${DOCUMENT_EXCERPT_PROJECTION_FLOOR}) must be <= ` +
|
|
191
|
+
`DOCUMENT_CAPTURE_CEILING_CHARS (${DOCUMENT_CAPTURE_CEILING_CHARS}).`);
|
|
192
|
+
}
|
|
193
|
+
const TEXT_READABLE_DOCUMENT_EXTENSIONS = new Set([".md", ".txt", ".adoc"]);
|
|
194
|
+
// M3a — explicit positive allowlist of real source-language extensions that earn whole-file
|
|
195
|
+
// capture/projection. The classifier also maps config/data extensions to `code`
|
|
196
|
+
// (.json/.yaml/.yml/.toml/.xml/.env/.cfg/.conf/.lock); those are deliberately NOT here and
|
|
197
|
+
// default to the bounded structural sample (a quality + cost volume reduction — NOT a
|
|
198
|
+
// secret-exposure closure, which is Track 2). Enumerated, no wildcard, so adding a language is
|
|
199
|
+
// a deliberate edit; fail-safe is bounded for any code extension not listed.
|
|
200
|
+
const CODE_WHOLE_CAPTURE_EXTENSIONS = new Set([
|
|
201
|
+
".c",
|
|
202
|
+
".cc",
|
|
203
|
+
".cpp",
|
|
204
|
+
".cs",
|
|
205
|
+
".css",
|
|
206
|
+
".dockerfile",
|
|
207
|
+
".go",
|
|
208
|
+
".graphql",
|
|
209
|
+
".java",
|
|
210
|
+
".js",
|
|
211
|
+
".jsx",
|
|
212
|
+
".kt",
|
|
213
|
+
".mjs",
|
|
214
|
+
".php",
|
|
215
|
+
".prisma",
|
|
216
|
+
".proto",
|
|
217
|
+
".py",
|
|
218
|
+
".rb",
|
|
219
|
+
".rs",
|
|
220
|
+
".scss",
|
|
221
|
+
".sh",
|
|
222
|
+
".swift",
|
|
223
|
+
".ts",
|
|
224
|
+
".tsx",
|
|
225
|
+
]);
|
|
226
|
+
// Extensionless build-language source basenames the classifier maps to `code` by name
|
|
227
|
+
// (CODE_BASENAMES). These are real source/build scripts → whole capture. The config/data
|
|
228
|
+
// basenames (package.json/tsconfig.json/cargo.toml/go.mod/pom.xml) are deliberately NOT here
|
|
229
|
+
// — they default to the bounded sample like other config/data (M3a-default), and are small
|
|
230
|
+
// enough that the sample is the whole file anyway.
|
|
231
|
+
const CODE_WHOLE_CAPTURE_BASENAMES = new Set([
|
|
232
|
+
"dockerfile",
|
|
233
|
+
"makefile",
|
|
234
|
+
"rakefile",
|
|
235
|
+
"gemfile",
|
|
236
|
+
]);
|
|
237
|
+
/**
|
|
238
|
+
* Single whole-capture / full-excerpt eligibility predicate (M3a). Consumed by BOTH the
|
|
239
|
+
* capture owner (`structuralExcerptCharLimit` here) and the seed-stage prompt projection
|
|
240
|
+
* (`isFullExcerptProjectionEligible` in run.ts), so capture and projection can never disagree
|
|
241
|
+
* — a bounded capture under a whole-projection budget would silently author the seed from a
|
|
242
|
+
* partial file. Takes the source REF (path/filename) so it is basename-aware: source-language
|
|
243
|
+
* code (allowlisted extension OR build-language basename) and text-readable documents
|
|
244
|
+
* (.md/.txt/.adoc) earn whole capture; binary documents, config/data code files, and
|
|
245
|
+
* structural-inventory kinds (spreadsheet/database) stay bounded.
|
|
246
|
+
*/
|
|
247
|
+
export function isFullExcerptCaptureEligible(kind, ref) {
|
|
248
|
+
const lower = typeof ref === "string" ? ref.toLowerCase() : "";
|
|
249
|
+
const ext = path.extname(lower);
|
|
250
|
+
const base = path.basename(lower);
|
|
251
|
+
if (kind === "code") {
|
|
252
|
+
return CODE_WHOLE_CAPTURE_EXTENSIONS.has(ext) ||
|
|
253
|
+
CODE_WHOLE_CAPTURE_BASENAMES.has(base);
|
|
254
|
+
}
|
|
255
|
+
return kind === "document" && isTextReadableDocumentExtension(ext);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Derives the seed-stage document projection budget (chars) for the active
|
|
259
|
+
* (provider, model) seat from its registered context window. Pure and total
|
|
260
|
+
* (never throws): an unresolved provider/model, an unregistered pair, or an entry
|
|
261
|
+
* without a window all fall back to the static FLOOR, so mock realization and
|
|
262
|
+
* provider-only seats are unchanged.
|
|
263
|
+
*
|
|
264
|
+
* The route key is the MODEL provider (the registry key, e.g. "openai"), not the
|
|
265
|
+
* runtime adapter provider (openai OAuth dispatches as "codex"; anthropic OAuth
|
|
266
|
+
* stays "anthropic"). The caller passes the model provider so the default
|
|
267
|
+
* gpt-5.5 OAuth seat resolves against `openai/gpt-5.5` (see reconstruct-api).
|
|
268
|
+
*
|
|
269
|
+
* This is the SINGLE model→budget conversion point: model literals never reach
|
|
270
|
+
* the char-budget tuning constants, so G2/INV-CFG-1 stay satisfied.
|
|
271
|
+
*/
|
|
272
|
+
export function deriveDocumentExcerptProjectionBudget(route, registry) {
|
|
273
|
+
if (!route.provider || !route.modelId) {
|
|
274
|
+
return DOCUMENT_EXCERPT_PROJECTION_FLOOR;
|
|
275
|
+
}
|
|
276
|
+
const window = registry.supported_models.find((entry) => entry.provider === route.provider && entry.model === route.modelId)?.context_window_tokens;
|
|
277
|
+
if (!window)
|
|
278
|
+
return DOCUMENT_EXCERPT_PROJECTION_FLOOR;
|
|
279
|
+
const raw = Math.floor(window * WINDOW_BUDGET_FRACTION * CHARS_PER_TOKEN_LB) -
|
|
280
|
+
PROMPT_OVERHEAD_RESERVE_CHARS;
|
|
281
|
+
return Math.min(Math.max(raw, DOCUMENT_EXCERPT_PROJECTION_FLOOR), DOCUMENT_CAPTURE_CEILING_CHARS);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* A text-readable document earns the whole-document excerpt budget. Both the capture
|
|
285
|
+
* (here) and the seed-stage prompt projection (run.ts) consult this single predicate so
|
|
286
|
+
* a binary document (.pdf/.docx/.ppt/.rtf) is never expanded — neither the captured
|
|
287
|
+
* artifact nor the prompt carries decoded binary bytes.
|
|
288
|
+
*/
|
|
289
|
+
export function isTextReadableDocumentExtension(extension) {
|
|
290
|
+
return (typeof extension === "string" &&
|
|
291
|
+
TEXT_READABLE_DOCUMENT_EXTENSIONS.has(extension.toLowerCase()));
|
|
292
|
+
}
|
|
293
|
+
function structuralExcerptCharLimit(kind, ref) {
|
|
294
|
+
// Whole-capture eligibility is the single shared predicate (M3a): a source-language file
|
|
295
|
+
// or a text-readable document is projected whole into seed authoring, so it must be
|
|
296
|
+
// CAPTURED whole too — a small leading sample would make seed authoring read only the
|
|
297
|
+
// file's head while the prompt-projection truncation never fires (capture < projection
|
|
298
|
+
// budget), silently authoring the seed from a partial file. Config/data code files,
|
|
299
|
+
// binary documents, and structural-inventory kinds (spreadsheet/database) stay bounded.
|
|
300
|
+
if (isFullExcerptCaptureEligible(kind, ref)) {
|
|
301
|
+
return DOCUMENT_CAPTURE_CEILING_CHARS;
|
|
302
|
+
}
|
|
303
|
+
return DEFAULT_STRUCTURAL_EXCERPT_CHAR_LIMIT;
|
|
304
|
+
}
|
|
305
|
+
async function textStats(ref, excerptLimit) {
|
|
118
306
|
try {
|
|
119
307
|
const text = await fs.readFile(ref, "utf8");
|
|
120
|
-
const excerptLimit = 6000;
|
|
121
308
|
return {
|
|
122
309
|
line_count: text.length === 0 ? 0 : text.split(/\r?\n/).length,
|
|
123
310
|
char_count: text.length,
|
|
@@ -136,20 +323,57 @@ async function textStats(ref) {
|
|
|
136
323
|
};
|
|
137
324
|
}
|
|
138
325
|
}
|
|
139
|
-
export async function buildReconstructSourceObservation(detection, lineage
|
|
326
|
+
export async function buildReconstructSourceObservation(detection, lineage,
|
|
327
|
+
// Defect-3 basis A: the caller asserts whether this ref is the user-provided
|
|
328
|
+
// reconstruct runtime target. Set true ONLY by the initial-target
|
|
329
|
+
// materialization; frontier-discovered / maturation-closure re-entry callers
|
|
330
|
+
// omit it (conservative default false). Every observation literal below sets
|
|
331
|
+
// is_runtime_target_source from this so source-safety can grant the
|
|
332
|
+
// material_claim/public_output tiers by provenance without leaking to
|
|
333
|
+
// non-target sources.
|
|
334
|
+
options) {
|
|
140
335
|
if (!detection.exists || !isConcreteTargetMaterialKind(detection.kind)) {
|
|
141
336
|
return null;
|
|
142
337
|
}
|
|
143
338
|
const extension = path.extname(detection.ref).toLowerCase();
|
|
144
339
|
const location = detection.ref;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
340
|
+
// Re-observation runs after an earlier detection: the ref may have vanished
|
|
341
|
+
// in between (TOCTOU). Treat a missing ref as nothing-to-observe (degrade to
|
|
342
|
+
// null, like the !detection.exists guard above) instead of crashing the run
|
|
343
|
+
// with an uncontextualized ENOENT. Other stat failures still propagate.
|
|
344
|
+
let stat;
|
|
345
|
+
try {
|
|
346
|
+
stat = await fs.stat(detection.ref);
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
const code = error.code;
|
|
350
|
+
// ENOENT: the ref was deleted; ENOTDIR: a parent path component became a
|
|
351
|
+
// file. Both mean the ref is no longer an observable source (TOCTOU), so
|
|
352
|
+
// degrade to null. Other stat failures still propagate.
|
|
353
|
+
if (code === "ENOENT" || code === "ENOTDIR")
|
|
354
|
+
return null;
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
// Spreadsheet sources route through the shared deterministic structure
|
|
358
|
+
// observer instead of the generic raw-text path (design S1 §2.2 / §11).
|
|
359
|
+
if (detection.kind === "spreadsheet") {
|
|
360
|
+
return buildSpreadsheetSourceObservation({
|
|
361
|
+
detection,
|
|
362
|
+
stat,
|
|
363
|
+
location,
|
|
364
|
+
lineage,
|
|
365
|
+
isRuntimeTargetSource: Boolean(options?.isRuntimeTargetSource),
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const stats = stat.isFile()
|
|
369
|
+
? await textStats(detection.ref, structuralExcerptCharLimit(detection.kind, detection.ref))
|
|
370
|
+
: {
|
|
371
|
+
line_count: null,
|
|
372
|
+
char_count: null,
|
|
373
|
+
content_sha256: null,
|
|
374
|
+
content_excerpt: null,
|
|
375
|
+
excerpt_truncated: false,
|
|
376
|
+
};
|
|
153
377
|
const observation = {
|
|
154
378
|
observation_id: stableObservationId({
|
|
155
379
|
sourceRef: detection.ref,
|
|
@@ -158,6 +382,7 @@ export async function buildReconstructSourceObservation(detection, lineage) {
|
|
|
158
382
|
round_id: lineage?.roundId ?? "initial_source_frontier",
|
|
159
383
|
observation_batch_id: lineage?.observationBatchId ?? "source-observation-batch:initial",
|
|
160
384
|
triggering_frontier_validation_ref: lineage?.triggeringFrontierValidationRef ?? null,
|
|
385
|
+
is_runtime_target_source: Boolean(options?.isRuntimeTargetSource),
|
|
161
386
|
target_material_kind: detection.kind,
|
|
162
387
|
adapter_id: `minimal-${detection.kind}-structure-observer`,
|
|
163
388
|
source_ref: detection.ref,
|
|
@@ -181,6 +406,96 @@ export async function buildReconstructSourceObservation(detection, lineage) {
|
|
|
181
406
|
}
|
|
182
407
|
return observation;
|
|
183
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* Observe a spreadsheet source through the shared structure observer (design S1
|
|
411
|
+
* §2.2) — a deterministic, LLM-free structural inventory. The observation carries
|
|
412
|
+
* NO raw cell values: it never emits the generic path's `content_excerpt` (which for
|
|
413
|
+
* a workbook would be raw data values) and the inventory's aggregate-only vocab
|
|
414
|
+
* leaves `top_values` absent. The one NARROWED exception (design-C, not raw data) is
|
|
415
|
+
* `data_validations[].members` — the bounded, DECLARED type=list enum labels parsed from a
|
|
416
|
+
* formula1 literal (never observed values). `content_sha256` is the RAW-byte hash (§11 HASH-1)
|
|
417
|
+
* surfaced at the structural_data top level because the downstream source-scout-pack
|
|
418
|
+
* reads it there; the full inventory is nested under `workbook_inventory` as the
|
|
419
|
+
* structural substrate the seed-authoring prompt observes. xlsx-family kinds are
|
|
420
|
+
* not yet extractable (P4) and arrive here carrying an `unsupported_reason`.
|
|
421
|
+
*/
|
|
422
|
+
/**
|
|
423
|
+
* The honest observation summary for a workbook inventory (P6): the base disclosure
|
|
424
|
+
* plus the fixed phrases the boundary gate asserts when the capture/macro flags are
|
|
425
|
+
* set. This is the single emit path; it and the gate bind to the SAME exported phrase
|
|
426
|
+
* consts, so assert + emit cannot drift. Exported for the gate-binding regression test.
|
|
427
|
+
*/
|
|
428
|
+
export function buildSpreadsheetObservationSummary(basename, inventory) {
|
|
429
|
+
const parts = [
|
|
430
|
+
inventory.unsupported_reason
|
|
431
|
+
? `spreadsheet workbook observed at ${basename} — extraction unsupported (${inventory.unsupported_reason}), structure_inspected_only`
|
|
432
|
+
: `spreadsheet workbook observed at ${basename} — ${inventory.sheets.length} sheet(s), structure_inspected_only`,
|
|
433
|
+
];
|
|
434
|
+
if (inventory.capture_truncated)
|
|
435
|
+
parts.push(SPREADSHEET_CAPTURE_TRUNCATED_PHRASE);
|
|
436
|
+
if (inventory.macro_present)
|
|
437
|
+
parts.push(SPREADSHEET_MACRO_PRESENT_PHRASE);
|
|
438
|
+
return parts.join("; ");
|
|
439
|
+
}
|
|
440
|
+
async function buildSpreadsheetSourceObservation(args) {
|
|
441
|
+
const { detection, stat, location, lineage, isRuntimeTargetSource } = args;
|
|
442
|
+
const basename = path.basename(detection.ref);
|
|
443
|
+
const extension = path.extname(detection.ref).toLowerCase();
|
|
444
|
+
// Route through the single shared projection: the structural_data inventory
|
|
445
|
+
// carries aggregate counts / structure only, never raw cell values (the sole narrowed
|
|
446
|
+
// exception is data_validations[].members — bounded declared type=list enum labels).
|
|
447
|
+
const inventory = projectInventoryForAdmission(await observeSpreadsheetSource(detection.ref));
|
|
448
|
+
const summary = buildSpreadsheetObservationSummary(basename, inventory);
|
|
449
|
+
const observationId = stableObservationId({ sourceRef: detection.ref, location });
|
|
450
|
+
// P1-C1 §5.7: the ComprehensionArtifact COMPANIONS the inventory (joined by observation_id) — a
|
|
451
|
+
// deterministic-only edition in this cut (LLM-touch fields are explicit not_applicable). It is
|
|
452
|
+
// validated by validateSourceObservationBoundary below (construction throws on an invalid contract).
|
|
453
|
+
const comprehensionArtifact = buildDeterministicComprehensionArtifact({
|
|
454
|
+
observationId,
|
|
455
|
+
inventory,
|
|
456
|
+
});
|
|
457
|
+
const observation = {
|
|
458
|
+
observation_id: observationId,
|
|
459
|
+
round_id: lineage?.roundId ?? "initial_source_frontier",
|
|
460
|
+
observation_batch_id: lineage?.observationBatchId ?? "source-observation-batch:initial",
|
|
461
|
+
triggering_frontier_validation_ref: lineage?.triggeringFrontierValidationRef ?? null,
|
|
462
|
+
is_runtime_target_source: isRuntimeTargetSource,
|
|
463
|
+
target_material_kind: "spreadsheet",
|
|
464
|
+
adapter_id: SPREADSHEET_OBSERVER_ADAPTER_ID,
|
|
465
|
+
source_ref: detection.ref,
|
|
466
|
+
location,
|
|
467
|
+
summary,
|
|
468
|
+
structural_data: {
|
|
469
|
+
basename,
|
|
470
|
+
extension: extension || null,
|
|
471
|
+
path_kind: stat.isDirectory() ? "directory" : "file",
|
|
472
|
+
size_bytes: stat.isFile() ? stat.size : null,
|
|
473
|
+
// Raw-byte hash (§11 HASH-1) surfaced top-level for source-scout-pack
|
|
474
|
+
// admission, which reads structural_data.content_sha256.
|
|
475
|
+
content_sha256: inventory.content_sha256,
|
|
476
|
+
workbook_inventory: inventory,
|
|
477
|
+
comprehension_artifact: comprehensionArtifact,
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
const validation = validateSourceObservationBoundary(observation);
|
|
481
|
+
if (!validation.valid) {
|
|
482
|
+
throw new Error(`Invalid source observation boundary for ${detection.ref}: ${validation.violations.join("; ")}`);
|
|
483
|
+
}
|
|
484
|
+
return observation;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* The `unsupported_reason` of a spreadsheet observation's workbook inventory, or
|
|
488
|
+
* null when the observation is not an unsupported workbook. An unsupported workbook
|
|
489
|
+
* (.xls/.xlsb/.ods, corrupt, oversized) carries no structural evidence, so the
|
|
490
|
+
* materialize loop demotes it to a skip rather than admitting an empty observation.
|
|
491
|
+
*/
|
|
492
|
+
export function spreadsheetUnsupportedReason(observation) {
|
|
493
|
+
if (observation.target_material_kind !== "spreadsheet")
|
|
494
|
+
return null;
|
|
495
|
+
const inventory = observation.structural_data.workbook_inventory;
|
|
496
|
+
const reason = inventory?.unsupported_reason;
|
|
497
|
+
return typeof reason === "string" && reason.length > 0 ? reason : null;
|
|
498
|
+
}
|
|
184
499
|
function stableFrontierRefId(unit) {
|
|
185
500
|
const digest = crypto
|
|
186
501
|
.createHash("sha256")
|
|
@@ -291,10 +606,6 @@ export async function materializeReconstructPreparationArtifacts(params) {
|
|
|
291
606
|
source: "binding",
|
|
292
607
|
},
|
|
293
608
|
};
|
|
294
|
-
const initialSourceFrontier = buildInitialSourceFrontier({
|
|
295
|
-
sessionId,
|
|
296
|
-
inventory,
|
|
297
|
-
});
|
|
298
609
|
const observations = [];
|
|
299
610
|
const skippedRefs = [];
|
|
300
611
|
for (const unit of inventory.inventory_units) {
|
|
@@ -309,10 +620,56 @@ export async function materializeReconstructPreparationArtifacts(params) {
|
|
|
309
620
|
const refDetection = detection.per_ref.find((candidate) => candidate.ref === unit.ref);
|
|
310
621
|
if (!refDetection)
|
|
311
622
|
continue;
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
623
|
+
// Defect-3 basis A: these are the user-provided reconstruct runtime-target
|
|
624
|
+
// refs (the initial target inventory), so they are runtime-target sources.
|
|
625
|
+
const observation = await buildReconstructSourceObservation(refDetection, undefined, {
|
|
626
|
+
isRuntimeTargetSource: true,
|
|
627
|
+
});
|
|
628
|
+
if (observation) {
|
|
629
|
+
const unsupportedReason = spreadsheetUnsupportedReason(observation);
|
|
630
|
+
if (unsupportedReason) {
|
|
631
|
+
// A workbook whose format the observer cannot extract (.xls/.xlsb/.ods,
|
|
632
|
+
// corrupt, oversized) yields an inventory carrying only `unsupported_reason`
|
|
633
|
+
// — no structural evidence. After the gate flip such a ref is runnable and
|
|
634
|
+
// produces an observation, so without this demotion a sole legacy-format
|
|
635
|
+
// target would pass the zero-observation halt and reach LLM authoring with
|
|
636
|
+
// empty evidence. Demote it to a skip so the run fails loud as unsupported
|
|
637
|
+
// (the evidence gate stays honest about un-observed material).
|
|
638
|
+
unit.scan_status = "skipped";
|
|
639
|
+
unit.skip_reason = `spreadsheet extraction unsupported: ${unsupportedReason}`;
|
|
640
|
+
skippedRefs.push({
|
|
641
|
+
ref: unit.ref,
|
|
642
|
+
target_material_kind: unit.target_material_kind,
|
|
643
|
+
reason: unit.skip_reason,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
observations.push(observation);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
else {
|
|
651
|
+
// buildReconstructSourceObservation returns null when the ref is no longer
|
|
652
|
+
// a concrete, existing source at observation time (e.g. deleted between
|
|
653
|
+
// detection and re-observation). Mark the inventory unit skipped — the
|
|
654
|
+
// single source of truth that the initial frontier (built below), the
|
|
655
|
+
// zero-observation halt, and later frontier admission all derive from — so
|
|
656
|
+
// the vanished ref is excluded everywhere instead of being silently dropped
|
|
657
|
+
// or re-queued by the deterministic first-frontier scout.
|
|
658
|
+
unit.scan_status = "skipped";
|
|
659
|
+
unit.skip_reason = "source ref unavailable at observation time";
|
|
660
|
+
skippedRefs.push({
|
|
661
|
+
ref: unit.ref,
|
|
662
|
+
target_material_kind: unit.target_material_kind,
|
|
663
|
+
reason: unit.skip_reason,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
315
666
|
}
|
|
667
|
+
// Built after observation so refs marked skipped above (vanished mid-run) are
|
|
668
|
+
// excluded from frontier source_refs rather than re-admitted later.
|
|
669
|
+
const initialSourceFrontier = buildInitialSourceFrontier({
|
|
670
|
+
sessionId,
|
|
671
|
+
inventory,
|
|
672
|
+
});
|
|
316
673
|
const sourceObservations = {
|
|
317
674
|
schema_version: "1",
|
|
318
675
|
session_id: sessionId,
|