@kodr/aiwrangler 0.1.1 → 0.1.3

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.
@@ -6,10 +6,384 @@ import {
6
6
  diffFingerprint,
7
7
  expandPath,
8
8
  formatFindings,
9
+ loadConfig,
9
10
  parseCargoJson,
10
11
  readRuntimeOverridesFile,
11
- runDeterministic
12
- } from "./chunk-EAQASAXL.js";
12
+ runDeterministic,
13
+ sha256Hex
14
+ } from "./chunk-F3YFQ4NW.js";
15
+
16
+ // src/types.ts
17
+ var PRECEDENCE = {
18
+ ruling: 0,
19
+ paper: 1,
20
+ door: 1,
21
+ spec: 2,
22
+ doc: 3,
23
+ nonRuling: 4
24
+ };
25
+
26
+ // src/groundtruth/sectionize.ts
27
+ function splitMarkdown(md) {
28
+ const lines = md.split("\n");
29
+ const sections = [];
30
+ let current = null;
31
+ for (let i = 0; i < lines.length; i++) {
32
+ const line = lines[i] ?? "";
33
+ const m = line.match(/^(#{1,6})\s+(.*)$/);
34
+ if (m) {
35
+ if (current) sections.push(current);
36
+ current = { level: m[1].length, title: m[2].trim(), text: "", line: i + 1 };
37
+ } else if (current) {
38
+ current.text += line + "\n";
39
+ }
40
+ }
41
+ if (current) sections.push(current);
42
+ return sections.map((s) => ({ ...s, text: s.text.trim() }));
43
+ }
44
+ function slug(title) {
45
+ return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
46
+ }
47
+
48
+ // src/groundtruth/parsers.ts
49
+ function section(partial) {
50
+ return { precedence: PRECEDENCE.doc, ...partial };
51
+ }
52
+ var ROMAN_RE = /^[IVX]+(?:-[A-Z])?$/;
53
+ function parseSpec(md, source) {
54
+ const out = [];
55
+ for (const sec of splitMarkdown(md)) {
56
+ const m = sec.title.match(/^(\d+)\.\s+(.*)$/);
57
+ if (!m) continue;
58
+ const title = m[2];
59
+ const ref = title.match(/\(([^)]+)\)\s*$/);
60
+ const paperRef = ref && ROMAN_RE.test(ref[1]) ? ref[1] : void 0;
61
+ out.push(
62
+ section({
63
+ id: `SPEC-${m[1]}`,
64
+ kind: "spec",
65
+ title,
66
+ text: sec.text,
67
+ source,
68
+ status: "info",
69
+ ...paperRef ? { paperRef } : {},
70
+ precedence: PRECEDENCE.spec
71
+ })
72
+ );
73
+ }
74
+ return out;
75
+ }
76
+ function parseRules(md, source) {
77
+ const lines = md.split("\n");
78
+ const out = [];
79
+ let current = null;
80
+ const flush = () => {
81
+ if (!current) return;
82
+ out.push(
83
+ section({
84
+ id: `RULE-${current.n}`,
85
+ kind: "rule",
86
+ title: current.lines[0].trim(),
87
+ text: current.lines.join("\n").trim(),
88
+ source,
89
+ status: "info",
90
+ precedence: PRECEDENCE.spec
91
+ })
92
+ );
93
+ current = null;
94
+ };
95
+ for (const line of lines) {
96
+ const m = line.match(/^(\d+)\.\s+(.*)$/);
97
+ if (m) {
98
+ flush();
99
+ current = { n: m[1], lines: [m[2]] };
100
+ continue;
101
+ }
102
+ if (current && (/^\s{2,}/.test(line) || line.trim() === "")) {
103
+ if (line.trim() === "" && current.lines[current.lines.length - 1] === "") continue;
104
+ current.lines.push(line.trim() === "" ? "" : line.trim());
105
+ continue;
106
+ }
107
+ flush();
108
+ }
109
+ flush();
110
+ return out;
111
+ }
112
+ function parseDivergences(md, source) {
113
+ const lines = md.split("\n");
114
+ const out = [];
115
+ let topic = "";
116
+ let current = null;
117
+ const flush = () => {
118
+ if (!current) return;
119
+ const body = current.lines.join("\n").trim();
120
+ const marker = body.indexOf("**RULED");
121
+ const ruled = marker >= 0;
122
+ const ruling = ruled ? body.slice(marker).replace(/\*\*/g, "").replace(/\s+/g, " ").trim().slice(0, 800) : void 0;
123
+ out.push(
124
+ section({
125
+ id: `DIV-D-${current.id}`,
126
+ kind: "divergence",
127
+ title: `${current.topic ? current.topic + ": " : ""}${current.lines[0].trim()}`,
128
+ text: body,
129
+ source,
130
+ status: ruled ? "ruled" : "open",
131
+ ...ruled && ruling ? { ruling } : {},
132
+ precedence: ruled ? PRECEDENCE.ruling : PRECEDENCE.nonRuling
133
+ })
134
+ );
135
+ current = null;
136
+ };
137
+ for (const line of lines) {
138
+ const h = line.match(/^##\s+(.*)$/);
139
+ if (h) {
140
+ flush();
141
+ topic = h[1].trim();
142
+ continue;
143
+ }
144
+ const m = line.match(/^D-(\d+)\s+(.*)$/);
145
+ if (m) {
146
+ flush();
147
+ current = { id: m[1], lines: [m[2]], topic };
148
+ continue;
149
+ }
150
+ if (current) current.lines.push(line);
151
+ }
152
+ flush();
153
+ return out;
154
+ }
155
+ function parseQuestions(md, source) {
156
+ const lines = md.split("\n");
157
+ const out = [];
158
+ let heading = "";
159
+ let current = null;
160
+ const flush = () => {
161
+ if (!current) return;
162
+ const body = current.lines.join("\n").trim();
163
+ const marker = body.indexOf("**ANSWERED");
164
+ const answered = marker >= 0;
165
+ const ruling = answered ? body.slice(marker).replace(/\*\*/g, "").replace(/\s+/g, " ").trim().slice(0, 800) : void 0;
166
+ out.push(
167
+ section({
168
+ id: `Q-${current.n}`,
169
+ kind: "question",
170
+ title: current.lines[0].trim(),
171
+ text: body,
172
+ source,
173
+ status: answered ? "answered" : "open",
174
+ ...answered && ruling ? { ruling } : {},
175
+ precedence: answered ? PRECEDENCE.ruling : PRECEDENCE.nonRuling
176
+ })
177
+ );
178
+ current = null;
179
+ };
180
+ for (const line of lines) {
181
+ const h = line.match(/^##\s+(.*)$/);
182
+ if (h) {
183
+ flush();
184
+ heading = h[1].trim();
185
+ continue;
186
+ }
187
+ const m = line.match(/^(\d+)\.\s+(.*)$/);
188
+ if (m) {
189
+ flush();
190
+ current = { n: m[1], lines: [m[2]], heading };
191
+ continue;
192
+ }
193
+ if (current) current.lines.push(line);
194
+ }
195
+ flush();
196
+ return out;
197
+ }
198
+ function parsePlan(md, source) {
199
+ const out = [];
200
+ for (const sec of splitMarkdown(md)) {
201
+ if (!/one-way door|launch gate/i.test(sec.title)) continue;
202
+ out.push(
203
+ section({
204
+ id: `DOOR-${slug(sec.title) || out.length + 1}`,
205
+ kind: "door",
206
+ title: sec.title,
207
+ text: sec.text,
208
+ source,
209
+ status: "info",
210
+ precedence: PRECEDENCE.door
211
+ })
212
+ );
213
+ }
214
+ return out;
215
+ }
216
+ function parseDoc(md, source, label) {
217
+ return splitMarkdown(md).filter((s) => s.text.length > 0).map(
218
+ (sec, i) => section({
219
+ id: `DOC-${label}-${String(i + 1).padStart(2, "0")}`,
220
+ kind: "doc",
221
+ title: sec.title || "(intro)",
222
+ text: sec.text,
223
+ source,
224
+ status: "info",
225
+ precedence: PRECEDENCE.doc
226
+ })
227
+ );
228
+ }
229
+ var PAPER_HEADING_RE = /^([IVX]+(?:-[A-Z])?)?\.?\s{1,4}(.+)$/;
230
+ var ROMAN_ONLY_RE = /^[IVX]+$/;
231
+ function mergeSpacedCaps(title) {
232
+ const tokens = title.split(/\s+/).filter(Boolean);
233
+ const merged = [];
234
+ let i = 0;
235
+ while (i < tokens.length) {
236
+ const tok = tokens[i];
237
+ const next = tokens[i + 1];
238
+ if (next !== void 0 && /^-?[A-Z]$/.test(tok) && /^[A-Z]/.test(next) && next.length > 1) {
239
+ merged.push((tok.startsWith("-") ? tok : "") + next);
240
+ i += 2;
241
+ continue;
242
+ }
243
+ if (tok.length === 1 && /^[A-Z]$/.test(tok) && next !== void 0 && /^[A-Z]/.test(next)) {
244
+ merged.push(tok + next);
245
+ i += 2;
246
+ continue;
247
+ }
248
+ merged.push(tok);
249
+ i++;
250
+ }
251
+ return merged.join(" ").replace(/ -/g, "-").replace(/\s+,/g, ",");
252
+ }
253
+ function capsRatio(s) {
254
+ const letters = s.replace(/[^A-Za-z]/g, "");
255
+ if (letters.length === 0) return 0;
256
+ const upper = letters.replace(/[^A-Z]/g, "");
257
+ return upper.length / letters.length;
258
+ }
259
+ function parsePaper(text, source) {
260
+ const lines = text.split("\n").map((l) => l.replace(/^\f/, ""));
261
+ const byId = /* @__PURE__ */ new Map();
262
+ let current = null;
263
+ const looksLikeCapsFragment = (line) => {
264
+ const t = line.trim();
265
+ return t.length >= 3 && t.length <= 40 && !/[.:;]$/.test(t) && !/\.\s*\.\s*\./.test(t) && capsRatio(t) >= 0.6;
266
+ };
267
+ const flush = () => {
268
+ if (!current) return;
269
+ const body = current.lines.join("\n").trim();
270
+ const existing = byId.get(current.id);
271
+ if (existing) {
272
+ existing.text = (existing.text + "\n" + body).trim();
273
+ } else {
274
+ byId.set(
275
+ current.id,
276
+ section({
277
+ id: `PAPER-${current.id}`,
278
+ kind: "paper",
279
+ title: current.title,
280
+ text: body,
281
+ source,
282
+ status: "info",
283
+ precedence: PRECEDENCE.paper
284
+ })
285
+ );
286
+ }
287
+ current = null;
288
+ };
289
+ for (let li = 0; li < lines.length; li++) {
290
+ const line = lines[li] ?? "";
291
+ if (/\.\s*\.\s*\./.test(line)) continue;
292
+ const m = line.match(PAPER_HEADING_RE);
293
+ if (m && m[1] && !ROMAN_ONLY_RE.test(m[2] ?? "") && !/^[ivx]+$/.test(m[1])) {
294
+ let title = mergeSpacedCaps(m[2] ?? "");
295
+ let consumed = 1;
296
+ while (li + consumed < lines.length && looksLikeCapsFragment(lines[li + consumed] ?? "") && title.split(/\s+/).length < 10) {
297
+ title = title + " " + mergeSpacedCaps((lines[li + consumed] ?? "").trim());
298
+ consumed++;
299
+ }
300
+ const letters = title.replace(/[^A-Za-z]/g, "");
301
+ if (letters.length >= 4 && capsRatio(title) >= 0.7) {
302
+ flush();
303
+ current = { id: m[1], title: title.replace(/\s{2,}/g, " ").trim(), lines: [] };
304
+ li += consumed - 1;
305
+ continue;
306
+ }
307
+ }
308
+ if (current) current.lines.push(line);
309
+ }
310
+ flush();
311
+ return [...byId.values()];
312
+ }
313
+
314
+ // src/groundtruth/whitepaper.ts
315
+ import { spawnSync } from "child_process";
316
+ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
317
+ import { join } from "path";
318
+ function ensurePaperText(config, root) {
319
+ const pdf = expandPath(config.corpus.whitepaper, root);
320
+ const cacheDir = expandPath(config.cache.dir, root);
321
+ const cached = join(cacheDir, "whitepaper.txt");
322
+ const pdfMtime = existsSync(pdf) ? statSync(pdf).mtimeMs : 0;
323
+ if (existsSync(cached) && statSync(cached).mtimeMs >= pdfMtime) {
324
+ return { text: readFileSync(cached, "utf8"), extracted: false };
325
+ }
326
+ if (!existsSync(pdf)) return { text: null, extracted: false };
327
+ const res = spawnSync("pdftotext", [pdf, "-"], { encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
328
+ if (res.error || res.status !== 0) {
329
+ const reason = res.error ? res.error.message : `pdftotext exit ${res.status}`;
330
+ throw new Error(`whitepaper extraction failed (${reason}); is poppler-utils installed?`);
331
+ }
332
+ const text = res.stdout;
333
+ if (text.split(/\s+/).length < 1e3) throw new Error("whitepaper extraction produced suspiciously little text; refusing to cache");
334
+ mkdirSync(cacheDir, { recursive: true });
335
+ writeFileSync(cached, text, "utf8");
336
+ return { text, extracted: true };
337
+ }
338
+
339
+ // src/groundtruth/corpus.ts
340
+ import { readFileSync as readFileSync2 } from "fs";
341
+ function slugFromPath(rel) {
342
+ return rel.toLowerCase().replace(/\.[a-z0-9]+$/i, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
343
+ }
344
+ function readOr(path, fallback) {
345
+ try {
346
+ return { text: readFileSync2(path, "utf8"), sections: [] };
347
+ } catch {
348
+ return { text: null, sections: fallback };
349
+ }
350
+ }
351
+ function loadCorpus(config, root) {
352
+ const sections = [];
353
+ const sources = [];
354
+ const addFile = (path, parse) => {
355
+ const resolved = expandPath(path, root);
356
+ const { text } = readOr(resolved, []);
357
+ if (text === null) return;
358
+ sources.push({ path: resolved, hash: sha256Hex(text) });
359
+ sections.push(...parse(text, resolved));
360
+ };
361
+ addFile(config.corpus.spec, parseSpec);
362
+ addFile(config.corpus.rules, parseRules);
363
+ addFile(config.corpus.divergences, parseDivergences);
364
+ addFile(config.corpus.questions, parseQuestions);
365
+ addFile(config.corpus.plan, parsePlan);
366
+ for (const doc of config.corpus.docs) {
367
+ const resolved = expandPath(doc, root);
368
+ let label = doc.split("/").pop()?.replace(/\.[a-z]+$/i, "") ?? "doc";
369
+ const rel = resolved.startsWith(root + "/") ? resolved.slice(root.length + 1) : null;
370
+ if (rel && rel.includes("/")) label = slugFromPath(rel);
371
+ addFile(doc, (md, source) => parseDoc(md, source, label));
372
+ }
373
+ const paper = ensurePaperText(config, root);
374
+ if (paper.text !== null) {
375
+ const paperPath = expandPath(config.corpus.whitepaper, root);
376
+ sources.push({ path: paperPath, hash: sha256Hex(paper.text) });
377
+ sections.push(...parsePaper(paper.text, paperPath));
378
+ }
379
+ const hash = sha256Hex(JSON.stringify({ sources: sources.map((s) => [s.path, s.hash]).sort((a, b) => (a[0] ?? "").localeCompare(b[0] ?? "")), ids: sections.map((s) => s.id).sort() }));
380
+ return { sections, sources, hash, paperText: paper.text ?? "" };
381
+ }
382
+ function loadCorpusFrom(startDir) {
383
+ const loaded = loadConfig(startDir);
384
+ const corpus = loadCorpus(loaded.config, loaded.root);
385
+ return { loaded, corpus };
386
+ }
13
387
 
14
388
  // src/jev/questions.ts
15
389
  function noul(instructions, yes, no) {
@@ -48,9 +422,9 @@ function lexicalScore(index, query) {
48
422
  const qTerms = tokenize(query);
49
423
  const N = Math.max(index.sections.length, 1);
50
424
  const scores = [];
51
- for (const section of index.sections) {
52
- const titleTerms = new Set(tokenize(section.title));
53
- const bodyTerms = tokenize(section.text);
425
+ for (const section2 of index.sections) {
426
+ const titleTerms = new Set(tokenize(section2.title));
427
+ const bodyTerms = tokenize(section2.text);
54
428
  const tf = /* @__PURE__ */ new Map();
55
429
  for (const t of bodyTerms) tf.set(t, (tf.get(t) ?? 0) + 1);
56
430
  let score2 = 0;
@@ -62,8 +436,8 @@ function lexicalScore(index, query) {
62
436
  if (tfCount > 0) score2 += idf * (1 + Math.log(tfCount)) / (1 + Math.log(bodyTerms.length || 1)) * 10;
63
437
  if (titleTerms.has(term)) score2 += idf * 3;
64
438
  }
65
- if (section.kind === "door") score2 += 0.5;
66
- scores.push({ section, score: score2 });
439
+ if (section2.kind === "door") score2 += 0.5;
440
+ scores.push({ section: section2, score: score2 });
67
441
  }
68
442
  return scores.sort((a, b) => b.score - a.score);
69
443
  }
@@ -831,12 +1205,12 @@ async function runCheckBattery(config, jev, corpus, input, opts = {}) {
831
1205
  });
832
1206
  return { findings, verdicts, sections: ranked, usage: res.usage, examinedFiles };
833
1207
  }
834
- function overridesFor(section, rulings) {
1208
+ function overridesFor(section2, rulings) {
835
1209
  const out = [];
836
1210
  const sectionTerms = new Set(
837
- section.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 3)
1211
+ section2.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 3)
838
1212
  );
839
- if (section.paperRef) sectionTerms.add(section.paperRef.toLowerCase());
1213
+ if (section2.paperRef) sectionTerms.add(section2.paperRef.toLowerCase());
840
1214
  for (const r of rulings) {
841
1215
  const text = (r.title + " " + (r.ruling ?? "")).toLowerCase();
842
1216
  let hits = 0;
@@ -903,12 +1277,12 @@ async function selectRootCause(jev, diags) {
903
1277
  }
904
1278
 
905
1279
  // src/gates/check/snapshot.ts
906
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
907
- import { join } from "path";
1280
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync3, renameSync, statSync as statSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1281
+ import { join as join2 } from "path";
908
1282
  var SNAPSHOT_CAP = 2e5;
909
1283
  var KEEP_SNAPSHOTS = 20;
910
1284
  function snapshotsDir(config, root) {
911
- return join(expandPath(config.cache.dir, root), "diffs");
1285
+ return join2(expandPath(config.cache.dir, root), "diffs");
912
1286
  }
913
1287
  function renderDiffSnapshot(diffs) {
914
1288
  const out = [`snapshot generated ${(/* @__PURE__ */ new Date()).toISOString()}; added lines carry their new-file line number`];
@@ -946,10 +1320,10 @@ function renderDiffSnapshot(diffs) {
946
1320
  function writeDiffSnapshot(config, root, diffHash, diffs) {
947
1321
  if (diffs.length === 0) return null;
948
1322
  const dir = snapshotsDir(config, root);
949
- mkdirSync(dir, { recursive: true });
950
- const path = join(dir, `${diffHash}.txt`);
1323
+ mkdirSync2(dir, { recursive: true });
1324
+ const path = join2(dir, `${diffHash}.txt`);
951
1325
  const tmp = path + ".tmp";
952
- writeFileSync(tmp, renderDiffSnapshot(diffs) + "\n", "utf8");
1326
+ writeFileSync2(tmp, renderDiffSnapshot(diffs) + "\n", "utf8");
953
1327
  renameSync(tmp, path);
954
1328
  pruneSnapshots(dir);
955
1329
  return path;
@@ -964,21 +1338,21 @@ function pruneSnapshots(dir) {
964
1338
  if (entries.length <= KEEP_SNAPSHOTS) return;
965
1339
  const withTimes = entries.map((name) => {
966
1340
  try {
967
- return { name, mtime: statSync(join(dir, name)).mtimeMs };
1341
+ return { name, mtime: statSync2(join2(dir, name)).mtimeMs };
968
1342
  } catch {
969
1343
  return null;
970
1344
  }
971
1345
  }).filter((e) => e !== null).sort((a, b) => b.mtime - a.mtime);
972
1346
  for (const old of withTimes.slice(KEEP_SNAPSHOTS)) {
973
1347
  try {
974
- unlinkSync(join(dir, old.name));
1348
+ unlinkSync(join2(dir, old.name));
975
1349
  } catch {
976
1350
  }
977
1351
  }
978
1352
  }
979
1353
 
980
1354
  // src/gates/check/run.ts
981
- import { join as join2 } from "path";
1355
+ import { join as join3 } from "path";
982
1356
  var SEVERITY_RANK = { review: 1, block: 2, human: 3 };
983
1357
  var FILES_SUMMARY_CAP = 200;
984
1358
  function fileSummary(diffs) {
@@ -1042,7 +1416,7 @@ async function runCheck(config, root, corpus, jev, opts) {
1042
1416
  };
1043
1417
  }
1044
1418
  const stateDir = expandPath(config.cache.dir, root);
1045
- const tracker = new LoopTracker(join2(stateDir, "loop-state.json"));
1419
+ const tracker = new LoopTracker(join3(stateDir, "loop-state.json"));
1046
1420
  const diffText = diffs.flatMap((d) => d.files.map((f) => f.path + "\n" + f.hunks.flatMap((h) => h.lines.filter((l) => l.kind === "add").map((l) => l.text)).join("\n"))).join("\n");
1047
1421
  const fingerprint = diffFingerprint(diffText);
1048
1422
  const files = fileSummary(diffs);
@@ -1264,6 +1638,19 @@ function overlaps(a, b) {
1264
1638
  }
1265
1639
 
1266
1640
  export {
1641
+ PRECEDENCE,
1642
+ splitMarkdown,
1643
+ slug,
1644
+ parseSpec,
1645
+ parseRules,
1646
+ parseDivergences,
1647
+ parseQuestions,
1648
+ parsePlan,
1649
+ parseDoc,
1650
+ parsePaper,
1651
+ ensurePaperText,
1652
+ loadCorpus,
1653
+ loadCorpusFrom,
1267
1654
  runPlanGate,
1268
1655
  runCheck,
1269
1656
  formatReport,
@@ -1273,4 +1660,4 @@ export {
1273
1660
  paperSectionForSpec,
1274
1661
  authoritativeFor
1275
1662
  };
1276
- //# sourceMappingURL=chunk-2CIV4KGU.js.map
1663
+ //# sourceMappingURL=chunk-RZWCNEZS.js.map