truecourse 0.7.0-next.3 → 0.7.0-next.4

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
@@ -973,7 +973,7 @@ var require_command = __commonJS({
973
973
  var EventEmitter2 = __require("node:events").EventEmitter;
974
974
  var childProcess = __require("node:child_process");
975
975
  var path80 = __require("node:path");
976
- var fs69 = __require("node:fs");
976
+ var fs70 = __require("node:fs");
977
977
  var process2 = __require("node:process");
978
978
  var { Argument: Argument2, humanReadableArgName } = require_argument();
979
979
  var { CommanderError: CommanderError2 } = require_error();
@@ -1906,10 +1906,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
1906
1906
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1907
1907
  function findFile(baseDir, baseName) {
1908
1908
  const localBin = path80.resolve(baseDir, baseName);
1909
- if (fs69.existsSync(localBin)) return localBin;
1909
+ if (fs70.existsSync(localBin)) return localBin;
1910
1910
  if (sourceExt.includes(path80.extname(baseName))) return void 0;
1911
1911
  const foundExt = sourceExt.find(
1912
- (ext2) => fs69.existsSync(`${localBin}${ext2}`)
1912
+ (ext2) => fs70.existsSync(`${localBin}${ext2}`)
1913
1913
  );
1914
1914
  if (foundExt) return `${localBin}${foundExt}`;
1915
1915
  return void 0;
@@ -1921,7 +1921,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
1921
1921
  if (this._scriptPath) {
1922
1922
  let resolvedScriptPath;
1923
1923
  try {
1924
- resolvedScriptPath = fs69.realpathSync(this._scriptPath);
1924
+ resolvedScriptPath = fs70.realpathSync(this._scriptPath);
1925
1925
  } catch (err) {
1926
1926
  resolvedScriptPath = this._scriptPath;
1927
1927
  }
@@ -105218,7 +105218,7 @@ function parseBlockedOnCapabilities(reason) {
105218
105218
  return [];
105219
105219
  return m[1].split(",").map((s) => s.trim()).filter(Boolean);
105220
105220
  }
105221
- var GuardWrittenScenarioSchema, GuardCoverageGapKindSchema, GuardCoverageGapSchema, GuardBirthFindingSchema, GuardGenerateErrorSchema, GuardEntryPreflightSchema, GuardExtractionFailureSchema, GuardOrphanedSectionSchema, GuardRecipeReportSchema, GuardGenerateUsageSchema, GuardGenerateReportSchema;
105221
+ var GuardWrittenScenarioSchema, GuardCoverageGapKindSchema, GuardCoverageGapSchema, GuardBirthFindingSchema, GuardGenerateErrorSchema, GuardReadyScenarioSchema, GuardHeldSectionSchema, GuardEntryPreflightSchema, GuardExtractionFailureSchema, GuardOrphanedDismissalSchema, GuardOrphanedSectionSchema, GuardRecipeReportSchema, GuardGenerateUsageSchema, GuardGenerateReportSchema;
105222
105222
  var init_report = __esm({
105223
105223
  "packages/shared/dist/guard/report.js"() {
105224
105224
  "use strict";
@@ -105236,7 +105236,8 @@ var init_report = __esm({
105236
105236
  "awaiting-driver",
105237
105237
  "untestable",
105238
105238
  "no-claim",
105239
- "blocked-on"
105239
+ "blocked-on",
105240
+ "dismissed"
105240
105241
  ]);
105241
105242
  GuardCoverageGapSchema = external_exports.object({
105242
105243
  doc: external_exports.string(),
@@ -105258,6 +105259,22 @@ var init_report = __esm({
105258
105259
  actual: external_exports.string(),
105259
105260
  /** Repo-relative pointer into `guard/evidence/`, when a transcript was written. */
105260
105261
  evidencePath: external_exports.string().optional(),
105262
+ /**
105263
+ * The failed candidate's authored YAML, serialized inline AT FINDING CREATION
105264
+ * (same serialize-at-creation as heldSections' readyScenarios). The finding
105265
+ * detail renders it in the scenario-source code block so the user can judge
105266
+ * "defect or drift" with the exact commands the scenario ran on-screen.
105267
+ * Optional so older `result.json` files keep parsing.
105268
+ */
105269
+ yaml: external_exports.string().optional(),
105270
+ /**
105271
+ * The EXTRACTED CLAIM's stable text — the claim identity a dismissal keys on
105272
+ * (anchor + this). The finding detail's Dismiss action writes it into
105273
+ * `scenarios/decisions.json`; generate then skips a matching claim before
105274
+ * authoring. Distinct from `title` (the scenario title). Optional so older
105275
+ * reports (and the internal retry-evidence findings) parse.
105276
+ */
105277
+ claim: external_exports.string().optional(),
105261
105278
  /**
105262
105279
  * The bound section's human heading, joined SERVER-SIDE at report read time
105263
105280
  * (never written to `result.json` — the enrichment is read-side). A finding's
@@ -105271,6 +105288,18 @@ var init_report = __esm({
105271
105288
  anchor: external_exports.string(),
105272
105289
  message: external_exports.string()
105273
105290
  }).strict();
105291
+ GuardReadyScenarioSchema = external_exports.object({
105292
+ id: external_exports.string(),
105293
+ title: external_exports.string(),
105294
+ /** The committed YAML the scenario would have been written as. */
105295
+ yaml: external_exports.string()
105296
+ }).strict();
105297
+ GuardHeldSectionSchema = external_exports.object({
105298
+ doc: external_exports.string(),
105299
+ anchor: external_exports.string(),
105300
+ headingText: external_exports.string().optional(),
105301
+ readyScenarios: external_exports.array(GuardReadyScenarioSchema)
105302
+ }).strict();
105274
105303
  GuardEntryPreflightSchema = external_exports.object({
105275
105304
  /** Display form of the entry argv, e.g. `node tools/cli/dist/index.js`. */
105276
105305
  entry: external_exports.string(),
@@ -105283,6 +105312,11 @@ var init_report = __esm({
105283
105312
  doc: external_exports.string(),
105284
105313
  reason: external_exports.string()
105285
105314
  }).strict();
105315
+ GuardOrphanedDismissalSchema = external_exports.object({
105316
+ doc: external_exports.string(),
105317
+ anchor: external_exports.string(),
105318
+ title: external_exports.string()
105319
+ }).strict();
105286
105320
  GuardOrphanedSectionSchema = external_exports.object({
105287
105321
  doc: external_exports.string(),
105288
105322
  anchor: external_exports.string(),
@@ -105323,6 +105357,19 @@ var init_report = __esm({
105323
105357
  * `written.length` when a passing scenario's section didn't settle.
105324
105358
  */
105325
105359
  birthPassed: external_exports.number().int().nonnegative().optional(),
105360
+ /**
105361
+ * Unsettled sections whose birth-passed candidates were withheld — the
105362
+ * ready-but-held scenarios, each carrying its authored YAML inline. Optional so
105363
+ * older reports (written before this field existed) keep parsing; absent reads
105364
+ * as "no held work".
105365
+ */
105366
+ heldSections: external_exports.array(GuardHeldSectionSchema).optional(),
105367
+ /**
105368
+ * Dismissals whose claim text matched nothing in a doc this run re-extracted —
105369
+ * stale entries in `scenarios/decisions.json`, surfaced (never silently
105370
+ * honored). Optional so older reports parse; absent reads as "none".
105371
+ */
105372
+ orphanedDismissals: external_exports.array(GuardOrphanedDismissalSchema).optional(),
105326
105373
  manifestPath: external_exports.string().optional(),
105327
105374
  usage: GuardGenerateUsageSchema.optional(),
105328
105375
  /**
@@ -105373,6 +105420,35 @@ var init_manifest = __esm({
105373
105420
  }
105374
105421
  });
105375
105422
 
105423
+ // packages/shared/dist/guard/decisions.js
105424
+ function dismissedClaimKey(doc, anchor, title) {
105425
+ return `${doc}\0${anchor}\0${title}`;
105426
+ }
105427
+ var GuardDismissedClaimSchema, GuardDecisionsSchema, EMPTY_GUARD_DECISIONS;
105428
+ var init_decisions = __esm({
105429
+ "packages/shared/dist/guard/decisions.js"() {
105430
+ "use strict";
105431
+ init_zod();
105432
+ GuardDismissedClaimSchema = external_exports.object({
105433
+ /** Repo-relative doc path the claim's section lives in. */
105434
+ doc: external_exports.string().min(1),
105435
+ /** The section anchor (slug) the claim was extracted under. */
105436
+ anchor: external_exports.string().min(1),
105437
+ /** The extracted claim's stable text — the identity, with `anchor`. */
105438
+ title: external_exports.string().min(1),
105439
+ /** ISO timestamp the dismissal was recorded. */
105440
+ dismissedAt: external_exports.string(),
105441
+ /** Optional free-text rationale ("flaky", "won't fix", …). */
105442
+ note: external_exports.string().optional()
105443
+ });
105444
+ GuardDecisionsSchema = external_exports.object({
105445
+ version: external_exports.literal(1),
105446
+ dismissedClaims: external_exports.array(GuardDismissedClaimSchema).default([])
105447
+ });
105448
+ EMPTY_GUARD_DECISIONS = { version: 1, dismissedClaims: [] };
105449
+ }
105450
+ });
105451
+
105376
105452
  // packages/shared/dist/guard/dashboard.js
105377
105453
  var init_dashboard = __esm({
105378
105454
  "packages/shared/dist/guard/dashboard.js"() {
@@ -105423,6 +105499,11 @@ function summarizeGenerate(r) {
105423
105499
  }
105424
105500
  }
105425
105501
  }
105502
+ const heldSections = r.heldSections ?? [];
105503
+ const heldKeys = new Set(heldSections.map((h) => `${h.doc}\0${h.anchor}`));
105504
+ const readyButHeld = heldSections.reduce((n, h) => n + h.readyScenarios.length, 0);
105505
+ const heldByFindings = r.birthFindings.filter((f2) => heldKeys.has(`${f2.doc}\0${f2.anchor}`)).length;
105506
+ const heldByErrors = r.errors.filter((e) => heldKeys.has(`${e.doc}\0${e.anchor}`)).length;
105426
105507
  return {
105427
105508
  generatedAt: r.generatedAt,
105428
105509
  status: r.status,
@@ -105433,6 +105514,9 @@ function summarizeGenerate(r) {
105433
105514
  blockedOnCapabilities,
105434
105515
  birthFindings: r.birthFindings.length,
105435
105516
  errors: r.errors.length,
105517
+ readyButHeld,
105518
+ heldByFindings,
105519
+ heldByErrors,
105436
105520
  ...r.usage ? { usage: r.usage } : {}
105437
105521
  };
105438
105522
  }
@@ -105464,6 +105548,7 @@ var init_guard = __esm({
105464
105548
  init_result();
105465
105549
  init_report();
105466
105550
  init_manifest();
105551
+ init_decisions();
105467
105552
  init_dashboard();
105468
105553
  init_summary();
105469
105554
  }
@@ -146609,7 +146694,7 @@ var require_windows = __commonJS({
146609
146694
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js"(exports, module) {
146610
146695
  module.exports = isexe;
146611
146696
  isexe.sync = sync;
146612
- var fs69 = __require("fs");
146697
+ var fs70 = __require("fs");
146613
146698
  function checkPathExt(path80, options) {
146614
146699
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
146615
146700
  if (!pathext) {
@@ -146634,12 +146719,12 @@ var require_windows = __commonJS({
146634
146719
  return checkPathExt(path80, options);
146635
146720
  }
146636
146721
  function isexe(path80, options, cb) {
146637
- fs69.stat(path80, function(er, stat) {
146722
+ fs70.stat(path80, function(er, stat) {
146638
146723
  cb(er, er ? false : checkStat(stat, path80, options));
146639
146724
  });
146640
146725
  }
146641
146726
  function sync(path80, options) {
146642
- return checkStat(fs69.statSync(path80), path80, options);
146727
+ return checkStat(fs70.statSync(path80), path80, options);
146643
146728
  }
146644
146729
  }
146645
146730
  });
@@ -146649,14 +146734,14 @@ var require_mode = __commonJS({
146649
146734
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/mode.js"(exports, module) {
146650
146735
  module.exports = isexe;
146651
146736
  isexe.sync = sync;
146652
- var fs69 = __require("fs");
146737
+ var fs70 = __require("fs");
146653
146738
  function isexe(path80, options, cb) {
146654
- fs69.stat(path80, function(er, stat) {
146739
+ fs70.stat(path80, function(er, stat) {
146655
146740
  cb(er, er ? false : checkStat(stat, options));
146656
146741
  });
146657
146742
  }
146658
146743
  function sync(path80, options) {
146659
- return checkStat(fs69.statSync(path80), options);
146744
+ return checkStat(fs70.statSync(path80), options);
146660
146745
  }
146661
146746
  function checkStat(stat, options) {
146662
146747
  return stat.isFile() && checkMode(stat, options);
@@ -146680,7 +146765,7 @@ var require_mode = __commonJS({
146680
146765
  // node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js
146681
146766
  var require_isexe = __commonJS({
146682
146767
  "node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/index.js"(exports, module) {
146683
- var fs69 = __require("fs");
146768
+ var fs70 = __require("fs");
146684
146769
  var core2;
146685
146770
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
146686
146771
  core2 = require_windows();
@@ -146944,16 +147029,16 @@ var require_shebang_command = __commonJS({
146944
147029
  var require_readShebang = __commonJS({
146945
147030
  "node_modules/.pnpm/cross-spawn@7.0.6/node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
146946
147031
  "use strict";
146947
- var fs69 = __require("fs");
147032
+ var fs70 = __require("fs");
146948
147033
  var shebangCommand = require_shebang_command();
146949
147034
  function readShebang(command) {
146950
147035
  const size = 150;
146951
147036
  const buffer = Buffer.alloc(size);
146952
147037
  let fd;
146953
147038
  try {
146954
- fd = fs69.openSync(command, "r");
146955
- fs69.readSync(fd, buffer, 0, size, 0);
146956
- fs69.closeSync(fd);
147039
+ fd = fs70.openSync(command, "r");
147040
+ fs70.readSync(fd, buffer, 0, size, 0);
147041
+ fs70.closeSync(fd);
146957
147042
  } catch (e) {
146958
147043
  }
146959
147044
  return shebangCommand(buffer.toString());
@@ -147357,7 +147442,7 @@ var require_package = __commonJS({
147357
147442
  // node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js
147358
147443
  var require_main = __commonJS({
147359
147444
  "node_modules/.pnpm/dotenv@16.6.1/node_modules/dotenv/lib/main.js"(exports, module) {
147360
- var fs69 = __require("fs");
147445
+ var fs70 = __require("fs");
147361
147446
  var path80 = __require("path");
147362
147447
  var os15 = __require("os");
147363
147448
  var crypto6 = __require("crypto");
@@ -147466,7 +147551,7 @@ var require_main = __commonJS({
147466
147551
  if (options && options.path && options.path.length > 0) {
147467
147552
  if (Array.isArray(options.path)) {
147468
147553
  for (const filepath of options.path) {
147469
- if (fs69.existsSync(filepath)) {
147554
+ if (fs70.existsSync(filepath)) {
147470
147555
  possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
147471
147556
  }
147472
147557
  }
@@ -147476,7 +147561,7 @@ var require_main = __commonJS({
147476
147561
  } else {
147477
147562
  possibleVaultPath = path80.resolve(process.cwd(), ".env.vault");
147478
147563
  }
147479
- if (fs69.existsSync(possibleVaultPath)) {
147564
+ if (fs70.existsSync(possibleVaultPath)) {
147480
147565
  return possibleVaultPath;
147481
147566
  }
147482
147567
  return null;
@@ -147525,7 +147610,7 @@ var require_main = __commonJS({
147525
147610
  const parsedAll = {};
147526
147611
  for (const path81 of optionPaths) {
147527
147612
  try {
147528
- const parsed = DotenvModule.parse(fs69.readFileSync(path81, { encoding }));
147613
+ const parsed = DotenvModule.parse(fs70.readFileSync(path81, { encoding }));
147529
147614
  DotenvModule.populate(parsedAll, parsed, options);
147530
147615
  } catch (e) {
147531
147616
  if (debug2) {
@@ -161618,7 +161703,7 @@ var require_form_data = __commonJS({
161618
161703
  var http = __require("http");
161619
161704
  var https = __require("https");
161620
161705
  var parseUrl = __require("url").parse;
161621
- var fs69 = __require("fs");
161706
+ var fs70 = __require("fs");
161622
161707
  var Stream = __require("stream").Stream;
161623
161708
  var crypto6 = __require("crypto");
161624
161709
  var mime = require_mime_types();
@@ -161685,7 +161770,7 @@ var require_form_data = __commonJS({
161685
161770
  if (value.end != void 0 && value.end != Infinity && value.start != void 0) {
161686
161771
  callback(null, value.end + 1 - (value.start ? value.start : 0));
161687
161772
  } else {
161688
- fs69.stat(value.path, function(err, stat) {
161773
+ fs70.stat(value.path, function(err, stat) {
161689
161774
  if (err) {
161690
161775
  callback(err);
161691
161776
  return;
@@ -162303,8 +162388,8 @@ var require_node2 = __commonJS({
162303
162388
  }
162304
162389
  break;
162305
162390
  case "FILE":
162306
- var fs69 = __require("fs");
162307
- stream2 = new fs69.SyncWriteStream(fd2, { autoClose: false });
162391
+ var fs70 = __require("fs");
162392
+ stream2 = new fs70.SyncWriteStream(fd2, { autoClose: false });
162308
162393
  stream2._type = "fs";
162309
162394
  break;
162310
162395
  case "PIPE":
@@ -170284,7 +170369,7 @@ function readToolVersion() {
170284
170369
  if (cachedVersion)
170285
170370
  return cachedVersion;
170286
170371
  if (true) {
170287
- cachedVersion = "0.7.0-next.3";
170372
+ cachedVersion = "0.7.0-next.4";
170288
170373
  return cachedVersion;
170289
170374
  }
170290
170375
  try {
@@ -172690,13 +172775,13 @@ var init_pexprs_toFailure = __esm({
172690
172775
  return new Failure(this, "a Unicode [" + this.categoryOrProp + "] character", "description");
172691
172776
  };
172692
172777
  Alt.prototype.toFailure = function(grammar2) {
172693
- const fs69 = this.terms.map((t2) => t2.toFailure(grammar2));
172694
- const description = "(" + fs69.join(" or ") + ")";
172778
+ const fs70 = this.terms.map((t2) => t2.toFailure(grammar2));
172779
+ const description = "(" + fs70.join(" or ") + ")";
172695
172780
  return new Failure(this, description, "description");
172696
172781
  };
172697
172782
  Seq.prototype.toFailure = function(grammar2) {
172698
- const fs69 = this.factors.map((f2) => f2.toFailure(grammar2));
172699
- const description = "(" + fs69.join(" ") + ")";
172783
+ const fs70 = this.factors.map((f2) => f2.toFailure(grammar2));
172784
+ const description = "(" + fs70.join(" ") + ")";
172700
172785
  return new Failure(this, description, "description");
172701
172786
  };
172702
172787
  Iter.prototype.toFailure = function(grammar2) {
@@ -174533,9 +174618,9 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174533
174618
  decl.withSuperGrammar(namespace[superGrammarName]);
174534
174619
  }
174535
174620
  },
174536
- Rule_define(n, fs69, d3, _2, b) {
174621
+ Rule_define(n, fs70, d3, _2, b) {
174537
174622
  currentRuleName = n.visit();
174538
- currentRuleFormals = fs69.children.map((c2) => c2.visit())[0] || [];
174623
+ currentRuleFormals = fs70.children.map((c2) => c2.visit())[0] || [];
174539
174624
  if (!decl.defaultStartRule && decl.ensureSuperGrammar() !== Grammar.ProtoBuiltInRules) {
174540
174625
  decl.withDefaultStartRule(currentRuleName);
174541
174626
  }
@@ -174544,9 +174629,9 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174544
174629
  const source = this.source.trimmed();
174545
174630
  return decl.define(currentRuleName, currentRuleFormals, body, description, source);
174546
174631
  },
174547
- Rule_override(n, fs69, _2, b) {
174632
+ Rule_override(n, fs70, _2, b) {
174548
174633
  currentRuleName = n.visit();
174549
- currentRuleFormals = fs69.children.map((c2) => c2.visit())[0] || [];
174634
+ currentRuleFormals = fs70.children.map((c2) => c2.visit())[0] || [];
174550
174635
  const source = this.source.trimmed();
174551
174636
  decl.ensureSuperGrammarRuleForOverriding(currentRuleName, source);
174552
174637
  overriding = true;
@@ -174554,9 +174639,9 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174554
174639
  overriding = false;
174555
174640
  return decl.override(currentRuleName, currentRuleFormals, body, null, source);
174556
174641
  },
174557
- Rule_extend(n, fs69, _2, b) {
174642
+ Rule_extend(n, fs70, _2, b) {
174558
174643
  currentRuleName = n.visit();
174559
- currentRuleFormals = fs69.children.map((c2) => c2.visit())[0] || [];
174644
+ currentRuleFormals = fs70.children.map((c2) => c2.visit())[0] || [];
174560
174645
  const body = b.visit();
174561
174646
  const source = this.source.trimmed();
174562
174647
  return decl.extend(currentRuleName, currentRuleFormals, body, null, source);
@@ -174583,8 +174668,8 @@ function buildGrammar2(match4, namespace, optOhmGrammarForTesting, options) {
174583
174668
  return builder.alt(...args).withSource(this.source);
174584
174669
  }
174585
174670
  },
174586
- Formals(opointy, fs69, cpointy) {
174587
- return fs69.visit();
174671
+ Formals(opointy, fs70, cpointy) {
174672
+ return fs70.visit();
174588
174673
  },
174589
174674
  Params(opointy, ps, cpointy) {
174590
174675
  return ps.visit();
@@ -174746,8 +174831,8 @@ function initPrototypeParser(grammar2) {
174746
174831
  formals: optFormals.children.map((c2) => c2.parse())[0] || []
174747
174832
  };
174748
174833
  },
174749
- Formals(oparen, fs69, cparen) {
174750
- return fs69.asIteration().children.map((c2) => c2.parse());
174834
+ Formals(oparen, fs70, cparen) {
174835
+ return fs70.asIteration().children.map((c2) => c2.parse());
174751
174836
  },
174752
174837
  name(first2, rest) {
174753
174838
  return this.sourceString;
@@ -196095,7 +196180,7 @@ async function runRulesReset({ ruleKey }) {
196095
196180
 
196096
196181
  // tools/cli/src/commands/contracts.ts
196097
196182
  init_dist4();
196098
- import fs66 from "node:fs";
196183
+ import fs67 from "node:fs";
196099
196184
  import path73 from "node:path";
196100
196185
 
196101
196186
  // packages/spec-consolidator/dist/types.js
@@ -204900,6 +204985,7 @@ var HISTORY_FILE2 = "history.json";
204900
204985
  var RESULT_FILE = "result.json";
204901
204986
  var RECIPE_FILE = "recipe.json";
204902
204987
  var MANIFEST_FILE = "manifest.json";
204988
+ var DECISIONS_FILE = "decisions.json";
204903
204989
  function guardDir(repoRoot5) {
204904
204990
  return path54.join(repoRoot5, TRUECOURSE_DIR3, GUARD_DIR);
204905
204991
  }
@@ -204927,6 +205013,9 @@ function recipePath(repoRoot5) {
204927
205013
  function manifestPath2(repoRoot5) {
204928
205014
  return path54.join(scenariosDir(repoRoot5), MANIFEST_FILE);
204929
205015
  }
205016
+ function guardDecisionsPath(repoRoot5) {
205017
+ return path54.join(scenariosDir(repoRoot5), DECISIONS_FILE);
205018
+ }
204930
205019
  function evidenceRunDir(repoRoot5, runId) {
204931
205020
  return path54.join(guardDir(repoRoot5), EVIDENCE_DIR, runId);
204932
205021
  }
@@ -205659,6 +205748,14 @@ function writeFile(dir, name, content) {
205659
205748
 
205660
205749
  // packages/guard-runner/dist/run-scenario.js
205661
205750
  var ENV_PINS = DETERMINISM_PINS;
205751
+ var SANDBOX_SETUP_EXPECTED = "sandbox setup to succeed";
205752
+ var CAPABILITY_SETUP_EXPECTED = "setup capabilities to materialize";
205753
+ function isSetupDefectResult(result) {
205754
+ if (result.outcome !== "error")
205755
+ return false;
205756
+ const expected = result.failure?.expected;
205757
+ return expected === SANDBOX_SETUP_EXPECTED || expected === CAPABILITY_SETUP_EXPECTED;
205758
+ }
205662
205759
  async function runScenario(scenario, ctx) {
205663
205760
  const start = Date.now();
205664
205761
  const base2 = {
@@ -205679,7 +205776,7 @@ async function runScenario(scenario, ctx) {
205679
205776
  ...base2,
205680
205777
  outcome: "error",
205681
205778
  durationMs: Date.now() - start,
205682
- failure: { step: 1, expected: "sandbox setup to succeed", actual: message }
205779
+ failure: { step: 1, expected: SANDBOX_SETUP_EXPECTED, actual: message }
205683
205780
  };
205684
205781
  }
205685
205782
  const normCtx = { sandboxRoot: sandbox.root, repoRoot: ctx.repoRoot };
@@ -205694,7 +205791,7 @@ async function runScenario(scenario, ctx) {
205694
205791
  ...base2,
205695
205792
  outcome: "error",
205696
205793
  durationMs: Date.now() - start,
205697
- failure: { step: 1, expected: "setup capabilities to materialize", actual: message }
205794
+ failure: { step: 1, expected: CAPABILITY_SETUP_EXPECTED, actual: message }
205698
205795
  };
205699
205796
  }
205700
205797
  for (let i = 0; i < scenario.steps.length; i++) {
@@ -206174,12 +206271,27 @@ async function mapWithConcurrency(items, limit, fn) {
206174
206271
  return results;
206175
206272
  }
206176
206273
 
206274
+ // packages/guard-runner/dist/decisions.js
206275
+ init_dist5();
206276
+ import fs57 from "node:fs";
206277
+ function readGuardDecisions(repoRoot5) {
206278
+ const file = guardDecisionsPath(repoRoot5);
206279
+ if (!fs57.existsSync(file))
206280
+ return EMPTY_GUARD_DECISIONS;
206281
+ try {
206282
+ const parsed = GuardDecisionsSchema.safeParse(JSON.parse(fs57.readFileSync(file, "utf-8")));
206283
+ return parsed.success ? parsed.data : EMPTY_GUARD_DECISIONS;
206284
+ } catch {
206285
+ return EMPTY_GUARD_DECISIONS;
206286
+ }
206287
+ }
206288
+
206177
206289
  // packages/guard-generator/dist/generate.js
206178
206290
  init_dist5();
206179
206291
 
206180
206292
  // packages/guard-generator/dist/section-plan.js
206181
206293
  init_zod();
206182
- import fs57 from "node:fs";
206294
+ import fs58 from "node:fs";
206183
206295
  import path63 from "node:path";
206184
206296
  import { createHash as createHash15 } from "node:crypto";
206185
206297
  init_dist5();
@@ -206497,10 +206609,10 @@ var CorpusShape2 = external_exports.object({
206497
206609
  function readCorpusAreaTags(repoRoot5) {
206498
206610
  const file = path63.join(repoRoot5, ".truecourse", "specs", "corpus.json");
206499
206611
  const map2 = /* @__PURE__ */ new Map();
206500
- if (!fs57.existsSync(file))
206612
+ if (!fs58.existsSync(file))
206501
206613
  return map2;
206502
206614
  try {
206503
- const parsed = CorpusShape2.safeParse(JSON.parse(fs57.readFileSync(file, "utf-8")));
206615
+ const parsed = CorpusShape2.safeParse(JSON.parse(fs58.readFileSync(file, "utf-8")));
206504
206616
  if (!parsed.success)
206505
206617
  return map2;
206506
206618
  for (const d3 of parsed.data.docs ?? [])
@@ -206519,17 +206631,17 @@ function generationInputsHash(fingerprint2, recipeFingerprint) {
206519
206631
  ].join("\0")).digest("hex");
206520
206632
  }
206521
206633
  function hasGuardUniverse(repoRoot5) {
206522
- return fs57.existsSync(path63.join(repoRoot5, ".truecourse", "specs", "corpus.json"));
206634
+ return fs58.existsSync(path63.join(repoRoot5, ".truecourse", "specs", "corpus.json"));
206523
206635
  }
206524
206636
  function planGuardWork(repoRoot5, recipeFingerprint) {
206525
206637
  const recipeFp = recipeFingerprint ?? computeRecipeFingerprint(repoRoot5);
206526
- const recipeMissing = !fs57.existsSync(recipePath(repoRoot5));
206638
+ const recipeMissing = !fs58.existsSync(recipePath(repoRoot5));
206527
206639
  const hasUniverse = hasGuardUniverse(repoRoot5);
206528
206640
  const { indexes } = indexRepoDocs(repoRoot5, []);
206529
206641
  const areaTags = readCorpusAreaTags(repoRoot5);
206530
206642
  const sections = [];
206531
206643
  for (const [doc, index] of indexes) {
206532
- const texts = extractSectionTexts(doc, fs57.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"));
206644
+ const texts = extractSectionTexts(doc, fs58.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"));
206533
206645
  for (const s of index.sections) {
206534
206646
  const t2 = texts.get(s.anchor);
206535
206647
  sections.push({
@@ -206574,7 +206686,7 @@ function collectWorkDocs(repoRoot5, plan) {
206574
206686
  }
206575
206687
  return [...byDoc].map(([doc, sections]) => ({
206576
206688
  doc,
206577
- content: fs57.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"),
206689
+ content: fs58.readFileSync(path63.resolve(repoRoot5, doc), "utf-8"),
206578
206690
  sections
206579
206691
  }));
206580
206692
  }
@@ -206961,7 +207073,7 @@ function truncate3(text4) {
206961
207073
  }
206962
207074
 
206963
207075
  // packages/guard-generator/dist/recipe-discovery.js
206964
- import fs58 from "node:fs";
207076
+ import fs59 from "node:fs";
206965
207077
  import os13 from "node:os";
206966
207078
  import path65 from "node:path";
206967
207079
  import { createHash as createHash18 } from "node:crypto";
@@ -207017,8 +207129,8 @@ async function discoverRecipe(repoRoot5, runner) {
207017
207129
  ...proposal.env ? { env: proposal.env } : {}
207018
207130
  };
207019
207131
  const target = recipePath(repoRoot5);
207020
- fs58.mkdirSync(path65.dirname(target), { recursive: true });
207021
- fs58.writeFileSync(target, JSON.stringify(recipe, null, 2) + "\n");
207132
+ fs59.mkdirSync(path65.dirname(target), { recursive: true });
207133
+ fs59.writeFileSync(target, JSON.stringify(recipe, null, 2) + "\n");
207022
207134
  return {
207023
207135
  status: "discovered",
207024
207136
  recipe,
@@ -207049,13 +207161,13 @@ async function proposeRecipeWithReask(input, runner) {
207049
207161
  }
207050
207162
  function readDiscoveryInputs(repoRoot5) {
207051
207163
  const pkgPath = path65.join(repoRoot5, "package.json");
207052
- const packageJson = fs58.existsSync(pkgPath) ? fs58.readFileSync(pkgPath, "utf-8") : "(no package.json)";
207053
- const presentInputs = DISCOVERY_INPUTS.filter((f2) => fs58.existsSync(path65.join(repoRoot5, f2)));
207164
+ const packageJson = fs59.existsSync(pkgPath) ? fs59.readFileSync(pkgPath, "utf-8") : "(no package.json)";
207165
+ const presentInputs = DISCOVERY_INPUTS.filter((f2) => fs59.existsSync(path65.join(repoRoot5, f2)));
207054
207166
  return { packageJson, presentInputs };
207055
207167
  }
207056
207168
  async function probeEntry(repoRoot5, entry) {
207057
207169
  const resolved = resolveEntry(repoRoot5, entry);
207058
- const cwd = fs58.mkdtempSync(path65.join(os13.tmpdir(), "tc-guard-probe-"));
207170
+ const cwd = fs59.mkdtempSync(path65.join(os13.tmpdir(), "tc-guard-probe-"));
207059
207171
  try {
207060
207172
  for (const args of [["--help"], []]) {
207061
207173
  const capture = await executeStep({
@@ -207072,7 +207184,7 @@ async function probeEntry(repoRoot5, entry) {
207072
207184
  reason: `entrypoint ${JSON.stringify(entry)} did not answer to \`--help\` or a bare invocation`
207073
207185
  };
207074
207186
  } finally {
207075
- fs58.rmSync(cwd, { recursive: true, force: true });
207187
+ fs59.rmSync(cwd, { recursive: true, force: true });
207076
207188
  }
207077
207189
  }
207078
207190
 
@@ -207119,7 +207231,7 @@ async function birthValidate(repoRoot5, candidates, opts = {}) {
207119
207231
  }
207120
207232
 
207121
207233
  // packages/guard-generator/dist/serialize.js
207122
- import fs59 from "node:fs";
207234
+ import fs60 from "node:fs";
207123
207235
  import path66 from "node:path";
207124
207236
  init_dist5();
207125
207237
  function anchorLeaf(anchor) {
@@ -207162,15 +207274,15 @@ function scenarioFileIndex(repoRoot5) {
207162
207274
  const root2 = scenariosDir(repoRoot5);
207163
207275
  const map2 = /* @__PURE__ */ new Map();
207164
207276
  const walk20 = (dir) => {
207165
- if (!fs59.existsSync(dir))
207277
+ if (!fs60.existsSync(dir))
207166
207278
  return;
207167
- for (const entry of fs59.readdirSync(dir, { withFileTypes: true })) {
207279
+ for (const entry of fs60.readdirSync(dir, { withFileTypes: true })) {
207168
207280
  const full = path66.join(dir, entry.name);
207169
207281
  if (entry.isDirectory())
207170
207282
  walk20(full);
207171
207283
  else if (entry.isFile() && /\.ya?ml$/i.test(entry.name)) {
207172
207284
  try {
207173
- const doc = jsYaml.load(fs59.readFileSync(full, "utf-8"));
207285
+ const doc = jsYaml.load(fs60.readFileSync(full, "utf-8"));
207174
207286
  if (doc && typeof doc.id === "string")
207175
207287
  map2.set(doc.id, full);
207176
207288
  } catch {
@@ -207184,19 +207296,22 @@ function scenarioFileIndex(repoRoot5) {
207184
207296
  function existingScenarioIds(repoRoot5) {
207185
207297
  return new Set(loadScenarios(repoRoot5).scenarios.map((s) => s.id));
207186
207298
  }
207299
+ function serializeScenarioYaml(scenario) {
207300
+ return jsYaml.dump(scenario, { lineWidth: -1, noRefs: true });
207301
+ }
207187
207302
  function writeScenarioFile(repoRoot5, slug2, scenario) {
207188
207303
  const dir = path66.join(scenariosDir(repoRoot5), slug2);
207189
- fs59.mkdirSync(dir, { recursive: true });
207304
+ fs60.mkdirSync(dir, { recursive: true });
207190
207305
  const file = path66.join(dir, `${scenario.id}.yaml`);
207191
- fs59.writeFileSync(file, jsYaml.dump(scenario, { lineWidth: -1, noRefs: true }));
207306
+ fs60.writeFileSync(file, serializeScenarioYaml(scenario));
207192
207307
  return path66.relative(repoRoot5, file);
207193
207308
  }
207194
207309
  function deleteScenarioFiles(repoRoot5, ids) {
207195
207310
  const index = scenarioFileIndex(repoRoot5);
207196
207311
  for (const id of ids) {
207197
207312
  const file = index.get(id);
207198
- if (file && fs59.existsSync(file))
207199
- fs59.rmSync(file);
207313
+ if (file && fs60.existsSync(file))
207314
+ fs60.rmSync(file);
207200
207315
  }
207201
207316
  }
207202
207317
 
@@ -207266,7 +207381,9 @@ async function generateGuards(options) {
207266
207381
  errors: [],
207267
207382
  extractionFailures: [],
207268
207383
  orphaned,
207269
- birthPassed: 0
207384
+ birthPassed: 0,
207385
+ heldSections: [],
207386
+ orphanedDismissals: []
207270
207387
  };
207271
207388
  }
207272
207389
  const limit = pLimit(Math.max(1, options.concurrency ?? defaultConcurrency3()));
@@ -207281,6 +207398,10 @@ async function generateGuards(options) {
207281
207398
  const coverageGaps = [];
207282
207399
  const errors = [];
207283
207400
  const extractionFailures = [];
207401
+ const decisions = readGuardDecisions(repoRoot5);
207402
+ const dismissalByKey = new Map(decisions.dismissedClaims.map((d3) => [dismissedClaimKey(d3.doc, d3.anchor, d3.title), d3]));
207403
+ const extractedClaimKeys = /* @__PURE__ */ new Set();
207404
+ const extractedDocs = /* @__PURE__ */ new Set();
207284
207405
  const workDocs = collectWorkDocs(repoRoot5, plan);
207285
207406
  const workKeys = new Set(plan.work.map(key2));
207286
207407
  const workDocByPath = new Map(workDocs.map((d3) => [d3.doc, d3]));
@@ -207310,34 +207431,47 @@ async function generateGuards(options) {
207310
207431
  reason: `${result.failedViews} extraction view(s) failed \u2014 re-run to complete coverage for affected sections`
207311
207432
  });
207312
207433
  }
207434
+ extractedDocs.add(doc.doc);
207435
+ for (const c2 of result.data.claims) {
207436
+ extractedClaimKeys.add(dismissedClaimKey(doc.doc, c2.sectionAnchor, c2.claim));
207437
+ }
207313
207438
  const { claimsByAnchor, noteByAnchor } = groupExtraction(result.data);
207314
207439
  for (const s of doc.sections) {
207315
207440
  if (!workKeys.has(key2(s)))
207316
207441
  continue;
207317
207442
  const claims = claimsByAnchor.get(s.anchor) ?? [];
207318
- const cli = claims.filter((c2) => isRunnableDriver(c2.driver));
207443
+ const cliAll = claims.filter((c2) => isRunnableDriver(c2.driver));
207319
207444
  const others = claims.filter((c2) => !isRunnableDriver(c2.driver));
207320
207445
  const note = noteByAnchor.get(s.anchor);
207446
+ const cli = cliAll.filter((c2) => !dismissalByKey.has(dismissedClaimKey(s.doc, s.anchor, c2.claim)));
207447
+ const dismissed = cliAll.filter((c2) => dismissalByKey.has(dismissedClaimKey(s.doc, s.anchor, c2.claim)));
207448
+ for (const d3 of dismissed) {
207449
+ const entry = dismissalByKey.get(dismissedClaimKey(s.doc, s.anchor, d3.claim));
207450
+ coverageGaps.push({ doc: s.doc, anchor: s.anchor, kind: "dismissed", reason: dismissedReason(d3.claim, entry?.note) });
207451
+ }
207321
207452
  for (const o of others) {
207322
207453
  coverageGaps.push({ doc: s.doc, anchor: s.anchor, kind: "awaiting-driver", driver: o.driver, reason: o.reason });
207323
207454
  }
207324
207455
  if (cli.length === 0 && others.length === 0) {
207325
- if (!result.complete && !note) {
207326
- extractFailedKeys.add(key2(s));
207327
- continue;
207456
+ if (dismissed.length === 0) {
207457
+ if (!result.complete && !note) {
207458
+ extractFailedKeys.add(key2(s));
207459
+ continue;
207460
+ }
207461
+ coverageGaps.push({
207462
+ doc: s.doc,
207463
+ anchor: s.anchor,
207464
+ kind: note ? "untestable" : "no-claim",
207465
+ reason: note?.reason ?? "the section states no CLI-assertable claim"
207466
+ });
207328
207467
  }
207329
- coverageGaps.push({
207330
- doc: s.doc,
207331
- anchor: s.anchor,
207332
- kind: note ? "untestable" : "no-claim",
207333
- reason: note?.reason ?? "the section states no CLI-assertable claim"
207334
- });
207335
207468
  }
207336
207469
  classificationByKey.set(key2(s), deriveClassification(cli, others, note));
207337
207470
  for (const c2 of cli)
207338
207471
  authTasks.push({ ref: `c${refSeq++}`, section: s, claim: c2 });
207339
207472
  }
207340
207473
  }
207474
+ const orphanedDismissals = decisions.dismissedClaims.filter((d3) => extractedDocs.has(d3.doc) && !extractedClaimKeys.has(dismissedClaimKey(d3.doc, d3.anchor, d3.title))).map((d3) => ({ doc: d3.doc, anchor: d3.anchor, title: d3.title }));
207341
207475
  const buildPromise = runBuild(repoRoot5, recipe.build, recipe.env);
207342
207476
  let buildAnnounced = false;
207343
207477
  const awaitBuild = async () => {
@@ -207406,6 +207540,7 @@ async function generateGuards(options) {
207406
207540
  };
207407
207541
  const written = [];
207408
207542
  const birthFindings = [];
207543
+ const heldSections = [];
207409
207544
  let entryPreflightFailure = null;
207410
207545
  let birthTotal = 0;
207411
207546
  let birthSettled = 0;
@@ -207513,9 +207648,9 @@ async function generateGuards(options) {
207513
207648
  pushInto(r1ByRef, o.candidate.ref, o);
207514
207649
  const retryEntries = [];
207515
207650
  for (const [ref, outcomes] of r1ByRef) {
207516
- const fail4 = outcomes.find((o) => o.result.outcome === "fail");
207517
- if (fail4) {
207518
- retryEntries.push({ task: taskByRef.get(ref), evidence: toFinding(fail4) });
207651
+ const retriable2 = outcomes.find((o) => o.result.outcome === "fail" || isSetupDefectResult(o.result));
207652
+ if (retriable2) {
207653
+ retryEntries.push({ task: taskByRef.get(ref), evidence: toFinding(retriable2) });
207519
207654
  continue;
207520
207655
  }
207521
207656
  for (const o of outcomes) {
@@ -207574,6 +207709,16 @@ async function generateGuards(options) {
207574
207709
  ids.push(c2.scenario.id);
207575
207710
  }
207576
207711
  upsertSection(section, ids);
207712
+ } else if (persistedHere.length > 0) {
207713
+ heldSections.push({
207714
+ doc: section.doc,
207715
+ anchor: section.anchor,
207716
+ readyScenarios: persistedHere.map((c2) => ({
207717
+ id: c2.scenario.id,
207718
+ title: c2.scenario.title,
207719
+ yaml: serializeScenarioYaml(c2.scenario)
207720
+ }))
207721
+ });
207577
207722
  }
207578
207723
  }
207579
207724
  const authorTotal = authTasks.length;
@@ -207652,6 +207797,8 @@ async function generateGuards(options) {
207652
207797
  extractionFailures,
207653
207798
  orphaned,
207654
207799
  birthPassed,
207800
+ heldSections,
207801
+ orphanedDismissals,
207655
207802
  manifestPath: manifestPath2(repoRoot5),
207656
207803
  ...entryPreflightFailure ? { entryPreflight: entryPreflightFailure } : {}
207657
207804
  };
@@ -207698,7 +207845,9 @@ function emptyResult(status, extra) {
207698
207845
  errors: [],
207699
207846
  extractionFailures: [],
207700
207847
  orphaned: [],
207701
- birthPassed: 0
207848
+ birthPassed: 0,
207849
+ heldSections: [],
207850
+ orphanedDismissals: []
207702
207851
  };
207703
207852
  }
207704
207853
  function readPriorManifest(repoRoot5) {
@@ -207793,9 +207942,18 @@ function toFinding(o) {
207793
207942
  step: f2?.step ?? 1,
207794
207943
  expected: f2?.expected ?? "",
207795
207944
  actual: f2?.actual ?? "",
207796
- ...o.result.evidencePath ? { evidencePath: o.result.evidencePath } : {}
207945
+ ...o.result.evidencePath ? { evidencePath: o.result.evidencePath } : {},
207946
+ // Judge-on-one-screen (item 19): the failed candidate's exact YAML rides inline
207947
+ // so the finding detail shows the commands it ran; `claim` is the dismissal
207948
+ // identity (item 20) so the detail's Dismiss action can key on it.
207949
+ yaml: serializeScenarioYaml(o.candidate.scenario),
207950
+ claim: o.candidate.claim.claim
207797
207951
  };
207798
207952
  }
207953
+ function dismissedReason(claim, note) {
207954
+ const base2 = `dismissed: ${oneLine(claim)}`;
207955
+ return note ? `${base2} \u2014 ${oneLine(note)}` : base2;
207956
+ }
207799
207957
  function errorFrom(o) {
207800
207958
  return {
207801
207959
  doc: o.candidate.section.doc,
@@ -207858,7 +208016,7 @@ init_context_router();
207858
208016
 
207859
208017
  // packages/core/dist/services/llm/model-prices.js
207860
208018
  init_paths();
207861
- import fs60 from "node:fs";
208019
+ import fs61 from "node:fs";
207862
208020
  import path67 from "node:path";
207863
208021
  var OPENROUTER_URL = "https://openrouter.ai/api/v1/models";
207864
208022
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
@@ -207879,7 +208037,7 @@ function cacheFilePath() {
207879
208037
  }
207880
208038
  function readCache7() {
207881
208039
  try {
207882
- const t2 = JSON.parse(fs60.readFileSync(cacheFilePath(), "utf-8"));
208040
+ const t2 = JSON.parse(fs61.readFileSync(cacheFilePath(), "utf-8"));
207883
208041
  if (t2 && t2.tiers && t2.byId && typeof t2.fetchedAt === "number")
207884
208042
  return t2;
207885
208043
  } catch {
@@ -207888,8 +208046,8 @@ function readCache7() {
207888
208046
  }
207889
208047
  function writeCache7(t2) {
207890
208048
  try {
207891
- fs60.mkdirSync(path67.dirname(cacheFilePath()), { recursive: true });
207892
- fs60.writeFileSync(cacheFilePath(), JSON.stringify(t2));
208049
+ fs61.mkdirSync(path67.dirname(cacheFilePath()), { recursive: true });
208050
+ fs61.writeFileSync(cacheFilePath(), JSON.stringify(t2));
207893
208051
  } catch {
207894
208052
  }
207895
208053
  }
@@ -208268,7 +208426,7 @@ function changedSubject(total, changed, noun) {
208268
208426
 
208269
208427
  // packages/core/dist/commands/spec-in-process.js
208270
208428
  init_dist8();
208271
- import fs65 from "node:fs";
208429
+ import fs66 from "node:fs";
208272
208430
  import path72 from "node:path";
208273
208431
  init_git();
208274
208432
  import { createHash as createHash20, randomUUID as randomUUID27 } from "node:crypto";
@@ -208276,7 +208434,7 @@ import { createHash as createHash20, randomUUID as randomUUID27 } from "node:cry
208276
208434
  // packages/core/dist/lib/verify-store.js
208277
208435
  init_atomic_write();
208278
208436
  init_analysis_store();
208279
- import fs61 from "node:fs";
208437
+ import fs62 from "node:fs";
208280
208438
  import path68 from "node:path";
208281
208439
 
208282
208440
  // packages/core/dist/types/verify-snapshot.js
@@ -208375,7 +208533,7 @@ var FileVerifyStore = class {
208375
208533
  const file = verifyLatestPath(repoPath);
208376
208534
  let mtime;
208377
208535
  try {
208378
- mtime = fs61.statSync(file).mtimeMs;
208536
+ mtime = fs62.statSync(file).mtimeMs;
208379
208537
  } catch (err) {
208380
208538
  if (err.code === "ENOENT") {
208381
208539
  latestCache2.delete(repoPath);
@@ -208386,7 +208544,7 @@ var FileVerifyStore = class {
208386
208544
  const cached = latestCache2.get(repoPath);
208387
208545
  if (cached && cached.mtime === mtime)
208388
208546
  return cached.data;
208389
- const data = JSON.parse(fs61.readFileSync(file, "utf-8"));
208547
+ const data = JSON.parse(fs62.readFileSync(file, "utf-8"));
208390
208548
  latestCache2.set(repoPath, { mtime, data });
208391
208549
  return data;
208392
208550
  }
@@ -208396,7 +208554,7 @@ var FileVerifyStore = class {
208396
208554
  }
208397
208555
  async deleteVerifyLatest(repoPath) {
208398
208556
  try {
208399
- fs61.unlinkSync(verifyLatestPath(repoPath));
208557
+ fs62.unlinkSync(verifyLatestPath(repoPath));
208400
208558
  } catch (err) {
208401
208559
  if (err.code !== "ENOENT")
208402
208560
  throw err;
@@ -208410,21 +208568,21 @@ var FileVerifyStore = class {
208410
208568
  }
208411
208569
  async readVerifyRun(repoPath, filename) {
208412
208570
  const file = verifyRunPath(repoPath, filename);
208413
- if (!fs61.existsSync(file))
208571
+ if (!fs62.existsSync(file))
208414
208572
  return null;
208415
- return JSON.parse(fs61.readFileSync(file, "utf-8"));
208573
+ return JSON.parse(fs62.readFileSync(file, "utf-8"));
208416
208574
  }
208417
208575
  async listVerifyRuns(repoPath) {
208418
208576
  const dir = runsDir(repoPath);
208419
- if (!fs61.existsSync(dir))
208577
+ if (!fs62.existsSync(dir))
208420
208578
  return [];
208421
- return fs61.readdirSync(dir).filter((n) => n.endsWith(".json")).sort();
208579
+ return fs62.readdirSync(dir).filter((n) => n.endsWith(".json")).sort();
208422
208580
  }
208423
208581
  async readVerifyHistory(repoPath) {
208424
208582
  const file = verifyHistoryPath(repoPath);
208425
- if (!fs61.existsSync(file))
208583
+ if (!fs62.existsSync(file))
208426
208584
  return { runs: [] };
208427
- return JSON.parse(fs61.readFileSync(file, "utf-8"));
208585
+ return JSON.parse(fs62.readFileSync(file, "utf-8"));
208428
208586
  }
208429
208587
  async appendVerifyHistory(repoPath, entry) {
208430
208588
  const history = await this.readVerifyHistory(repoPath);
@@ -208437,7 +208595,7 @@ var FileVerifyStore = class {
208437
208595
  if (!entry)
208438
208596
  return false;
208439
208597
  try {
208440
- fs61.unlinkSync(verifyRunPath(repoPath, entry.filename));
208598
+ fs62.unlinkSync(verifyRunPath(repoPath, entry.filename));
208441
208599
  } catch (err) {
208442
208600
  if (err.code !== "ENOENT")
208443
208601
  throw err;
@@ -208459,16 +208617,16 @@ var FileVerifyStore = class {
208459
208617
  }
208460
208618
  async readVerifyDiff(repoPath, scope) {
208461
208619
  const file = verifyDiffPath(repoPath, scope);
208462
- if (!fs61.existsSync(file))
208620
+ if (!fs62.existsSync(file))
208463
208621
  return null;
208464
- return JSON.parse(fs61.readFileSync(file, "utf-8"));
208622
+ return JSON.parse(fs62.readFileSync(file, "utf-8"));
208465
208623
  }
208466
208624
  async writeVerifyDiff(repoPath, diff, scope) {
208467
208625
  atomicWriteJson(verifyDiffPath(repoPath, scope), diff);
208468
208626
  }
208469
208627
  async deleteVerifyDiff(repoPath, scope) {
208470
208628
  try {
208471
- fs61.unlinkSync(verifyDiffPath(repoPath, scope));
208629
+ fs62.unlinkSync(verifyDiffPath(repoPath, scope));
208472
208630
  } catch (err) {
208473
208631
  if (err.code !== "ENOENT")
208474
208632
  throw err;
@@ -208502,7 +208660,7 @@ async function repoRef(repoRoot5, commitOverride) {
208502
208660
  }
208503
208661
 
208504
208662
  // packages/core/dist/lib/contract-store.js
208505
- import fs62 from "node:fs";
208663
+ import fs63 from "node:fs";
208506
208664
  import path69 from "node:path";
208507
208665
  var KIND_REL = {
208508
208666
  contracts: [".truecourse", "contracts"],
@@ -208513,7 +208671,7 @@ function countTcFiles(dir) {
208513
208671
  let n = 0;
208514
208672
  let entries;
208515
208673
  try {
208516
- entries = fs62.readdirSync(dir, { withFileTypes: true });
208674
+ entries = fs63.readdirSync(dir, { withFileTypes: true });
208517
208675
  } catch {
208518
208676
  return 0;
208519
208677
  }
@@ -208530,7 +208688,7 @@ function walkTcRel(dir, excludeInferred) {
208530
208688
  const walk20 = (rel) => {
208531
208689
  let entries;
208532
208690
  try {
208533
- entries = fs62.readdirSync(path69.join(dir, rel), { withFileTypes: true });
208691
+ entries = fs63.readdirSync(path69.join(dir, rel), { withFileTypes: true });
208534
208692
  } catch {
208535
208693
  return;
208536
208694
  }
@@ -208568,13 +208726,13 @@ var FileContractStore = class {
208568
208726
  }
208569
208727
  async loadContracts(ref, kind) {
208570
208728
  const dir = path69.join(ref.repoKey, ...KIND_REL[kind]);
208571
- if (!fs62.existsSync(dir))
208729
+ if (!fs63.existsSync(dir))
208572
208730
  return null;
208573
208731
  return { dir, cleanup: async () => {
208574
208732
  } };
208575
208733
  }
208576
208734
  async hasContracts(ref, kind) {
208577
- return fs62.existsSync(path69.join(ref.repoKey, ...KIND_REL[kind]));
208735
+ return fs63.existsSync(path69.join(ref.repoKey, ...KIND_REL[kind]));
208578
208736
  }
208579
208737
  // The file impl reads the live repo tree, which is whatever is checked out —
208580
208738
  // there is no per-commit history, so `commitSha` is ignored (OSS is latest).
@@ -208583,21 +208741,21 @@ var FileContractStore = class {
208583
208741
  }
208584
208742
  async readContractFile(repoKey, kind, relPath, _commitSha) {
208585
208743
  const dest = safeResolve(path69.join(repoKey, ...KIND_REL[kind]), relPath);
208586
- if (!dest || !fs62.existsSync(dest) || !fs62.statSync(dest).isFile())
208744
+ if (!dest || !fs63.existsSync(dest) || !fs63.statSync(dest).isFile())
208587
208745
  return null;
208588
- return fs62.readFileSync(dest, "utf-8");
208746
+ return fs63.readFileSync(dest, "utf-8");
208589
208747
  }
208590
208748
  async putContractFile(ref, kind, relPath, content) {
208591
208749
  const dest = safeResolve(path69.join(ref.repoKey, ...KIND_REL[kind]), relPath);
208592
208750
  if (!dest)
208593
208751
  throw new Error(`[contract-store] unsafe contract path: ${relPath}`);
208594
- fs62.mkdirSync(path69.dirname(dest), { recursive: true });
208595
- fs62.writeFileSync(dest, content);
208752
+ fs63.mkdirSync(path69.dirname(dest), { recursive: true });
208753
+ fs63.writeFileSync(dest, content);
208596
208754
  }
208597
208755
  async deleteContractFile(ref, kind, relPath) {
208598
208756
  const dest = safeResolve(path69.join(ref.repoKey, ...KIND_REL[kind]), relPath);
208599
208757
  if (dest)
208600
- fs62.rmSync(dest, { force: true });
208758
+ fs63.rmSync(dest, { force: true });
208601
208759
  }
208602
208760
  // OSS/local has no workspace concept (mirrors the spec store). Writing throws
208603
208761
  // (fail loud — a caller that reached here is mis-wired); reads are empty so an
@@ -208624,7 +208782,7 @@ var loadWorkspaceContracts = (ref, kind) => active8.loadWorkspaceContracts(ref,
208624
208782
  var contractsMaterializeInPlace = () => active8.materializesInPlace;
208625
208783
 
208626
208784
  // packages/core/dist/lib/spec-store.js
208627
- import fs63 from "node:fs";
208785
+ import fs64 from "node:fs";
208628
208786
  import path70 from "node:path";
208629
208787
  function specPath(repoKey, artifact) {
208630
208788
  return path70.join(repoKey, ".truecourse", "specs", `${artifact}.json`);
@@ -208640,18 +208798,18 @@ var FileSpecStore = class {
208640
208798
  throw new Error(PR_DECISIONS_FILE_ERROR);
208641
208799
  }
208642
208800
  const file = specPath(ref.repoKey, artifact);
208643
- fs63.mkdirSync(path70.dirname(file), { recursive: true });
208644
- fs63.writeFileSync(file, JSON.stringify(json2, null, 2) + "\n", "utf-8");
208801
+ fs64.mkdirSync(path70.dirname(file), { recursive: true });
208802
+ fs64.writeFileSync(file, JSON.stringify(json2, null, 2) + "\n", "utf-8");
208645
208803
  }
208646
208804
  async loadSpec(ref, artifact) {
208647
208805
  if (artifact === "decisions" && isPrDecisionsRef(ref.commitSha)) {
208648
208806
  throw new Error(PR_DECISIONS_FILE_ERROR);
208649
208807
  }
208650
208808
  const file = specPath(ref.repoKey, artifact);
208651
- if (!fs63.existsSync(file))
208809
+ if (!fs64.existsSync(file))
208652
208810
  return null;
208653
208811
  try {
208654
- return JSON.parse(fs63.readFileSync(file, "utf-8"));
208812
+ return JSON.parse(fs64.readFileSync(file, "utf-8"));
208655
208813
  } catch {
208656
208814
  return null;
208657
208815
  }
@@ -208661,8 +208819,8 @@ var FileSpecStore = class {
208661
208819
  throw new Error(PR_DECISIONS_FILE_ERROR);
208662
208820
  }
208663
208821
  const file = specPath(ref.repoKey, artifact);
208664
- if (fs63.existsSync(file))
208665
- fs63.rmSync(file);
208822
+ if (fs64.existsSync(file))
208823
+ fs64.rmSync(file);
208666
208824
  }
208667
208825
  // The file impl is single-document-per-repo, so "latest" === read the file.
208668
208826
  async loadLatest(repoKey, artifact) {
@@ -208689,7 +208847,7 @@ var loadLatestSpec = (repoKey, artifact) => active9.loadLatest(repoKey, artifact
208689
208847
  var specsMaterializeInPlace = () => active9.materializesInPlace;
208690
208848
 
208691
208849
  // packages/core/dist/lib/inferred-action-store.js
208692
- import fs64 from "node:fs";
208850
+ import fs65 from "node:fs";
208693
208851
  import path71 from "node:path";
208694
208852
  function actionsFile(repoKey) {
208695
208853
  return path71.join(repoKey, ".truecourse", "specs", "inferred-actions.json");
@@ -208697,15 +208855,15 @@ function actionsFile(repoKey) {
208697
208855
  var FileInferredActionStore = class {
208698
208856
  read(repoKey) {
208699
208857
  try {
208700
- return JSON.parse(fs64.readFileSync(actionsFile(repoKey), "utf-8"));
208858
+ return JSON.parse(fs65.readFileSync(actionsFile(repoKey), "utf-8"));
208701
208859
  } catch {
208702
208860
  return [];
208703
208861
  }
208704
208862
  }
208705
208863
  write(repoKey, actions) {
208706
208864
  const file = actionsFile(repoKey);
208707
- fs64.mkdirSync(path71.dirname(file), { recursive: true });
208708
- fs64.writeFileSync(file, JSON.stringify(actions, null, 2) + "\n", "utf-8");
208865
+ fs65.mkdirSync(path71.dirname(file), { recursive: true });
208866
+ fs65.writeFileSync(file, JSON.stringify(actions, null, 2) + "\n", "utf-8");
208709
208867
  }
208710
208868
  async setAction(repoKey, action) {
208711
208869
  const next = this.read(repoKey).filter((a) => !(a.kind === action.kind && a.identity === action.identity));
@@ -208840,7 +208998,7 @@ function generatedMarkerPath(repoRoot5) {
208840
208998
  }
208841
208999
  function stampGeneratedMarker(repoRoot5, summary) {
208842
209000
  const file = generatedMarkerPath(repoRoot5);
208843
- fs65.mkdirSync(path72.dirname(file), { recursive: true });
209001
+ fs66.mkdirSync(path72.dirname(file), { recursive: true });
208844
209002
  const body = {
208845
209003
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
208846
209004
  written: summary?.written ?? 0,
@@ -208848,7 +209006,7 @@ function stampGeneratedMarker(repoRoot5, summary) {
208848
209006
  validationIssues: summary?.validationIssues ?? [],
208849
209007
  enumerateFailures: summary?.enumerateFailures ?? []
208850
209008
  };
208851
- fs65.writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
209009
+ fs66.writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
208852
209010
  }
208853
209011
  function resolveCurateModels(repoRoot5) {
208854
209012
  return {
@@ -208973,19 +209131,19 @@ async function curateInProcess(repoRoot5, options = {}) {
208973
209131
  }
208974
209132
  function buildPriorContracts(repoRoot5) {
208975
209133
  const dir = path72.join(repoRoot5, ".truecourse", "contracts");
208976
- if (!fs65.existsSync(dir))
209134
+ if (!fs66.existsSync(dir))
208977
209135
  return void 0;
208978
209136
  const targets = [];
208979
209137
  const bodyByKey = /* @__PURE__ */ new Map();
208980
209138
  const walk20 = (d3) => {
208981
- for (const e of fs65.readdirSync(d3, { withFileTypes: true })) {
209139
+ for (const e of fs66.readdirSync(d3, { withFileTypes: true })) {
208982
209140
  if (e.isDirectory()) {
208983
209141
  if (e.name === "_inferred")
208984
209142
  continue;
208985
209143
  walk20(path72.join(d3, e.name));
208986
209144
  } else if (e.name.endsWith(".tc")) {
208987
209145
  const abs = path72.join(d3, e.name);
208988
- const src = fs65.readFileSync(abs, "utf-8");
209146
+ const src = fs66.readFileSync(abs, "utf-8");
208989
209147
  try {
208990
209148
  const file = parser_ohm_exports.parseTcFile(path72.relative(dir, abs), src);
208991
209149
  for (const key4 of resolver_exports.resolve([file]).index.keys()) {
@@ -209158,7 +209316,7 @@ async function withContracts(repoRoot5, options, tracker, fn) {
209158
209316
  try {
209159
209317
  let recorded;
209160
209318
  if (options.contractsDir) {
209161
- if (!fs65.existsSync(options.contractsDir)) {
209319
+ if (!fs66.existsSync(options.contractsDir)) {
209162
209320
  const err = new Error(`Contracts directory not found at ${options.contractsDir}. Run \`truecourse contracts generate\` first.`);
209163
209321
  tracker?.error("load", err.message);
209164
209322
  throw err;
@@ -209247,7 +209405,7 @@ async function verifyInProcess(repoRoot5, options = {}) {
209247
209405
  bySeverity: summary.bySeverity
209248
209406
  });
209249
209407
  await deleteVerifyDiff(storeKey);
209250
- fs65.rmSync(legacyVerifyStatePath(repoRoot5), { force: true });
209408
+ fs66.rmSync(legacyVerifyStatePath(repoRoot5), { force: true });
209251
209409
  }
209252
209410
  const state = {
209253
209411
  verifiedAt,
@@ -209465,7 +209623,7 @@ async function persistInferred(repoRoot5, options, contractsDir, codeDir, starte
209465
209623
  }
209466
209624
  function autodetectCodeDir(repoRoot5) {
209467
209625
  const codeSubdir = path72.join(repoRoot5, "code");
209468
- if (fs65.existsSync(codeSubdir) && fs65.statSync(codeSubdir).isDirectory()) {
209626
+ if (fs66.existsSync(codeSubdir) && fs66.statSync(codeSubdir).isDirectory()) {
209469
209627
  return codeSubdir;
209470
209628
  }
209471
209629
  return repoRoot5;
@@ -209786,7 +209944,7 @@ async function runContractsGenerate(options = {}) {
209786
209944
  async function runContractsList(options = {}) {
209787
209945
  const repoRoot5 = options.cwd ?? process.cwd();
209788
209946
  const contractsDir = path73.join(repoRoot5, ".truecourse", "contracts");
209789
- if (!fs66.existsSync(contractsDir)) {
209947
+ if (!fs67.existsSync(contractsDir)) {
209790
209948
  O2.info("No contracts found. Run `truecourse contracts generate` first.");
209791
209949
  return;
209792
209950
  }
@@ -209794,12 +209952,12 @@ async function runContractsList(options = {}) {
209794
209952
  const fileNodes = [];
209795
209953
  let parseErrors = 0;
209796
209954
  const visit = (dir) => {
209797
- for (const entry of fs66.readdirSync(dir, { withFileTypes: true })) {
209955
+ for (const entry of fs67.readdirSync(dir, { withFileTypes: true })) {
209798
209956
  const full = path73.join(dir, entry.name);
209799
209957
  if (entry.isDirectory()) visit(full);
209800
209958
  else if (entry.isFile() && entry.name.endsWith(".tc")) {
209801
209959
  try {
209802
- fileNodes.push(parserOhm.parseTcFile(full, fs66.readFileSync(full, "utf-8")));
209960
+ fileNodes.push(parserOhm.parseTcFile(full, fs67.readFileSync(full, "utf-8")));
209803
209961
  } catch {
209804
209962
  parseErrors += 1;
209805
209963
  }
@@ -209839,7 +209997,7 @@ async function runContractsList(options = {}) {
209839
209997
  async function runContractsValidate(options = {}) {
209840
209998
  const repoRoot5 = options.cwd ?? process.cwd();
209841
209999
  const contractsDir = path73.join(repoRoot5, ".truecourse", "contracts");
209842
- if (!fs66.existsSync(contractsDir)) {
210000
+ if (!fs67.existsSync(contractsDir)) {
209843
210001
  O2.error("No .truecourse/contracts/ directory found.");
209844
210002
  process.exit(1);
209845
210003
  }
@@ -209847,12 +210005,12 @@ async function runContractsValidate(options = {}) {
209847
210005
  const fileNodes = [];
209848
210006
  const issues = [];
209849
210007
  const visit = (dir) => {
209850
- for (const entry of fs66.readdirSync(dir, { withFileTypes: true })) {
210008
+ for (const entry of fs67.readdirSync(dir, { withFileTypes: true })) {
209851
210009
  const full = path73.join(dir, entry.name);
209852
210010
  if (entry.isDirectory()) visit(full);
209853
210011
  else if (entry.isFile() && entry.name.endsWith(".tc")) {
209854
210012
  try {
209855
- fileNodes.push(parserOhm.parseTcFile(full, fs66.readFileSync(full, "utf-8")));
210013
+ fileNodes.push(parserOhm.parseTcFile(full, fs67.readFileSync(full, "utf-8")));
209856
210014
  } catch (e) {
209857
210015
  issues.push(`${path73.relative(repoRoot5, full)}: parse error: ${e instanceof Error ? e.message : e}`);
209858
210016
  }
@@ -210095,7 +210253,7 @@ async function runInfer(opts = {}) {
210095
210253
 
210096
210254
  // tools/cli/src/commands/spec-conflicts.ts
210097
210255
  init_dist4();
210098
- import fs67 from "node:fs";
210256
+ import fs68 from "node:fs";
210099
210257
  import path75 from "node:path";
210100
210258
  var root = (opts) => opts.cwd ?? process.cwd();
210101
210259
  var base = (ref) => ref.split("/").pop() ?? ref;
@@ -210143,7 +210301,7 @@ async function runSpecConflictsList(opts = {}) {
210143
210301
  function excerpt(repoRoot5, ref, note, max = 20) {
210144
210302
  let text4;
210145
210303
  try {
210146
- text4 = fs67.readFileSync(path75.join(repoRoot5, ref), "utf-8");
210304
+ text4 = fs68.readFileSync(path75.join(repoRoot5, ref), "utf-8");
210147
210305
  } catch {
210148
210306
  return ` (could not read ${ref})`;
210149
210307
  }
@@ -210855,6 +211013,11 @@ function printGuardGenerateSummary(report4, reportPath) {
210855
211013
  O2.step(`sections ${report4.sectionsChanged} changed \xB7 ${settled} settled \xB7 ${unsettled.size} unsettled \xB7 ${report4.skippedUnchanged} unchanged`);
210856
211014
  const birth = g.birthPassed !== null ? ` \xB7 ${g.birthPassed} passed birth` : "";
210857
211015
  O2.step(`scenarios ${g.written} written${birth}`);
211016
+ if (g.readyButHeld > 0) {
211017
+ O2.step(
211018
+ `held ${g.readyButHeld} ready but held (${g.heldByFindings} finding${g.heldByFindings === 1 ? "" : "s"} \xB7 ${g.heldByErrors} error${g.heldByErrors === 1 ? "" : "s"})`
211019
+ );
211020
+ }
210858
211021
  const gapTotal = Object.values(g.coverageGapsByKind).reduce((a, b) => a + b, 0);
210859
211022
  if (gapTotal > 0) {
210860
211023
  const kinds = Object.entries(g.coverageGapsByKind).filter(([, n]) => n > 0).map(([k, n]) => k === "blocked-on" ? `${n} blocked-on${blockedOnBreakdown(g.blockedOnCapabilities)}` : `${n} ${k}`);
@@ -210866,6 +211029,10 @@ function printGuardGenerateSummary(report4, reportPath) {
210866
211029
  if (report4.extractionFailures.length > 0) {
210867
211030
  O2.step(`extraction ${report4.extractionFailures.length} document${report4.extractionFailures.length === 1 ? "" : "s"} failed \u2014 re-run to retry`);
210868
211031
  }
211032
+ if (report4.orphanedDismissals && report4.orphanedDismissals.length > 0) {
211033
+ const n = report4.orphanedDismissals.length;
211034
+ O2.step(`dismissals ${n} orphaned \u2014 the dismissed claim no longer exists; re-dismiss the new text or drop it from decisions.json`);
211035
+ }
210869
211036
  if (g.birthFindings > 0) O2.step(`findings ${g.birthFindings} birth finding${g.birthFindings === 1 ? "" : "s"}`);
210870
211037
  if (g.errors > 0) O2.step(`errors ${g.errors} authoring error${g.errors === 1 ? "" : "s"}`);
210871
211038
  if (g.usage) O2.step(`cost ${g.usage.calls} call${g.usage.calls === 1 ? "" : "s"} \xB7 $${g.usage.costUsd.toFixed(2)}`);
@@ -210945,6 +211112,7 @@ async function runGuardStatus(opts = {}) {
210945
211112
  const kinds = Object.entries(g.coverageGapsByKind).filter(([, n]) => n > 0).map(([k, n]) => k === "blocked-on" ? `${n} blocked-on${blockedOnBreakdown(g.blockedOnCapabilities)}` : `${n} ${k}`);
210946
211113
  detail.push(`${gapTotal} gap${gapTotal === 1 ? "" : "s"} (${kinds.join(", ")})`);
210947
211114
  }
211115
+ if (g.readyButHeld > 0) detail.push(`${g.readyButHeld} ready but held`);
210948
211116
  if (g.birthFindings > 0) detail.push(`${g.birthFindings} birth finding${g.birthFindings === 1 ? "" : "s"}`);
210949
211117
  if (g.errors > 0) detail.push(`${g.errors} error${g.errors === 1 ? "" : "s"}`);
210950
211118
  if (detail.length > 0) O2.message(` ${detail.join(" \xB7 ")}`);
@@ -211081,7 +211249,7 @@ async function runConfigLlmShow(options = {}) {
211081
211249
 
211082
211250
  // tools/cli/src/commands/hooks.ts
211083
211251
  import { execSync as execSync4 } from "node:child_process";
211084
- import fs68 from "node:fs";
211252
+ import fs69 from "node:fs";
211085
211253
  import path79 from "node:path";
211086
211254
  init_dist4();
211087
211255
  init_paths();
@@ -211117,11 +211285,11 @@ function findGitDir(from) {
211117
211285
  let dir = from;
211118
211286
  while (true) {
211119
211287
  const gitPath = path79.join(dir, ".git");
211120
- if (fs68.existsSync(gitPath)) {
211121
- const stat = fs68.statSync(gitPath);
211288
+ if (fs69.existsSync(gitPath)) {
211289
+ const stat = fs69.statSync(gitPath);
211122
211290
  if (stat.isDirectory()) return gitPath;
211123
211291
  if (stat.isFile()) {
211124
- const content = fs68.readFileSync(gitPath, "utf-8").trim();
211292
+ const content = fs69.readFileSync(gitPath, "utf-8").trim();
211125
211293
  const match4 = content.match(/^gitdir:\s*(.+)$/);
211126
211294
  if (match4) return path79.resolve(dir, match4[1]);
211127
211295
  }
@@ -211134,7 +211302,7 @@ function findGitDir(from) {
211134
211302
  function findProjectRoot(from) {
211135
211303
  let dir = from;
211136
211304
  while (true) {
211137
- if (fs68.existsSync(path79.join(dir, ".git"))) return dir;
211305
+ if (fs69.existsSync(path79.join(dir, ".git"))) return dir;
211138
211306
  const parent = path79.dirname(dir);
211139
211307
  if (parent === dir) return null;
211140
211308
  dir = parent;
@@ -211142,10 +211310,10 @@ function findProjectRoot(from) {
211142
211310
  }
211143
211311
  function loadConfig(projectRoot) {
211144
211312
  const configPath = path79.join(projectRoot, ".truecourse", "hooks.yaml");
211145
- if (!fs68.existsSync(configPath)) return null;
211313
+ if (!fs69.existsSync(configPath)) return null;
211146
211314
  let parsed;
211147
211315
  try {
211148
- const raw = fs68.readFileSync(configPath, "utf-8");
211316
+ const raw = fs69.readFileSync(configPath, "utf-8");
211149
211317
  parsed = jsYaml.load(raw) || {};
211150
211318
  } catch (err) {
211151
211319
  console.error(`Error parsing ${configPath}: ${err.message}`);
@@ -211197,10 +211365,10 @@ async function runHooksInstall() {
211197
211365
  console.log(INSTALL_WARNING);
211198
211366
  }
211199
211367
  const hooksDir = path79.join(gitDir, "hooks");
211200
- fs68.mkdirSync(hooksDir, { recursive: true });
211368
+ fs69.mkdirSync(hooksDir, { recursive: true });
211201
211369
  const hookPath = path79.join(hooksDir, "pre-commit");
211202
- if (fs68.existsSync(hookPath)) {
211203
- const existing = fs68.readFileSync(hookPath, "utf-8");
211370
+ if (fs69.existsSync(hookPath)) {
211371
+ const existing = fs69.readFileSync(hookPath, "utf-8");
211204
211372
  if (!existing.includes(HOOK_IDENTIFIER)) {
211205
211373
  console.error(
211206
211374
  "Error: A pre-commit hook already exists and was not installed by TrueCourse."
@@ -211210,16 +211378,16 @@ async function runHooksInstall() {
211210
211378
  process.exit(1);
211211
211379
  }
211212
211380
  }
211213
- fs68.writeFileSync(hookPath, HOOK_SCRIPT, { mode: 493 });
211381
+ fs69.writeFileSync(hookPath, HOOK_SCRIPT, { mode: 493 });
211214
211382
  console.log("TrueCourse pre-commit hook installed.");
211215
211383
  console.log(` ${hookPath}`);
211216
211384
  const projectRoot = findProjectRoot(process.cwd());
211217
211385
  if (projectRoot) {
211218
211386
  const cfgDir = path79.join(projectRoot, ".truecourse");
211219
211387
  const cfgPath = path79.join(cfgDir, "hooks.yaml");
211220
- if (!fs68.existsSync(cfgPath)) {
211221
- fs68.mkdirSync(cfgDir, { recursive: true });
211222
- fs68.writeFileSync(cfgPath, HOOKS_YAML_TEMPLATE);
211388
+ if (!fs69.existsSync(cfgPath)) {
211389
+ fs69.mkdirSync(cfgDir, { recursive: true });
211390
+ fs69.writeFileSync(cfgPath, HOOKS_YAML_TEMPLATE);
211223
211391
  console.log(` ${cfgPath} (starter config \u2014 edit to customize, commit to share with the team)`);
211224
211392
  }
211225
211393
  }
@@ -211231,16 +211399,16 @@ function runHooksUninstall() {
211231
211399
  process.exit(1);
211232
211400
  }
211233
211401
  const hookPath = path79.join(gitDir, "hooks", "pre-commit");
211234
- if (!fs68.existsSync(hookPath)) {
211402
+ if (!fs69.existsSync(hookPath)) {
211235
211403
  console.log("No pre-commit hook installed.");
211236
211404
  return;
211237
211405
  }
211238
- const content = fs68.readFileSync(hookPath, "utf-8");
211406
+ const content = fs69.readFileSync(hookPath, "utf-8");
211239
211407
  if (!content.includes(HOOK_IDENTIFIER)) {
211240
211408
  console.error("Error: The pre-commit hook was not installed by TrueCourse. Leaving it in place.");
211241
211409
  process.exit(1);
211242
211410
  }
211243
- fs68.unlinkSync(hookPath);
211411
+ fs69.unlinkSync(hookPath);
211244
211412
  console.log("TrueCourse pre-commit hook removed.");
211245
211413
  }
211246
211414
  function runHooksStatus() {
@@ -211250,7 +211418,7 @@ function runHooksStatus() {
211250
211418
  process.exit(1);
211251
211419
  }
211252
211420
  const hookPath = path79.join(gitDir, "hooks", "pre-commit");
211253
- const installed = fs68.existsSync(hookPath) && fs68.readFileSync(hookPath, "utf-8").includes(HOOK_IDENTIFIER);
211421
+ const installed = fs69.existsSync(hookPath) && fs69.readFileSync(hookPath, "utf-8").includes(HOOK_IDENTIFIER);
211254
211422
  if (installed) {
211255
211423
  console.log("TrueCourse pre-commit hook: installed");
211256
211424
  console.log(` ${hookPath}`);
@@ -211359,7 +211527,7 @@ async function runHooksRun() {
211359
211527
 
211360
211528
  // tools/cli/src/index.ts
211361
211529
  var program2 = new Command();
211362
- program2.name("truecourse").version("0.7.0-next.3").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
211530
+ program2.name("truecourse").version("0.7.0-next.4").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
211363
211531
  var dashboardCmd = program2.command("dashboard").description("Start the TrueCourse dashboard and open it in your browser").option("--reconfigure", "Re-prompt for console vs background service mode").option("--service", "Run as a background service (skips mode prompt)").option("--console", "Run in this terminal (skips mode prompt)").action(async (options) => {
211364
211532
  if (options.service && options.console) {
211365
211533
  console.error("error: --service and --console are mutually exclusive");