truecourse 0.7.0-next.5 → 0.7.0-next.6

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/cli.mjs CHANGED
@@ -105837,6 +105837,153 @@ var init_guard = __esm({
105837
105837
  }
105838
105838
  });
105839
105839
 
105840
+ // packages/shared/dist/spec/overlap-resolution.js
105841
+ function coveringRelationInAreas(relations, a, b, areas) {
105842
+ return relations.find((r) => {
105843
+ const samePair = r.older === a && r.newer === b || r.older === b && r.newer === a;
105844
+ return samePair && (r.scope === void 0 || areas.includes(r.scope));
105845
+ });
105846
+ }
105847
+ function dedupeCrossAreaOverlaps(entries) {
105848
+ const byPair = /* @__PURE__ */ new Map();
105849
+ for (const e of entries) {
105850
+ const key4 = unorderedPairKey(e.overlap.docs[0], e.overlap.docs[1]);
105851
+ const list = byPair.get(key4);
105852
+ if (list)
105853
+ list.push(e);
105854
+ else
105855
+ byPair.set(key4, [e]);
105856
+ }
105857
+ const merged = [];
105858
+ for (const members of byPair.values()) {
105859
+ const parent = members.map((_2, i) => i);
105860
+ const find = (i) => {
105861
+ while (parent[i] !== i) {
105862
+ parent[i] = parent[parent[i]];
105863
+ i = parent[i];
105864
+ }
105865
+ return i;
105866
+ };
105867
+ const union = (i, j2) => {
105868
+ const ri = find(i);
105869
+ const rj = find(j2);
105870
+ if (ri !== rj)
105871
+ parent[Math.max(ri, rj)] = Math.min(ri, rj);
105872
+ };
105873
+ const ptrOwner = /* @__PURE__ */ new Map();
105874
+ members.forEach((m, i) => {
105875
+ for (const ptr of sectionPointerKeys(m.overlap)) {
105876
+ const owner = ptrOwner.get(ptr);
105877
+ if (owner === void 0)
105878
+ ptrOwner.set(ptr, i);
105879
+ else
105880
+ union(owner, i);
105881
+ }
105882
+ });
105883
+ const components = /* @__PURE__ */ new Map();
105884
+ members.forEach((_2, i) => {
105885
+ const root2 = find(i);
105886
+ const list = components.get(root2);
105887
+ if (list)
105888
+ list.push(i);
105889
+ else
105890
+ components.set(root2, [i]);
105891
+ });
105892
+ for (const idxs of components.values()) {
105893
+ const group = idxs.map((i) => members[i]);
105894
+ const rep = [...group].sort((x, y) => {
105895
+ const px = preambleCount(x.overlap);
105896
+ const py = preambleCount(y.overlap);
105897
+ if (px !== py)
105898
+ return px - py;
105899
+ if (x.area !== y.area)
105900
+ return x.area < y.area ? -1 : 1;
105901
+ return (x.overlap.note ?? "") < (y.overlap.note ?? "") ? -1 : 1;
105902
+ })[0];
105903
+ const span = /* @__PURE__ */ new Set();
105904
+ for (const g of group) {
105905
+ span.add(g.area);
105906
+ for (const a of g.overlap.areas ?? [])
105907
+ span.add(a);
105908
+ }
105909
+ merged.push({ area: rep.area, areas: [...span].sort(), overlap: rep.overlap });
105910
+ }
105911
+ }
105912
+ merged.sort((x, y) => {
105913
+ if (x.area !== y.area)
105914
+ return x.area < y.area ? -1 : 1;
105915
+ return unorderedPairKey(x.overlap.docs[0], x.overlap.docs[1]) < unorderedPairKey(y.overlap.docs[0], y.overlap.docs[1]) ? -1 : 1;
105916
+ });
105917
+ return merged;
105918
+ }
105919
+ function buildCorpusConflicts(corpus, decisions) {
105920
+ const userRelations = decisions.relations ?? [];
105921
+ const effectiveRels = [...corpus.relations, ...userRelations];
105922
+ const excludes = new Set(decisions.manualExcludes ?? []);
105923
+ const entries = [];
105924
+ for (const area of corpus.areas)
105925
+ for (const ov of area.overlaps)
105926
+ entries.push({ area: area.id, overlap: ov });
105927
+ const mergedOverlaps = dedupeCrossAreaOverlaps(entries);
105928
+ const flagged = [];
105929
+ for (const m of mergedOverlaps) {
105930
+ const [a, b] = m.overlap.docs;
105931
+ const relation = coveringRelationInAreas(effectiveRels, a, b, m.areas);
105932
+ const userRelation = coveringRelationInAreas(userRelations, a, b, m.areas);
105933
+ const excludedRef = excludes.has(a) ? a : excludes.has(b) ? b : void 0;
105934
+ flagged.push({
105935
+ area: m.area,
105936
+ areas: m.areas,
105937
+ a,
105938
+ b,
105939
+ note: m.overlap.note ?? "",
105940
+ resolved: relation !== void 0 || excludedRef !== void 0,
105941
+ synthesized: false,
105942
+ ...relation ? { relation } : {},
105943
+ ...userRelation ? { userRelation } : {},
105944
+ ...excludedRef ? { excludedRef } : {}
105945
+ });
105946
+ }
105947
+ const coversFlagged = (r) => flagged.some((o) => {
105948
+ const samePair = o.a === r.older && o.b === r.newer || o.a === r.newer && o.b === r.older;
105949
+ return samePair && (r.scope === void 0 || o.areas.includes(r.scope));
105950
+ });
105951
+ const synthesized = userRelations.filter((r) => !coversFlagged(r)).map((r) => ({
105952
+ area: r.scope ?? "",
105953
+ areas: r.scope ? [r.scope] : [],
105954
+ a: r.older,
105955
+ b: r.newer,
105956
+ note: r.note ?? "",
105957
+ resolved: true,
105958
+ synthesized: true,
105959
+ relation: r,
105960
+ userRelation: r
105961
+ }));
105962
+ return [...flagged, ...synthesized];
105963
+ }
105964
+ function openConflicts(corpus, decisions) {
105965
+ return buildCorpusConflicts(corpus, decisions).filter((c2) => !c2.resolved);
105966
+ }
105967
+ var PREAMBLE_PTR, NUL, unorderedPairKey, sectionPointerKeys, preambleCount;
105968
+ var init_overlap_resolution = __esm({
105969
+ "packages/shared/dist/spec/overlap-resolution.js"() {
105970
+ "use strict";
105971
+ PREAMBLE_PTR = "\0preamble";
105972
+ NUL = "\0";
105973
+ unorderedPairKey = (a, b) => a < b ? `${a}${NUL}${b}` : `${b}${NUL}${a}`;
105974
+ sectionPointerKeys = (ov) => (ov.sections ?? []).map((s) => `${s.doc}${NUL}${s.heading ?? PREAMBLE_PTR}`);
105975
+ preambleCount = (ov) => (ov.sections ?? []).filter((s) => s.heading === null || s.heading === void 0).length;
105976
+ }
105977
+ });
105978
+
105979
+ // packages/shared/dist/spec/index.js
105980
+ var init_spec = __esm({
105981
+ "packages/shared/dist/spec/index.js"() {
105982
+ "use strict";
105983
+ init_overlap_resolution();
105984
+ }
105985
+ });
105986
+
105840
105987
  // packages/shared/dist/fs/tcignore.js
105841
105988
  import fs11 from "node:fs";
105842
105989
  import path13 from "node:path";
@@ -105960,6 +106107,7 @@ var init_dist5 = __esm({
105960
106107
  init_types3();
105961
106108
  init_schemas();
105962
106109
  init_guard();
106110
+ init_spec();
105963
106111
  init_tcignore();
105964
106112
  init_skip_dirs();
105965
106113
  init_spec_scope();
@@ -171028,7 +171176,7 @@ function readToolVersion() {
171028
171176
  if (cachedVersion)
171029
171177
  return cachedVersion;
171030
171178
  if (true) {
171031
- cachedVersion = "0.7.0-next.5";
171179
+ cachedVersion = "0.7.0-next.6";
171032
171180
  return cachedVersion;
171033
171181
  }
171034
171182
  try {
@@ -197070,7 +197218,16 @@ var OverlapSchema = external_exports.object({
197070
197218
  /** Short note on what may disagree ("auth0_id vs auth0_sub"). */
197071
197219
  note: external_exports.string().default(""),
197072
197220
  /** The specific conflicting sections per doc (markdown headings), when known. */
197073
- sections: external_exports.array(OverlapSectionSchema).default([])
197221
+ sections: external_exports.array(OverlapSectionSchema).default([]),
197222
+ /**
197223
+ * The area ids this dispute spans. Detection runs per area, so one disagreement
197224
+ * on a doc pair sharing several areas is flagged in each; the cross-area merge
197225
+ * collapses those to this single record and lists every area it spanned here, so
197226
+ * a resolution scoped to any of them clears the dispute everywhere. Empty for
197227
+ * older corpora written before the merge — the read layer recomputes the span
197228
+ * from the per-area placement of the (then-duplicated) records.
197229
+ */
197230
+ areas: external_exports.array(external_exports.string()).default([])
197074
197231
  });
197075
197232
  var AreaSchema = external_exports.object({
197076
197233
  /** Canonical area id, `product/concern`. */
@@ -197845,6 +198002,7 @@ init_zod();
197845
198002
  import { createHash as createHash7 } from "node:crypto";
197846
198003
  import fs26 from "node:fs";
197847
198004
  init_transport();
198005
+ init_dist5();
197848
198006
  var DEFAULT_MAX_PAIRS_PER_AREA = 60;
197849
198007
  async function flagOverlaps(repoRoot5, areas, docs, opts = {}) {
197850
198008
  const result = /* @__PURE__ */ new Map();
@@ -197916,7 +198074,7 @@ async function flagOverlaps(repoRoot5, areas, docs, opts = {}) {
197916
198074
  examineOne(repoRoot5, pair.areaId, pair.a, pair.b, runner).then((verdict) => {
197917
198075
  if (verdict.overlap) {
197918
198076
  const list = result.get(pair.areaId) ?? [];
197919
- list.push({ docs: [pair.a.path, pair.b.path], note: verdict.note, sections: verdict.sections ?? [] });
198077
+ list.push({ docs: [pair.a.path, pair.b.path], note: verdict.note, sections: verdict.sections ?? [], areas: [] });
197920
198078
  result.set(pair.areaId, list);
197921
198079
  }
197922
198080
  }).catch(() => {
@@ -197934,19 +198092,13 @@ async function flagOverlaps(repoRoot5, areas, docs, opts = {}) {
197934
198092
  };
197935
198093
  launch();
197936
198094
  });
197937
- const seenKeys = /* @__PURE__ */ new Set();
197938
- for (const areaId of [...result.keys()].sort()) {
197939
- const kept = result.get(areaId).filter((ov) => {
197940
- const key4 = overlapDedupKey(ov);
197941
- if (seenKeys.has(key4))
197942
- return false;
197943
- seenKeys.add(key4);
197944
- return true;
197945
- });
197946
- if (kept.length === 0)
197947
- result.delete(areaId);
197948
- else
197949
- result.set(areaId, kept);
198095
+ const entries = [...result].flatMap(([area, list]) => list.map((overlap) => ({ area, overlap })));
198096
+ const merged = dedupeCrossAreaOverlaps(entries);
198097
+ result.clear();
198098
+ for (const m of merged) {
198099
+ const list = result.get(m.area) ?? [];
198100
+ list.push({ ...m.overlap, areas: m.areas });
198101
+ result.set(m.area, list);
197950
198102
  }
197951
198103
  for (const [areaId, list] of result) {
197952
198104
  list.sort((x, y) => x.docs.join() < y.docs.join() ? -1 : 1);
@@ -197954,11 +198106,6 @@ async function flagOverlaps(repoRoot5, areas, docs, opts = {}) {
197954
198106
  }
197955
198107
  return result;
197956
198108
  }
197957
- function overlapDedupKey(ov) {
197958
- const docs = [...ov.docs].sort().join("::");
197959
- const secs = (ov.sections ?? []).map((s) => `${s.doc}|${s.heading}`).sort().join("::");
197960
- return `${docs}##${secs}`;
197961
- }
197962
198109
  async function examineOne(repoRoot5, areaId, a, b, runner) {
197963
198110
  const cacheKey = computeCacheKey4(areaId, a, b);
197964
198111
  const cached = await readCache3(repoRoot5, cacheKey);
@@ -210356,6 +210503,41 @@ async function storeDecisions(repoKey, next, opts) {
210356
210503
  }
210357
210504
  await saveSpec({ repoKey, commitSha: decisionsRef(opts?.pr) }, "decisions", next);
210358
210505
  }
210506
+ async function getDecisions(repoKey, opts) {
210507
+ if (opts?.pr === void 0)
210508
+ return loadDecisions(repoKey);
210509
+ const [base2, overlay] = await Promise.all([
210510
+ loadDecisions(repoKey),
210511
+ loadDecisions(repoKey, { pr: opts.pr })
210512
+ ]);
210513
+ return mergeDecisions(base2, overlay);
210514
+ }
210515
+ function mergeDecisions(base2, overlay) {
210516
+ const overlayRelKeys = new Set((overlay.relations ?? []).map(relationKey));
210517
+ const relations = [
210518
+ ...(base2.relations ?? []).filter((r) => !overlayRelKeys.has(relationKey(r))),
210519
+ ...overlay.relations ?? []
210520
+ ];
210521
+ const overlayIncludes = new Set(overlay.manualIncludes ?? []);
210522
+ const overlayExcludes = new Set(overlay.manualExcludes ?? []);
210523
+ const manualIncludes = uniqueStrings([
210524
+ ...base2.manualIncludes ?? [],
210525
+ ...overlay.manualIncludes ?? []
210526
+ ]).filter((p2) => !overlayExcludes.has(p2));
210527
+ const manualExcludes = uniqueStrings([
210528
+ ...base2.manualExcludes ?? [],
210529
+ ...overlay.manualExcludes ?? []
210530
+ ]).filter((p2) => !overlayIncludes.has(p2));
210531
+ const overlayAreaDocs = new Set((overlay.manualAreas ?? []).map((a) => a.doc));
210532
+ const manualAreas = [
210533
+ ...(base2.manualAreas ?? []).filter((a) => !overlayAreaDocs.has(a.doc)),
210534
+ ...overlay.manualAreas ?? []
210535
+ ];
210536
+ return { version: 1, manualIncludes, manualExcludes, relations, manualAreas };
210537
+ }
210538
+ function uniqueStrings(items) {
210539
+ return [...new Set(items)];
210540
+ }
210359
210541
  function getCorpus(repoKey) {
210360
210542
  return loadLatestSpec(repoKey, "corpus");
210361
210543
  }
@@ -210738,6 +210920,7 @@ async function runContractsValidate(options = {}) {
210738
210920
  // tools/cli/src/commands/spec.ts
210739
210921
  init_dist4();
210740
210922
  import path74 from "node:path";
210923
+ init_dist5();
210741
210924
  init_progress();
210742
210925
  init_registry();
210743
210926
  var repoRoot = (opts = {}) => opts.cwd ?? process.cwd();
@@ -210797,7 +210980,10 @@ async function runSpecScan(opts = {}) {
210797
210980
  O2.message(` \u2026 (+${s.openOverlaps.length - 10} more)`);
210798
210981
  }
210799
210982
  }
210800
- gt("Corpus written to .truecourse/specs/corpus.json. Run `truecourse contracts generate`.");
210983
+ const openCount = s.openOverlaps.length;
210984
+ gt(
210985
+ openCount === 0 ? "Corpus written to .truecourse/specs/corpus.json. Run `truecourse guard generate`." : `Corpus written to .truecourse/specs/corpus.json. ${openCount} conflict${openCount === 1 ? "" : "s"} to resolve (\`truecourse spec conflicts list\`), then \`truecourse guard generate\`.`
210986
+ );
210801
210987
  }
210802
210988
  async function runSpecStatus(opts = {}) {
210803
210989
  const root2 = repoRoot(opts);
@@ -210810,19 +210996,9 @@ async function runSpecStatus(opts = {}) {
210810
210996
  }
210811
210997
  const decisions = readCorpusDecisions(root2);
210812
210998
  const userRels = decisions.relations ?? [];
210813
- const allRels = [...corpus.relations, ...userRels];
210814
- const covered = (a, b, area) => allRels.some((r) => {
210815
- const samePair = r.older === a && r.newer === b || r.older === b && r.newer === a;
210816
- return samePair && (r.scope === void 0 || r.scope === area);
210817
- });
210818
- let open = 0;
210819
- let resolved = 0;
210820
- for (const area of corpus.areas) {
210821
- for (const ov of area.overlaps) {
210822
- if (covered(ov.docs[0], ov.docs[1], area.id)) resolved++;
210823
- else open++;
210824
- }
210825
- }
210999
+ const conflicts2 = buildCorpusConflicts(corpus, decisions);
211000
+ const open = conflicts2.filter((c2) => !c2.resolved).length;
211001
+ const resolved = conflicts2.length - open;
210826
211002
  const rows = [
210827
211003
  ["Docs (kept)", String(corpus.docs.length)],
210828
211004
  ["Areas", String(corpus.areas.length)],
@@ -210837,7 +211013,7 @@ async function runSpecStatus(opts = {}) {
210837
211013
  O2.message(` ${area.id.padEnd(30)} ${area.docRefs.length} doc${area.docRefs.length === 1 ? "" : "s"}${ov}`);
210838
211014
  }
210839
211015
  gt(
210840
- open === 0 ? "No open overlaps \u2014 run `truecourse contracts generate`." : "Open overlaps \u2014 see `truecourse spec conflicts list`."
211016
+ open === 0 ? "No open overlaps \u2014 run `truecourse guard generate`." : "Open overlaps \u2014 see `truecourse spec conflicts list`."
210841
211017
  );
210842
211018
  }
210843
211019
  async function runVerify(opts = {}) {
@@ -210954,18 +211130,9 @@ async function runInfer(opts = {}) {
210954
211130
  init_dist4();
210955
211131
  import fs69 from "node:fs";
210956
211132
  import path75 from "node:path";
211133
+ init_dist5();
210957
211134
  var root = (opts) => opts.cwd ?? process.cwd();
210958
211135
  var base = (ref) => ref.split("/").pop() ?? ref;
210959
- function effectiveRelations2(corpus, repoRoot5) {
210960
- const user = readCorpusDecisions(repoRoot5).relations ?? [];
210961
- return [...corpus.relations, ...user];
210962
- }
210963
- function coveringRelation(rels, a, b, area) {
210964
- return rels.find((r) => {
210965
- const samePair = r.older === a && r.newer === b || r.older === b && r.newer === a;
210966
- return samePair && (r.scope === void 0 || r.scope === area);
210967
- });
210968
- }
210969
211136
  function loadCorpusOrExit(repoRoot5) {
210970
211137
  const corpus = readCorpus(repoRoot5);
210971
211138
  if (!corpus) {
@@ -210977,25 +211144,17 @@ function loadCorpusOrExit(repoRoot5) {
210977
211144
  async function runSpecConflictsList(opts = {}) {
210978
211145
  const repoRoot5 = root(opts);
210979
211146
  const corpus = loadCorpusOrExit(repoRoot5);
210980
- const rels = effectiveRelations2(corpus, repoRoot5);
210981
- let open = 0;
210982
- let resolved = 0;
211147
+ const conflicts2 = buildCorpusConflicts(corpus, readCorpusDecisions(repoRoot5));
211148
+ const open = conflicts2.filter((c2) => !c2.resolved);
211149
+ const resolved = conflicts2.length - open.length;
210983
211150
  mt("Overlaps (areas where docs may disagree)");
210984
- for (const area of corpus.areas) {
210985
- for (const ov of area.overlaps) {
210986
- const [a, b] = ov.docs;
210987
- if (coveringRelation(rels, a, b, area.id)) {
210988
- resolved++;
210989
- continue;
210990
- }
210991
- open++;
210992
- O2.warn(`${area.id}`);
210993
- O2.message(` ${base(a)} \u2194 ${base(b)}${ov.note ? ` \xB7 ${ov.note}` : ""}`);
210994
- O2.message(` resolve: truecourse spec conflicts resolve ${area.id} --older ${a} --newer ${b} --precedence`);
210995
- }
211151
+ for (const c2 of open) {
211152
+ O2.warn(`${c2.area}`);
211153
+ O2.message(` ${base(c2.a)} \u2194 ${base(c2.b)}${c2.note ? ` \xB7 ${c2.note}` : ""}`);
211154
+ O2.message(` resolve: truecourse spec conflicts resolve ${c2.area} --older ${c2.a} --newer ${c2.b} --precedence`);
210996
211155
  }
210997
- if (open === 0) O2.step("No open overlaps.");
210998
- gt(`${open} open \xB7 ${resolved} resolved. Inspect with \`spec conflicts show <area>\`.`);
211156
+ if (open.length === 0) O2.step("No open overlaps.");
211157
+ gt(`${open.length} open \xB7 ${resolved} resolved. Inspect with \`spec conflicts show <area>\`.`);
210999
211158
  }
211000
211159
  function excerpt(repoRoot5, ref, note, max = 20) {
211001
211160
  let text4;
@@ -211018,14 +211177,18 @@ async function runSpecConflictsShow(area, opts = {}) {
211018
211177
  pt(`No such area: ${area}. List areas with \`spec status\`.`);
211019
211178
  process.exit(1);
211020
211179
  }
211021
- const rels = effectiveRelations2(corpus, repoRoot5);
211180
+ const conflicts2 = buildCorpusConflicts(corpus, readCorpusDecisions(repoRoot5));
211022
211181
  mt(`Overlaps in ${area}`);
211023
211182
  if (a.overlaps.length === 0) O2.step("(no overlaps in this area)");
211024
211183
  for (const ov of a.overlaps) {
211025
211184
  const [da, db] = ov.docs;
211026
- const rel = coveringRelation(rels, da, db, area);
211185
+ const c2 = conflicts2.find(
211186
+ (x) => x.area === area && (x.a === da && x.b === db || x.a === db && x.b === da)
211187
+ );
211027
211188
  O2.warn(`${base(da)} \u2194 ${base(db)}${ov.note ? ` \xB7 ${ov.note}` : ""}`);
211028
- O2.message(rel ? ` resolved \u2192 ${rel.type} (${rel.older} \u21D2 ${rel.newer})` : " open");
211189
+ if (c2?.relation) O2.message(` resolved \u2192 ${c2.relation.type} (${c2.relation.older} \u21D2 ${c2.relation.newer})`);
211190
+ else if (c2?.excludedRef) O2.message(` resolved \u2192 ${c2.excludedRef} excluded`);
211191
+ else O2.message(" open");
211029
211192
  O2.message(` ${da}:`);
211030
211193
  O2.message(excerpt(repoRoot5, da, ov.note));
211031
211194
  O2.message(` ${db}:`);
@@ -211044,11 +211207,15 @@ async function runSpecConflictsResolve(area, opts) {
211044
211207
  for (const ref of [opts.older, opts.newer]) {
211045
211208
  if (!known.has(ref)) return fail(`${ref} is not a doc in area ${area}. Docs: ${a.docRefs.join(", ")}`);
211046
211209
  }
211210
+ const conflict = buildCorpusConflicts(corpus, readCorpusDecisions(repoRoot5)).find(
211211
+ (c2) => c2.a === opts.older && c2.b === opts.newer || c2.a === opts.newer && c2.b === opts.older
211212
+ );
211213
+ const scope = conflict && conflict.areas.length > 1 ? void 0 : area;
211047
211214
  await addRelation(repoRoot5, {
211048
211215
  type: opts.type,
211049
211216
  older: opts.older,
211050
211217
  newer: opts.newer,
211051
- scope: area,
211218
+ scope,
211052
211219
  detectedFrom: "manual",
211053
211220
  note: opts.note
211054
211221
  });
@@ -211056,7 +211223,7 @@ async function runSpecConflictsResolve(area, opts) {
211056
211223
  s.start("Re-scanning to apply the relation");
211057
211224
  await curateInProcess(repoRoot5, {});
211058
211225
  s.stop("Re-scanned");
211059
- gt(`Recorded ${opts.type}: ${opts.older} \u21D2 ${opts.newer} (scope ${area}).`);
211226
+ gt(`Recorded ${opts.type}: ${opts.older} \u21D2 ${opts.newer} (scope ${scope ?? "all areas"}).`);
211060
211227
  }
211061
211228
  function fail(msg) {
211062
211229
  pt(msg);
@@ -211297,8 +211464,42 @@ import path77 from "node:path";
211297
211464
  init_progress();
211298
211465
 
211299
211466
  // packages/core/dist/commands/guard-in-process.js
211467
+ init_dist5();
211300
211468
  init_git();
211301
211469
  init_transport();
211470
+ var OpenConflictsError = class extends Error {
211471
+ conflicts;
211472
+ constructor(conflicts2) {
211473
+ super(formatOpenConflictsMessage(conflicts2));
211474
+ this.conflicts = conflicts2;
211475
+ this.name = "OpenConflictsError";
211476
+ }
211477
+ };
211478
+ function formatOpenConflictsMessage(conflicts2) {
211479
+ const lines = [
211480
+ `${conflicts2.length} open spec conflict${conflicts2.length === 1 ? "" : "s"} must be resolved before guard generate.`,
211481
+ "Extracting both sides of an unresolved overlap births a red finding that is really the dispute.",
211482
+ ""
211483
+ ];
211484
+ for (const c2 of conflicts2) {
211485
+ lines.push(` ${c2.area}`);
211486
+ lines.push(` ${c2.a} \u2194 ${c2.b}`);
211487
+ if (c2.note)
211488
+ lines.push(` ${c2.note}`);
211489
+ }
211490
+ lines.push("");
211491
+ lines.push("Resolve them with `truecourse spec conflicts list` (or the dashboard Conflicts group), then re-run `truecourse guard generate`.");
211492
+ return lines.join("\n");
211493
+ }
211494
+ async function assertNoOpenConflicts(repoRoot5) {
211495
+ const corpus = await getCorpus(repoRoot5);
211496
+ if (!corpus)
211497
+ return;
211498
+ const decisions = await getDecisions(repoRoot5);
211499
+ const open = openConflicts(corpus, decisions);
211500
+ if (open.length > 0)
211501
+ throw new OpenConflictsError(open);
211502
+ }
211302
211503
  var GUARD_GENERATE_STEPS = [
211303
211504
  { key: "index", label: "Indexing sections" },
211304
211505
  { key: "extract", label: "Extracting claims" },
@@ -211331,6 +211532,7 @@ function resolveTransport2(options) {
211331
211532
  }
211332
211533
  async function guardGenerateInProcess(repoRoot5, options = {}) {
211333
211534
  const { tracker } = options;
211535
+ await assertNoOpenConflicts(repoRoot5);
211334
211536
  if (options.onLlmEstimate) {
211335
211537
  const prices = await getModelPrices();
211336
211538
  const estimate = await estimateGuardTokens(repoRoot5, prices);
@@ -211662,6 +211864,16 @@ async function runGuardGenerate(opts = {}) {
211662
211864
  pt("Generate cancelled.");
211663
211865
  process.exit(0);
211664
211866
  }
211867
+ if (e instanceof OpenConflictsError) {
211868
+ O2.error(`${e.conflicts.length} open spec conflict${e.conflicts.length === 1 ? "" : "s"} block guard generate \u2014 resolve them first:`);
211869
+ for (const c2 of e.conflicts) {
211870
+ O2.message(` ${c2.area}`);
211871
+ O2.message(` ${c2.a} \u2194 ${c2.b}${c2.note ? ` \xB7 ${c2.note}` : ""}`);
211872
+ }
211873
+ O2.step("Resolve with `truecourse spec conflicts list` (or the dashboard Conflicts group), then re-run `truecourse guard generate`.");
211874
+ gt("Aborted \u2014 resolve the conflicts first.");
211875
+ process.exit(1);
211876
+ }
211665
211877
  O2.error(`Guard generate failed: ${e.message}`);
211666
211878
  gt("Aborted.");
211667
211879
  process.exit(1);
@@ -212226,7 +212438,7 @@ async function runHooksRun() {
212226
212438
 
212227
212439
  // tools/cli/src/index.ts
212228
212440
  var program2 = new Command();
212229
- program2.name("truecourse").version("0.7.0-next.5").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
212441
+ program2.name("truecourse").version("0.7.0-next.6").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
212230
212442
  function warnDiscontinued(command) {
212231
212443
  process.stderr.write(
212232
212444
  `\u26A0 \`${command}\` is discontinued in favor of \`truecourse guard\` \u2014 see the README. Planned removal: 0.8.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "truecourse",
3
- "version": "0.7.0-next.5",
3
+ "version": "0.7.0-next.6",
4
4
  "description": "Visualize your codebase architecture as an interactive graph",
5
5
  "type": "module",
6
6
  "bin": {