truecourse 0.7.0-next.5 → 0.7.0-next.7

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/public/index.html CHANGED
@@ -20,7 +20,7 @@
20
20
  } catch (e) {}
21
21
  })();
22
22
  </script>
23
- <script type="module" crossorigin src="/assets/index-CI8n7IHP.js"></script>
23
+ <script type="module" crossorigin src="/assets/index-D7hFPhQQ.js"></script>
24
24
  <link rel="stylesheet" crossorigin href="/assets/index-CMCPeAwv.css">
25
25
  </head>
26
26
  <body class="font-sans">
Binary file
Binary file
package/server.mjs CHANGED
@@ -28409,6 +28409,153 @@ var init_guard = __esm({
28409
28409
  }
28410
28410
  });
28411
28411
 
28412
+ // packages/shared/dist/spec/overlap-resolution.js
28413
+ function coveringRelationInAreas(relations, a, b, areas) {
28414
+ return relations.find((r) => {
28415
+ const samePair = r.older === a && r.newer === b || r.older === b && r.newer === a;
28416
+ return samePair && (r.scope === void 0 || areas.includes(r.scope));
28417
+ });
28418
+ }
28419
+ function dedupeCrossAreaOverlaps(entries) {
28420
+ const byPair = /* @__PURE__ */ new Map();
28421
+ for (const e of entries) {
28422
+ const key4 = unorderedPairKey(e.overlap.docs[0], e.overlap.docs[1]);
28423
+ const list = byPair.get(key4);
28424
+ if (list)
28425
+ list.push(e);
28426
+ else
28427
+ byPair.set(key4, [e]);
28428
+ }
28429
+ const merged = [];
28430
+ for (const members of byPair.values()) {
28431
+ const parent = members.map((_, i) => i);
28432
+ const find = (i) => {
28433
+ while (parent[i] !== i) {
28434
+ parent[i] = parent[parent[i]];
28435
+ i = parent[i];
28436
+ }
28437
+ return i;
28438
+ };
28439
+ const union = (i, j) => {
28440
+ const ri = find(i);
28441
+ const rj = find(j);
28442
+ if (ri !== rj)
28443
+ parent[Math.max(ri, rj)] = Math.min(ri, rj);
28444
+ };
28445
+ const ptrOwner = /* @__PURE__ */ new Map();
28446
+ members.forEach((m, i) => {
28447
+ for (const ptr of sectionPointerKeys(m.overlap)) {
28448
+ const owner = ptrOwner.get(ptr);
28449
+ if (owner === void 0)
28450
+ ptrOwner.set(ptr, i);
28451
+ else
28452
+ union(owner, i);
28453
+ }
28454
+ });
28455
+ const components = /* @__PURE__ */ new Map();
28456
+ members.forEach((_, i) => {
28457
+ const root = find(i);
28458
+ const list = components.get(root);
28459
+ if (list)
28460
+ list.push(i);
28461
+ else
28462
+ components.set(root, [i]);
28463
+ });
28464
+ for (const idxs of components.values()) {
28465
+ const group = idxs.map((i) => members[i]);
28466
+ const rep = [...group].sort((x, y) => {
28467
+ const px = preambleCount(x.overlap);
28468
+ const py = preambleCount(y.overlap);
28469
+ if (px !== py)
28470
+ return px - py;
28471
+ if (x.area !== y.area)
28472
+ return x.area < y.area ? -1 : 1;
28473
+ return (x.overlap.note ?? "") < (y.overlap.note ?? "") ? -1 : 1;
28474
+ })[0];
28475
+ const span = /* @__PURE__ */ new Set();
28476
+ for (const g of group) {
28477
+ span.add(g.area);
28478
+ for (const a of g.overlap.areas ?? [])
28479
+ span.add(a);
28480
+ }
28481
+ merged.push({ area: rep.area, areas: [...span].sort(), overlap: rep.overlap });
28482
+ }
28483
+ }
28484
+ merged.sort((x, y) => {
28485
+ if (x.area !== y.area)
28486
+ return x.area < y.area ? -1 : 1;
28487
+ return unorderedPairKey(x.overlap.docs[0], x.overlap.docs[1]) < unorderedPairKey(y.overlap.docs[0], y.overlap.docs[1]) ? -1 : 1;
28488
+ });
28489
+ return merged;
28490
+ }
28491
+ function buildCorpusConflicts(corpus, decisions) {
28492
+ const userRelations = decisions.relations ?? [];
28493
+ const effectiveRels = [...corpus.relations, ...userRelations];
28494
+ const excludes = new Set(decisions.manualExcludes ?? []);
28495
+ const entries = [];
28496
+ for (const area of corpus.areas)
28497
+ for (const ov of area.overlaps)
28498
+ entries.push({ area: area.id, overlap: ov });
28499
+ const mergedOverlaps = dedupeCrossAreaOverlaps(entries);
28500
+ const flagged = [];
28501
+ for (const m of mergedOverlaps) {
28502
+ const [a, b] = m.overlap.docs;
28503
+ const relation = coveringRelationInAreas(effectiveRels, a, b, m.areas);
28504
+ const userRelation = coveringRelationInAreas(userRelations, a, b, m.areas);
28505
+ const excludedRef = excludes.has(a) ? a : excludes.has(b) ? b : void 0;
28506
+ flagged.push({
28507
+ area: m.area,
28508
+ areas: m.areas,
28509
+ a,
28510
+ b,
28511
+ note: m.overlap.note ?? "",
28512
+ resolved: relation !== void 0 || excludedRef !== void 0,
28513
+ synthesized: false,
28514
+ ...relation ? { relation } : {},
28515
+ ...userRelation ? { userRelation } : {},
28516
+ ...excludedRef ? { excludedRef } : {}
28517
+ });
28518
+ }
28519
+ const coversFlagged = (r) => flagged.some((o) => {
28520
+ const samePair = o.a === r.older && o.b === r.newer || o.a === r.newer && o.b === r.older;
28521
+ return samePair && (r.scope === void 0 || o.areas.includes(r.scope));
28522
+ });
28523
+ const synthesized = userRelations.filter((r) => !coversFlagged(r)).map((r) => ({
28524
+ area: r.scope ?? "",
28525
+ areas: r.scope ? [r.scope] : [],
28526
+ a: r.older,
28527
+ b: r.newer,
28528
+ note: r.note ?? "",
28529
+ resolved: true,
28530
+ synthesized: true,
28531
+ relation: r,
28532
+ userRelation: r
28533
+ }));
28534
+ return [...flagged, ...synthesized];
28535
+ }
28536
+ function openConflicts(corpus, decisions) {
28537
+ return buildCorpusConflicts(corpus, decisions).filter((c) => !c.resolved);
28538
+ }
28539
+ var PREAMBLE_PTR, NUL, unorderedPairKey, sectionPointerKeys, preambleCount;
28540
+ var init_overlap_resolution = __esm({
28541
+ "packages/shared/dist/spec/overlap-resolution.js"() {
28542
+ "use strict";
28543
+ PREAMBLE_PTR = "\0preamble";
28544
+ NUL = "\0";
28545
+ unorderedPairKey = (a, b) => a < b ? `${a}${NUL}${b}` : `${b}${NUL}${a}`;
28546
+ sectionPointerKeys = (ov) => (ov.sections ?? []).map((s) => `${s.doc}${NUL}${s.heading ?? PREAMBLE_PTR}`);
28547
+ preambleCount = (ov) => (ov.sections ?? []).filter((s) => s.heading === null || s.heading === void 0).length;
28548
+ }
28549
+ });
28550
+
28551
+ // packages/shared/dist/spec/index.js
28552
+ var init_spec = __esm({
28553
+ "packages/shared/dist/spec/index.js"() {
28554
+ "use strict";
28555
+ init_overlap_resolution();
28556
+ }
28557
+ });
28558
+
28412
28559
  // node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
28413
28560
  var require_ignore = __commonJS({
28414
28561
  "node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
@@ -28999,6 +29146,7 @@ var init_dist = __esm({
28999
29146
  init_types2();
29000
29147
  init_schemas();
29001
29148
  init_guard();
29149
+ init_spec();
29002
29150
  init_tcignore();
29003
29151
  init_skip_dirs();
29004
29152
  init_spec_scope();
@@ -197014,7 +197162,16 @@ var OverlapSchema = external_exports.object({
197014
197162
  /** Short note on what may disagree ("auth0_id vs auth0_sub"). */
197015
197163
  note: external_exports.string().default(""),
197016
197164
  /** The specific conflicting sections per doc (markdown headings), when known. */
197017
- sections: external_exports.array(OverlapSectionSchema).default([])
197165
+ sections: external_exports.array(OverlapSectionSchema).default([]),
197166
+ /**
197167
+ * The area ids this dispute spans. Detection runs per area, so one disagreement
197168
+ * on a doc pair sharing several areas is flagged in each; the cross-area merge
197169
+ * collapses those to this single record and lists every area it spanned here, so
197170
+ * a resolution scoped to any of them clears the dispute everywhere. Empty for
197171
+ * older corpora written before the merge — the read layer recomputes the span
197172
+ * from the per-area placement of the (then-duplicated) records.
197173
+ */
197174
+ areas: external_exports.array(external_exports.string()).default([])
197018
197175
  });
197019
197176
  var AreaSchema = external_exports.object({
197020
197177
  /** Canonical area id, `product/concern`. */
@@ -197789,6 +197946,7 @@ init_zod();
197789
197946
  import { createHash as createHash6 } from "node:crypto";
197790
197947
  import fs28 from "node:fs";
197791
197948
  init_transport();
197949
+ init_dist();
197792
197950
  var DEFAULT_MAX_PAIRS_PER_AREA = 60;
197793
197951
  async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197794
197952
  const result = /* @__PURE__ */ new Map();
@@ -197860,7 +198018,7 @@ async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197860
198018
  examineOne(repoRoot, pair.areaId, pair.a, pair.b, runner2).then((verdict) => {
197861
198019
  if (verdict.overlap) {
197862
198020
  const list = result.get(pair.areaId) ?? [];
197863
- list.push({ docs: [pair.a.path, pair.b.path], note: verdict.note, sections: verdict.sections ?? [] });
198021
+ list.push({ docs: [pair.a.path, pair.b.path], note: verdict.note, sections: verdict.sections ?? [], areas: [] });
197864
198022
  result.set(pair.areaId, list);
197865
198023
  }
197866
198024
  }).catch(() => {
@@ -197878,19 +198036,13 @@ async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197878
198036
  };
197879
198037
  launch();
197880
198038
  });
197881
- const seenKeys = /* @__PURE__ */ new Set();
197882
- for (const areaId of [...result.keys()].sort()) {
197883
- const kept = result.get(areaId).filter((ov) => {
197884
- const key4 = overlapDedupKey(ov);
197885
- if (seenKeys.has(key4))
197886
- return false;
197887
- seenKeys.add(key4);
197888
- return true;
197889
- });
197890
- if (kept.length === 0)
197891
- result.delete(areaId);
197892
- else
197893
- result.set(areaId, kept);
198039
+ const entries = [...result].flatMap(([area, list]) => list.map((overlap) => ({ area, overlap })));
198040
+ const merged = dedupeCrossAreaOverlaps(entries);
198041
+ result.clear();
198042
+ for (const m of merged) {
198043
+ const list = result.get(m.area) ?? [];
198044
+ list.push({ ...m.overlap, areas: m.areas });
198045
+ result.set(m.area, list);
197894
198046
  }
197895
198047
  for (const [areaId, list] of result) {
197896
198048
  list.sort((x, y) => x.docs.join() < y.docs.join() ? -1 : 1);
@@ -197898,11 +198050,6 @@ async function flagOverlaps(repoRoot, areas, docs, opts = {}) {
197898
198050
  }
197899
198051
  return result;
197900
198052
  }
197901
- function overlapDedupKey(ov) {
197902
- const docs = [...ov.docs].sort().join("::");
197903
- const secs = (ov.sections ?? []).map((s) => `${s.doc}|${s.heading}`).sort().join("::");
197904
- return `${docs}##${secs}`;
197905
- }
197906
198053
  async function examineOne(repoRoot, areaId, a, b, runner2) {
197907
198054
  const cacheKey = computeCacheKey4(areaId, a, b);
197908
198055
  const cached = await readCache3(repoRoot, cacheKey);
@@ -232294,7 +232441,7 @@ function readToolVersion() {
232294
232441
  if (cachedVersion)
232295
232442
  return cachedVersion;
232296
232443
  if (true) {
232297
- cachedVersion = "0.7.0-next.5";
232444
+ cachedVersion = "0.7.0-next.7";
232298
232445
  return cachedVersion;
232299
232446
  }
232300
232447
  try {
@@ -239986,7 +240133,41 @@ var guard_default = router13;
239986
240133
  var import_express14 = __toESM(require_express2(), 1);
239987
240134
 
239988
240135
  // packages/core/dist/commands/guard-in-process.js
240136
+ init_dist();
239989
240137
  init_transport();
240138
+ var OpenConflictsError = class extends Error {
240139
+ conflicts;
240140
+ constructor(conflicts2) {
240141
+ super(formatOpenConflictsMessage(conflicts2));
240142
+ this.conflicts = conflicts2;
240143
+ this.name = "OpenConflictsError";
240144
+ }
240145
+ };
240146
+ function formatOpenConflictsMessage(conflicts2) {
240147
+ const lines = [
240148
+ `${conflicts2.length} open spec conflict${conflicts2.length === 1 ? "" : "s"} must be resolved before guard generate.`,
240149
+ "Extracting both sides of an unresolved overlap births a red finding that is really the dispute.",
240150
+ ""
240151
+ ];
240152
+ for (const c of conflicts2) {
240153
+ lines.push(` ${c.area}`);
240154
+ lines.push(` ${c.a} \u2194 ${c.b}`);
240155
+ if (c.note)
240156
+ lines.push(` ${c.note}`);
240157
+ }
240158
+ lines.push("");
240159
+ lines.push("Resolve them with `truecourse spec conflicts list` (or the dashboard Conflicts group), then re-run `truecourse guard generate`.");
240160
+ return lines.join("\n");
240161
+ }
240162
+ async function assertNoOpenConflicts(repoRoot) {
240163
+ const corpus = await getCorpus(repoRoot);
240164
+ if (!corpus)
240165
+ return;
240166
+ const decisions = await getDecisions(repoRoot);
240167
+ const open = openConflicts(corpus, decisions);
240168
+ if (open.length > 0)
240169
+ throw new OpenConflictsError(open);
240170
+ }
239990
240171
  var GUARD_GENERATE_STEPS = [
239991
240172
  { key: "index", label: "Indexing sections" },
239992
240173
  { key: "extract", label: "Extracting claims" },
@@ -240022,6 +240203,7 @@ function resolveTransport2(options) {
240022
240203
  }
240023
240204
  async function guardGenerateInProcess(repoRoot, options = {}) {
240024
240205
  const { tracker } = options;
240206
+ await assertNoOpenConflicts(repoRoot);
240025
240207
  if (options.onLlmEstimate) {
240026
240208
  const prices = await getModelPrices();
240027
240209
  const estimate = await estimateGuardTokens(repoRoot, prices);
@@ -240270,6 +240452,10 @@ router14.post("/:id/guard/generate", async (req, res, next) => {
240270
240452
  res.json({ cancelled: true });
240271
240453
  return;
240272
240454
  }
240455
+ if (e instanceof OpenConflictsError) {
240456
+ res.status(422).json({ error: e.message });
240457
+ return;
240458
+ }
240273
240459
  emitSpecProgress(repoId, { step: "error", percent: 100, detail: e.message });
240274
240460
  next(e);
240275
240461
  } finally {