#!/usr/bin/env node // cut.mjs — entry point for the DETERMINISTIC half of the OKF bundle // consumption skill (contract C4: the script cuts, the agent judges). // // Usage: // node scripts/okf-consume/cut.mjs --bundle --query "" [--k N] // [--budget N] [--mode index|headscan] [--json] // // The OKF envelope of each bundle document is parsed by llm-ingestion-okf // itself (materialize.parse_frontmatter), invoked as a subprocess. Nothing in // this repo re-implements it. Set OKF_PYTHON to point at that package's venv // interpreter; without it the envelope is reported as unavailable rather than // guessed at. import { readFileSync, readdirSync, existsSync } from 'node:fs'; import { join } from 'node:path'; import { execFileSync } from 'node:child_process'; import { tokenize, parseIndex, headScan, scoreCandidate, cutToBudget, AGENT_BUDGET, HEAD_SCAN_LINES, } from './lib/bundle-cut.mjs'; const OKF_PYTHON = process.env.OKF_PYTHON || '/Users/ktg/repos/llm-ingestion-okf/.venv/bin/python'; /** * Parse the OKF envelope of every document in the bundle, using the producing * package. Returns a map target -> envelope, or null when the interpreter is * unavailable — never a hand-rolled fallback, because a fallback would be this * repo owning a format it does not own. */ export function readEnvelopes(bundleDir, python = OKF_PYTHON) { if (!existsSync(python)) return null; const src = ` import json, sys from pathlib import Path from llm_ingestion_okf.materialize import parse_frontmatter d = Path(sys.argv[1]) out = {} for p in sorted(d.iterdir()): if p.name == 'index.md' or p.suffix != '.md': continue try: out[p.name] = parse_frontmatter(p) except Exception as e: out[p.name] = {'_error': type(e).__name__} json.dump(out, sys.stdout) `; try { return JSON.parse(execFileSync(python, ['-c', src, bundleDir], { encoding: 'utf8' })); } catch { return null; } } /** * Build the candidate set. In 'index' mode the ranker sees only what the index * carries (labels); in 'headscan' mode it also sees the preserved frontmatter * and H2 headings inside the first HEAD_SCAN_LINES body lines. * * assemblyChars counts what the SCRIPT read to get here. It is reported * separately from what reaches the agent, because those are different costs and * conflating them is how a cut looks cheaper than it is. */ export function buildCandidates({ bundleDir, mode = 'headscan', entries, readFile = readFileSync }) { let assemblyChars = 0; const candidates = []; for (const { label, target } of entries) { const path = join(bundleDir, target); let text = ''; try { text = readFile(path, 'utf8'); } catch { continue; } const cand = { label, target, body: text, h2: [] }; if (mode === 'headscan') { const head = text.split('\n').slice(0, HEAD_SCAN_LINES + 8).join('\n'); assemblyChars += head.length; Object.assign(cand, headScan(text)); } candidates.push(cand); } return { candidates, assemblyChars }; } export function runCut({ bundleDir, query, mode = 'headscan', k = Infinity, budget = AGENT_BUDGET }) { const indexPath = join(bundleDir, 'index.md'); const indexMd = readFileSync(indexPath, 'utf8'); const entries = parseIndex(indexMd); const { candidates, assemblyChars: scanChars } = buildCandidates({ bundleDir, mode, entries }); const queryTokens = tokenize(query); for (const c of candidates) c.score = scoreCandidate(queryTokens, c); const result = cutToBudget({ candidates, budget, k, applySupersession: mode === 'headscan', }); const assemblyChars = indexMd.length + scanChars + result.delivered.reduce((n, d) => n + d.body.length, 0); return { ...result, mode, queryTokens, indexChars: indexMd.length, scanChars, assemblyChars }; } function main(argv) { const arg = (name, fallback) => { const i = argv.indexOf(`--${name}`); return i === -1 ? fallback : argv[i + 1]; }; const bundleDir = arg('bundle'); const query = arg('query'); if (!bundleDir || !query) { console.error('usage: cut.mjs --bundle --query "" [--k N] [--budget N] [--mode index|headscan] [--json]'); process.exit(2); } const r = runCut({ bundleDir, query, mode: arg('mode', 'headscan'), k: arg('k') ? Number(arg('k')) : Infinity, budget: Number(arg('budget', AGENT_BUDGET)), }); if (argv.includes('--json')) { console.log(JSON.stringify({ ...r, delivered: r.delivered.map((d) => ({ label: d.label, target: d.target, score: d.score })), }, null, 2)); return; } // The receipt comes FIRST, so a reader cannot consume the cut without seeing // what was held back. console.log(`## Kutt-kvittering (OKF-bundle, modus: ${r.mode})`); console.log(`- Levert: ${r.delivered.length} av ${r.denominator} dokumenter`); console.log(`- \`[unread]\`: ${r.unread} av ${r.denominator} dokumenter i bundlen ble IKKE lest`); if (r.superseded) console.log(`- Forkastet som \`status: erstattet\`: ${r.superseded}`); if (r.statusUnknown) console.log(`- Uten \`status\`-felt (byggingen registrerte ingen status — ikke det samme som gjeldende): ${r.statusUnknown}`); console.log(`- Agent-kontekst: ${r.agentChars} tegn av budsjett ${arg('budget', AGENT_BUDGET)}`); console.log(`- Assemblering (hva skriptet leste): ${r.assemblyChars} tegn`); console.log(''); for (const d of r.delivered) { console.log(`### ${d.label} (score ${d.score})`); console.log(d.body); } } if (import.meta.url === `file://${process.argv[1]}`) main(process.argv.slice(2));