truecourse 0.7.0-next.0 → 0.7.0-next.2

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
@@ -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, GuardExtractionFailureSchema, GuardOrphanedSectionSchema, GuardRecipeReportSchema, GuardGenerateUsageSchema, GuardGenerateReportSchema;
105221
+ var GuardWrittenScenarioSchema, GuardCoverageGapKindSchema, GuardCoverageGapSchema, GuardBirthFindingSchema, GuardGenerateErrorSchema, GuardEntryPreflightSchema, GuardExtractionFailureSchema, GuardOrphanedSectionSchema, GuardRecipeReportSchema, GuardGenerateUsageSchema, GuardGenerateReportSchema;
105222
105222
  var init_report = __esm({
105223
105223
  "packages/shared/dist/guard/report.js"() {
105224
105224
  "use strict";
@@ -105271,6 +105271,14 @@ var init_report = __esm({
105271
105271
  anchor: external_exports.string(),
105272
105272
  message: external_exports.string()
105273
105273
  }).strict();
105274
+ GuardEntryPreflightSchema = external_exports.object({
105275
+ /** Display form of the entry argv, e.g. `node tools/cli/dist/index.js`. */
105276
+ entry: external_exports.string(),
105277
+ /** The recipe build command, for the rebuild hint. */
105278
+ buildCommand: external_exports.string(),
105279
+ /** The full, UNTRUNCATED startup output the dead entry produced. */
105280
+ stderr: external_exports.string()
105281
+ }).strict();
105274
105282
  GuardExtractionFailureSchema = external_exports.object({
105275
105283
  doc: external_exports.string(),
105276
105284
  reason: external_exports.string()
@@ -105316,7 +105324,13 @@ var init_report = __esm({
105316
105324
  */
105317
105325
  birthPassed: external_exports.number().int().nonnegative().optional(),
105318
105326
  manifestPath: external_exports.string().optional(),
105319
- usage: GuardGenerateUsageSchema.optional()
105327
+ usage: GuardGenerateUsageSchema.optional(),
105328
+ /**
105329
+ * Present ONLY when the built entry failed to start — the whole birth phase was
105330
+ * short-circuited, so every changed section stayed unsettled. Optional so older
105331
+ * reports (written before this field existed) keep parsing.
105332
+ */
105333
+ entryPreflight: GuardEntryPreflightSchema.optional()
105320
105334
  }).strict();
105321
105335
  }
105322
105336
  });
@@ -170270,7 +170284,7 @@ function readToolVersion() {
170270
170284
  if (cachedVersion)
170271
170285
  return cachedVersion;
170272
170286
  if (true) {
170273
- cachedVersion = "0.7.0-next.0";
170287
+ cachedVersion = "0.7.0-next.2";
170274
170288
  return cachedVersion;
170275
170289
  }
170276
170290
  try {
@@ -196272,8 +196286,14 @@ var CorpusDocSchema = external_exports.object({
196272
196286
  var OverlapSectionSchema = external_exports.object({
196273
196287
  /** The doc this section lives in, by ref (one of the overlap's two docs). */
196274
196288
  doc: DocRefSchema,
196275
- /** The heading text of the conflicting section (verbatim from the doc). */
196276
- heading: external_exports.string()
196289
+ /**
196290
+ * The heading text of the conflicting section (verbatim from the doc), or
196291
+ * `null` when the conflicting passage sits in the doc's PREAMBLE — the block
196292
+ * before its first heading (README badges/tagline, intro line). A plain string
196293
+ * from older corpora still parses; `null` is the preamble marker the viewer
196294
+ * bands as the pre-first-heading block.
196295
+ */
196296
+ heading: external_exports.string().nullable()
196277
196297
  });
196278
196298
  var OverlapSchema = external_exports.object({
196279
196299
  /** The two docs that overlap, by ref. */
@@ -197210,7 +197230,9 @@ NOT a disagreement (do NOT flag):
197210
197230
 
197211
197231
  Bias: when there is a PLAUSIBLE contradiction a human should check, flag it. When the docs are clearly complementary or agree, do not.
197212
197232
 
197213
- When you flag an overlap, also identify the SECTIONS that conflict: the nearest markdown heading (the \`#\`/\`##\`/\`###\` title, verbatim, WITHOUT the leading #s) above the conflicting text in EACH doc. List one entry per side; omit a side only if it genuinely has no heading.
197233
+ When you flag an overlap, also identify the SECTIONS that conflict: the nearest markdown heading (the \`#\`/\`##\`/\`###\` title, verbatim, WITHOUT the leading #s) above the conflicting text in EACH doc. List one entry per side; omit a side only when it genuinely has no conflicting passage.
197234
+
197235
+ PREAMBLE: when a doc's conflicting passage sits ABOVE its first \`#\` heading \u2014 the preamble block (a README's badges, tagline, or the intro line before any heading) \u2014 there is no heading to name. Set that side's \`heading\` to the JSON literal \`null\` (not the string "null", not ""). Use \`null\` ONLY for that pre-first-heading block; whenever the passage is under a heading, name the heading verbatim.
197214
197236
 
197215
197237
  In the NOTE, refer to each doc by its FILENAME (the basename shown in the header, e.g. \`users.md\`) \u2014 NEVER "doc A" / "doc B", which mean nothing to the reader.
197216
197238
 
@@ -197220,6 +197242,12 @@ Output ONLY a JSON object, no prose, no code fences:
197220
197242
  "note": "users.md uses auth0_id; identity.md uses auth0_sub for the same user column",
197221
197243
  "sections": [ { "side": "A", "heading": "User model" }, { "side": "B", "heading": "Identity" } ] }
197222
197244
 
197245
+ Preamble example \u2014 doc A's claim is a README badge/tagline above any heading, so its side uses null:
197246
+
197247
+ { "overlap": true,
197248
+ "note": "README.md lists C# as a supported language in the preamble; plan.md's Tech Stack omits it",
197249
+ "sections": [ { "side": "A", "heading": null }, { "side": "B", "heading": "Tech Stack" } ] }
197250
+
197223
197251
  Use { "overlap": false, "note": "", "sections": [] } when they are complementary or agree. The note is shown to the user \u2014 name the specific thing that differs.`;
197224
197252
  var OVERLAP_PREVIEW_LINES = 120;
197225
197253
  function docBody2(doc) {
@@ -197271,12 +197299,12 @@ function buildOverlapUserPrompt(areaId, a, b) {
197271
197299
  var LlmOverlapSchema = external_exports.object({
197272
197300
  overlap: external_exports.boolean(),
197273
197301
  note: external_exports.string().default(""),
197274
- sections: external_exports.array(external_exports.object({ side: external_exports.enum(["A", "B"]), heading: external_exports.string() })).default([])
197302
+ sections: external_exports.array(external_exports.object({ side: external_exports.enum(["A", "B"]), heading: external_exports.string().nullable() })).default([])
197275
197303
  });
197276
197304
  var OverlapVerdictSchema = external_exports.object({
197277
197305
  overlap: external_exports.boolean(),
197278
197306
  note: external_exports.string().default(""),
197279
- sections: external_exports.array(external_exports.object({ doc: external_exports.string(), heading: external_exports.string() })).default([])
197307
+ sections: external_exports.array(external_exports.object({ doc: external_exports.string(), heading: external_exports.string().nullable() })).default([])
197280
197308
  });
197281
197309
  function spawnOverlapRunner(opts = {}) {
197282
197310
  const transport = opts.transport ?? cliTransport({ bin: opts.bin });
@@ -197466,35 +197494,49 @@ function prefilterDocs(docs, manualIncludes = []) {
197466
197494
  skipped: [...reasons].map(([path79, reason]) => ({ path: path79, reason }))
197467
197495
  };
197468
197496
  }
197497
+ async function planRelevanceWork(repoRoot5, docs, manualIncludes = []) {
197498
+ const manualSet = new Set(manualIncludes);
197499
+ const { toClassify, skipped } = prefilterDocs(docs, manualIncludes);
197500
+ const needsCall = [];
197501
+ const known = /* @__PURE__ */ new Map();
197502
+ await Promise.all(toClassify.map(async (doc) => {
197503
+ if (manualSet.has(doc.path)) {
197504
+ known.set(doc.path, { path: doc.path, include: true, reason: "manual include" });
197505
+ return;
197506
+ }
197507
+ const cached = await readCache4(repoRoot5, computeCacheKey5(doc));
197508
+ if (cached)
197509
+ known.set(doc.path, cached);
197510
+ else
197511
+ needsCall.push(doc);
197512
+ }));
197513
+ return { toClassify, prefilterSkipped: skipped, needsCall, known };
197514
+ }
197469
197515
  async function filterByRelevance(repoRoot5, docs, opts = {}) {
197470
197516
  if (opts.enabled === false || docs.length === 0) {
197471
197517
  return { included: docs, skipped: [] };
197472
197518
  }
197473
- const manualSet = new Set(opts.manualIncludes ?? []);
197474
197519
  const runner = opts.runner ?? spawnRelevanceRunner({ transport: opts.transport, model: opts.model, fallbackModel: opts.fallbackModel });
197475
197520
  const concurrency = opts.concurrency ?? defaultConcurrency();
197476
- const { toClassify, skipped: prefilterSkipped } = prefilterDocs(docs, opts.manualIncludes ?? []);
197477
- const prefilterReason = new Map(prefilterSkipped.map((s) => [s.path, s.reason]));
197521
+ const plan = await planRelevanceWork(repoRoot5, docs, opts.manualIncludes ?? []);
197522
+ const prefilterReason = new Map(plan.prefilterSkipped.map((s) => [s.path, s.reason]));
197478
197523
  const total = docs.length;
197479
197524
  let done = 0;
197480
197525
  const markDone = () => opts.onProgress?.(++done, total);
197481
197526
  opts.onProgress?.(0, total);
197482
- for (let i = 0; i < prefilterReason.size; i++)
197527
+ const resolvedUpfront = prefilterReason.size + plan.known.size;
197528
+ for (let i = 0; i < resolvedUpfront; i++)
197483
197529
  markDone();
197484
- const verdicts = /* @__PURE__ */ new Map();
197530
+ const verdicts = new Map(plan.known);
197531
+ const pending = plan.needsCall;
197485
197532
  let cursor = 0;
197486
197533
  let active11 = 0;
197487
197534
  await new Promise((resolve12) => {
197535
+ if (pending.length === 0)
197536
+ return resolve12();
197488
197537
  const launch = () => {
197489
- while (active11 < concurrency && cursor < toClassify.length) {
197490
- const doc = toClassify[cursor++];
197491
- if (manualSet.has(doc.path)) {
197492
- verdicts.set(doc.path, { path: doc.path, include: true, reason: "manual include" });
197493
- markDone();
197494
- if (cursor >= toClassify.length && active11 === 0)
197495
- resolve12();
197496
- continue;
197497
- }
197538
+ while (active11 < concurrency && cursor < pending.length) {
197539
+ const doc = pending[cursor++];
197498
197540
  active11++;
197499
197541
  classifyOne(repoRoot5, doc, runner).then((verdict) => {
197500
197542
  verdicts.set(doc.path, verdict);
@@ -197507,14 +197549,12 @@ async function filterByRelevance(repoRoot5, docs, opts = {}) {
197507
197549
  }).finally(() => {
197508
197550
  markDone();
197509
197551
  active11--;
197510
- if (cursor >= toClassify.length && active11 === 0)
197552
+ if (cursor >= pending.length && active11 === 0)
197511
197553
  resolve12();
197512
197554
  else
197513
197555
  launch();
197514
197556
  });
197515
197557
  }
197516
- if (cursor >= toClassify.length && active11 === 0)
197517
- resolve12();
197518
197558
  };
197519
197559
  launch();
197520
197560
  });
@@ -197628,7 +197668,7 @@ async function classifyOne(repoRoot5, doc, runner) {
197628
197668
  await writeCache4(repoRoot5, cacheKey, verdict);
197629
197669
  return verdict;
197630
197670
  }
197631
- var RELEVANCE_SYSTEM_PROMPT = `You are a documentation relevance classifier. Classify by CONTENT, not by folder or filename: does this doc state durable, intended behavior or decisions about THE SYSTEM IN THIS REPOSITORY (its endpoints, data, auth, events, invariants, business rules, architecture)?
197671
+ var RELEVANCE_SYSTEM_PROMPT = `You are a documentation relevance classifier. Judge mainly by CONTENT \u2014 does this doc state durable, intended behavior or decisions about THE SYSTEM IN THIS REPOSITORY (its endpoints, data, auth, events, invariants, business rules, architecture)? The doc's PATH (given below) is evidence too, never an automatic verdict: weigh path and content together.
197632
197672
 
197633
197673
  ${OUTPUT_ONLY_GUARDRAIL}
197634
197674
 
@@ -197641,22 +197681,23 @@ INCLUDE (spec-source material):
197641
197681
  SKIP (not spec-source material):
197642
197682
  - Pure status / TODO checklists, kanban boards, release notes / changelog drafts
197643
197683
  - Docs about a THIRD-PARTY / external system (vendor API research, integration notes) \u2014 that is someone else's contract, not ours, and cannot be verified against this codebase
197684
+ - TEST-DATA specs: a doc under a test / fixture / sample / example tree (PATH segments like tests/, fixtures/, __fixtures__/, examples/, sample-*) that describes a FICTIONAL or sample product used as test data for THIS repo's own tooling \u2014 that product is not this repository's system, so its "spec" is not ours. (The path is evidence, not proof: if the doc plainly describes THIS repository's real product or engineering, keep it.)
197644
197685
  - SUPERSEDED docs \u2014 an older version of a newer doc covering the same subject
197645
197686
  - Process / meta docs not about product behavior: contribution / onboarding guides, code-style guides, deployment runbooks (keep a deployment doc ONLY if it states our runtime contracts)
197646
197687
  - Exploratory scratch with no committed decisions (brain dumps, open-questions-only notes)
197647
197688
  - AI-agent instructions / prompt templates; personal engineering journals
197648
197689
 
197649
- Distinguish "states a decision about our system" (INCLUDE) from "tracks status / describes an external system / is superseded / is process" (SKIP). The SKIP categories above are explicit \u2014 they are not "doubt." WHEN GENUINELY AMBIGUOUS: include (dropping a real spec doc costs more than keeping noise).
197690
+ Distinguish "states a decision about our system" (INCLUDE) from "tracks status / describes an external or test-data product / is superseded / is process" (SKIP). The SKIP categories above are explicit \u2014 they are not "doubt." WHEN GENUINELY AMBIGUOUS: include (dropping a real spec doc costs more than keeping noise).
197650
197691
 
197651
197692
  Output ONLY a JSON object:
197652
197693
 
197653
197694
  { "include": true|false, "reason": "short explanation" }
197654
197695
 
197655
- The reason is shown to the user in the dashboard \u2014 be specific ("vendor API research (ServiceTitan)", "superseded by capacity-ml-plan-v3", "deployment runbook, no product contracts") so they can verify the call.`;
197696
+ The reason is shown to the user in the dashboard \u2014 be specific ("vendor API research (ServiceTitan)", "superseded by capacity-ml-plan-v3", "deployment runbook, no product contracts", "test-data spec for a sample product (tests/fixtures/\u2026)") so they can verify the call.`;
197656
197697
  function buildRelevanceUserPrompt(doc) {
197657
197698
  const preview = doc.preview.split("\n").slice(0, 60).join("\n");
197658
197699
  return [
197659
- `Path: ${doc.path}`,
197700
+ `PATH (repo-relative): ${doc.path}`,
197660
197701
  `Detected kind: ${doc.kind}`,
197661
197702
  `Size: ${doc.size} bytes`,
197662
197703
  "",
@@ -197710,9 +197751,6 @@ async function readCache4(scope, cacheKey) {
197710
197751
  async function writeCache4(scope, cacheKey, verdict) {
197711
197752
  await setCacheEntry(scope, CACHE_NAME5, cacheKey, verdict);
197712
197753
  }
197713
- async function readRelevanceCache(repoRoot5, doc) {
197714
- return readCache4(repoRoot5, computeCacheKey5(doc));
197715
- }
197716
197754
 
197717
197755
  // packages/spec-consolidator/dist/curate.js
197718
197756
  async function curate(repoRoot5, opts = {}) {
@@ -201820,6 +201858,9 @@ var STAGE_DEFAULTS = {
201820
201858
  // Authoring an executable scenario faithful to a spec claim is the hard,
201821
201859
  // load-bearing call (a weak scenario is false confidence) — opus.
201822
201860
  "guard.generate": "opus",
201861
+ // The one evidence-retry per birth-failed claim — the same authoring task, so
201862
+ // the same tier; a distinct stage so retry spend is attributed to the birth line.
201863
+ "guard.retry": "opus",
201823
201864
  // Proposing a build/entry recipe is a modest structured task — sonnet.
201824
201865
  "guard.recipe": "sonnet",
201825
201866
  "rules.violationGen": "opus"
@@ -205110,6 +205151,129 @@ function listSandboxFiles(dir) {
205110
205151
  return out.sort();
205111
205152
  }
205112
205153
 
205154
+ // packages/guard-runner/dist/executor.js
205155
+ import { spawn as spawn9 } from "node:child_process";
205156
+ var DEFAULT_STEP_TIMEOUT_MS = 3e4;
205157
+ function executeStep(opts) {
205158
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
205159
+ const [command, ...args] = opts.argv;
205160
+ const start = Date.now();
205161
+ return new Promise((resolve12) => {
205162
+ const child = spawn9(command, args, {
205163
+ cwd: opts.cwd,
205164
+ env: opts.env,
205165
+ stdio: ["pipe", "pipe", "pipe"]
205166
+ });
205167
+ let stdout = "";
205168
+ let stderr = "";
205169
+ let timedOut = false;
205170
+ let settled = false;
205171
+ const timer = setTimeout(() => {
205172
+ timedOut = true;
205173
+ child.kill("SIGKILL");
205174
+ }, timeoutMs);
205175
+ const finish = (capture) => {
205176
+ if (settled)
205177
+ return;
205178
+ settled = true;
205179
+ clearTimeout(timer);
205180
+ resolve12(capture);
205181
+ };
205182
+ child.stdout.on("data", (chunk3) => {
205183
+ stdout += chunk3.toString("utf-8");
205184
+ });
205185
+ child.stderr.on("data", (chunk3) => {
205186
+ stderr += chunk3.toString("utf-8");
205187
+ });
205188
+ child.on("error", (err) => {
205189
+ finish({
205190
+ exitCode: null,
205191
+ signal: null,
205192
+ stdout,
205193
+ stderr,
205194
+ timedOut,
205195
+ spawnError: err.message,
205196
+ durationMs: Date.now() - start
205197
+ });
205198
+ });
205199
+ child.on("close", (code, signal) => {
205200
+ finish({
205201
+ exitCode: code,
205202
+ signal,
205203
+ stdout,
205204
+ stderr,
205205
+ timedOut,
205206
+ durationMs: Date.now() - start
205207
+ });
205208
+ });
205209
+ if (opts.stdin !== void 0)
205210
+ child.stdin.write(opts.stdin);
205211
+ child.stdin.end();
205212
+ });
205213
+ }
205214
+
205215
+ // packages/guard-runner/dist/preflight.js
205216
+ var ENTRY_PROBE_ARGVS = [[], ["--help"]];
205217
+ var ENTRY_PREFLIGHT_TIMEOUT_MS = 2e4;
205218
+ var defaultEntryProbeExecutor = async (fullArgv, recipeEnv, timeoutMs) => {
205219
+ const sandbox = createSandbox({ recipeEnv });
205220
+ try {
205221
+ return await executeStep({ argv: fullArgv, cwd: sandbox.cwd, env: sandbox.env, timeoutMs });
205222
+ } finally {
205223
+ sandbox.cleanup();
205224
+ }
205225
+ };
205226
+ async function preflightEntry(opts) {
205227
+ const exec3 = opts.exec ?? defaultEntryProbeExecutor;
205228
+ const timeoutMs = opts.timeoutMs ?? ENTRY_PREFLIGHT_TIMEOUT_MS;
205229
+ const entry = opts.displayEntry.join(" ");
205230
+ const probes = await Promise.all(ENTRY_PROBE_ARGVS.map(async (argv) => ({
205231
+ argv: [...argv],
205232
+ capture: await exec3([...opts.resolvedEntry, ...argv], opts.recipeEnv, timeoutMs)
205233
+ })));
205234
+ const ok = entryStarts(probes);
205235
+ return { ok, entry, stderr: ok ? "" : startupOutput(probes[0].capture, timeoutMs), probes };
205236
+ }
205237
+ function entryStarts(probes) {
205238
+ if (probes.some((p2) => startedCleanly(p2.capture)))
205239
+ return true;
205240
+ const shapes = probes.map((p2) => failureShape(p2.capture));
205241
+ return shapes.some((s) => s !== shapes[0]);
205242
+ }
205243
+ function startedCleanly(c2) {
205244
+ return !c2.spawnError && !c2.timedOut && c2.signal === null && c2.exitCode === 0;
205245
+ }
205246
+ function failureShape(c2) {
205247
+ return JSON.stringify({
205248
+ spawnError: c2.spawnError ?? null,
205249
+ signal: c2.signal ?? null,
205250
+ timedOut: c2.timedOut,
205251
+ exitCode: c2.exitCode,
205252
+ stdout: c2.stdout,
205253
+ stderr: c2.stderr
205254
+ });
205255
+ }
205256
+ function startupOutput(c2, timeoutMs) {
205257
+ const parts = [];
205258
+ const primary = c2.stderr || c2.stdout;
205259
+ if (primary)
205260
+ parts.push(primary.trimEnd());
205261
+ if (c2.spawnError)
205262
+ parts.push(`failed to spawn: ${c2.spawnError}`);
205263
+ if (c2.timedOut)
205264
+ parts.push(`(the entry produced no output within ${timeoutMs}ms and was killed)`);
205265
+ if (parts.length === 0) {
205266
+ parts.push(c2.exitCode !== null ? `(the entry exited ${c2.exitCode} with no output)` : `(the entry was killed by ${c2.signal})`);
205267
+ }
205268
+ return parts.join("\n");
205269
+ }
205270
+ function entryPreflightHeadline(entry, buildCommand) {
205271
+ return `The recipe entry \`${entry}\` failed to start \u2014 it crashes before it runs, so every scenario would fail identically. Rebuild it with \`${buildCommand}\` (its build output is likely stale or incomplete), then retry.`;
205272
+ }
205273
+ function formatEntryPreflightError(opts) {
205274
+ return [entryPreflightHeadline(opts.entry, opts.buildCommand), "", "Startup output:", opts.stderr].join("\n");
205275
+ }
205276
+
205113
205277
  // packages/guard-runner/dist/capabilities/git.js
205114
205278
  import { spawnSync as spawnSync2 } from "node:child_process";
205115
205279
  import fs51 from "node:fs";
@@ -205190,67 +205354,6 @@ function applyCapabilities(setup, ctx) {
205190
205354
  }
205191
205355
  }
205192
205356
 
205193
- // packages/guard-runner/dist/executor.js
205194
- import { spawn as spawn9 } from "node:child_process";
205195
- var DEFAULT_STEP_TIMEOUT_MS = 3e4;
205196
- function executeStep(opts) {
205197
- const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
205198
- const [command, ...args] = opts.argv;
205199
- const start = Date.now();
205200
- return new Promise((resolve12) => {
205201
- const child = spawn9(command, args, {
205202
- cwd: opts.cwd,
205203
- env: opts.env,
205204
- stdio: ["pipe", "pipe", "pipe"]
205205
- });
205206
- let stdout = "";
205207
- let stderr = "";
205208
- let timedOut = false;
205209
- let settled = false;
205210
- const timer = setTimeout(() => {
205211
- timedOut = true;
205212
- child.kill("SIGKILL");
205213
- }, timeoutMs);
205214
- const finish = (capture) => {
205215
- if (settled)
205216
- return;
205217
- settled = true;
205218
- clearTimeout(timer);
205219
- resolve12(capture);
205220
- };
205221
- child.stdout.on("data", (chunk3) => {
205222
- stdout += chunk3.toString("utf-8");
205223
- });
205224
- child.stderr.on("data", (chunk3) => {
205225
- stderr += chunk3.toString("utf-8");
205226
- });
205227
- child.on("error", (err) => {
205228
- finish({
205229
- exitCode: null,
205230
- signal: null,
205231
- stdout,
205232
- stderr,
205233
- timedOut,
205234
- spawnError: err.message,
205235
- durationMs: Date.now() - start
205236
- });
205237
- });
205238
- child.on("close", (code, signal) => {
205239
- finish({
205240
- exitCode: code,
205241
- signal,
205242
- stdout,
205243
- stderr,
205244
- timedOut,
205245
- durationMs: Date.now() - start
205246
- });
205247
- });
205248
- if (opts.stdin !== void 0)
205249
- child.stdin.write(opts.stdin);
205250
- child.stdin.end();
205251
- });
205252
- }
205253
-
205254
205357
  // packages/guard-runner/dist/normalizers.js
205255
205358
  var CANONICAL_ORDER = [
205256
205359
  "abs-paths",
@@ -205881,16 +205984,27 @@ async function runGuard(opts) {
205881
205984
  }));
205882
205985
  const executable = planned.filter((p2) => p2.resolution.kind === "match" || p2.resolution.kind === "remap");
205883
205986
  const nonExecutable = planned.filter((p2) => p2.resolution.kind === "stale" || p2.resolution.kind === "orphaned");
205884
- if (!opts.skipBuild && executable.length > 0) {
205987
+ const buildsOwnEntry = !opts.skipBuild && executable.length > 0;
205988
+ if (buildsOwnEntry) {
205885
205989
  opts.onPhase?.("build");
205886
205990
  const build8 = await runBuild(repoRoot5, loaded.recipe.build, loaded.recipe.env);
205887
205991
  if (!build8.ok)
205888
205992
  return { status: "build-failed", build: build8, loadErrors };
205889
205993
  }
205994
+ const resolvedEntry = resolveEntry(repoRoot5, loaded.recipe.entry);
205995
+ if (buildsOwnEntry) {
205996
+ const preflight = await preflightEntry({
205997
+ resolvedEntry,
205998
+ displayEntry: loaded.recipe.entry,
205999
+ recipeEnv: loaded.recipe.env
206000
+ });
206001
+ if (!preflight.ok) {
206002
+ return { status: "entry-preflight-failed", preflight, buildCommand: loaded.recipe.build, loadErrors };
206003
+ }
206004
+ }
205890
206005
  opts.onPhase?.("run", selected.length);
205891
206006
  const runId = buildRunId();
205892
206007
  const ranAt = (/* @__PURE__ */ new Date()).toISOString();
205893
- const resolvedEntry = resolveEntry(repoRoot5, loaded.recipe.entry);
205894
206008
  const stepTimeoutMs = opts.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
205895
206009
  const concurrency = opts.concurrency ?? defaultRunConcurrency();
205896
206010
  const results = [];
@@ -206435,11 +206549,11 @@ function spawnGenerateRunner2(opts = {}) {
206435
206549
  return async (ctx) => {
206436
206550
  const refs = ctx.claims.map((c2) => c2.ref).join(",");
206437
206551
  const isRetry = ctx.claims.some((c2) => c2.retry);
206438
- const suffix = `${isRetry ? ":retry" : ""}${ctx.correction ? ":correction" : ""}`;
206552
+ const stage = isRetry ? "guard.retry" : "guard.generate";
206439
206553
  const raw = await transport({
206440
- id: `guard.generate:${ctx.doc}:${refs}${suffix}`,
206441
- stage: "guard.generate",
206442
- model: opts.model,
206554
+ id: `${stage}:${ctx.doc}:${refs}${ctx.correction ? ":correction" : ""}`,
206555
+ stage,
206556
+ model: isRetry ? opts.retryModel ?? opts.model : opts.model,
206443
206557
  fallbackModel: opts.fallbackModel,
206444
206558
  system: GENERATE_SYSTEM_PROMPT,
206445
206559
  user: buildAuthorUserPrompt(ctx),
@@ -206751,12 +206865,15 @@ async function captureProbes(opts) {
206751
206865
  const cached = await getCacheEntry(opts.repoRoot, GROUND_CACHE_NAME, key4);
206752
206866
  if (cached) {
206753
206867
  const parsed = ProbeTranscriptSchema.safeParse(cached);
206754
- if (parsed.success)
206868
+ if (parsed.success) {
206869
+ opts.onProbeCaptured?.();
206755
206870
  return parsed.data;
206871
+ }
206756
206872
  }
206757
206873
  const capture = await exec3([...opts.resolvedEntry, ...argv], opts.recipeEnv);
206758
206874
  const transcript = toTranscript(argv, [...opts.displayEntry, ...argv], capture);
206759
206875
  await setCacheEntry(opts.repoRoot, GROUND_CACHE_NAME, key4, transcript);
206876
+ opts.onProbeCaptured?.();
206760
206877
  return transcript;
206761
206878
  }));
206762
206879
  }
@@ -207021,6 +207138,7 @@ function deleteScenarioFiles(repoRoot5, ids) {
207021
207138
 
207022
207139
  // packages/guard-generator/dist/generate.js
207023
207140
  var GENERATE_CACHE_NAME = "guard/generate";
207141
+ var ENTRY_PREFLIGHT_ANCHOR = "(entry preflight)";
207024
207142
  function defaultConcurrency3() {
207025
207143
  const env = process.env.TRUECOURSE_MAX_CONCURRENCY;
207026
207144
  if (env) {
@@ -207090,7 +207208,12 @@ async function generateGuards(options) {
207090
207208
  const limit = pLimit(Math.max(1, options.concurrency ?? defaultConcurrency3()));
207091
207209
  const batchSize = Math.max(1, options.batchSize ?? defaultGenerateBatch2());
207092
207210
  const extractRunner = options.extractRunner ?? spawnExtractRunner({ transport: options.transport, model: options.models?.extract, fallbackModel: options.models?.fallback });
207093
- const generateRunner = options.generateRunner ?? spawnGenerateRunner2({ transport: options.transport, model: options.models?.generate, fallbackModel: options.models?.fallback });
207211
+ const generateRunner = options.generateRunner ?? spawnGenerateRunner2({
207212
+ transport: options.transport,
207213
+ model: options.models?.generate,
207214
+ retryModel: options.models?.retry,
207215
+ fallbackModel: options.models?.fallback
207216
+ });
207094
207217
  const coverageGaps = [];
207095
207218
  const errors = [];
207096
207219
  const extractionFailures = [];
@@ -207161,11 +207284,15 @@ async function generateGuards(options) {
207161
207284
  return buildPromise;
207162
207285
  };
207163
207286
  let resolvedEntryMemo = null;
207287
+ let groundPlanned = 0;
207288
+ let groundCaptured = 0;
207164
207289
  const groundClaims = async (claimTexts) => {
207165
207290
  const probes = deriveProbes(claimTexts, recipe.entry);
207166
207291
  const build8 = await buildPromise;
207167
- if (!build8.ok)
207292
+ if (!build8.ok || probes.length === 0)
207168
207293
  return [];
207294
+ groundPlanned += probes.length;
207295
+ options.onGroundProgress?.(groundCaptured, groundPlanned);
207169
207296
  resolvedEntryMemo ??= resolveEntry(repoRoot5, recipe.entry);
207170
207297
  return captureProbes({
207171
207298
  repoRoot: repoRoot5,
@@ -207173,9 +207300,21 @@ async function generateGuards(options) {
207173
207300
  resolvedEntry: resolvedEntryMemo,
207174
207301
  displayEntry: recipe.entry,
207175
207302
  recipeFingerprint,
207176
- recipeEnv: recipe.env
207303
+ recipeEnv: recipe.env,
207304
+ onProbeCaptured: () => options.onGroundProgress?.(++groundCaptured, groundPlanned)
207177
207305
  });
207178
207306
  };
207307
+ let entryPreflightMemo = null;
207308
+ const preflightEntryOnce = () => {
207309
+ entryPreflightMemo ??= (async () => {
207310
+ const build8 = await buildPromise;
207311
+ if (!build8.ok)
207312
+ return null;
207313
+ resolvedEntryMemo ??= resolveEntry(repoRoot5, recipe.entry);
207314
+ return preflightEntry({ resolvedEntry: resolvedEntryMemo, displayEntry: recipe.entry, recipeEnv: recipe.env });
207315
+ })();
207316
+ return entryPreflightMemo;
207317
+ };
207179
207318
  const priorSections = readPriorManifest(repoRoot5);
207180
207319
  const manifestByKey = new Map(priorSections.map((e) => [`${e.doc}\0${e.anchor}`, e]));
207181
207320
  const usedIds = existingScenarioIds(repoRoot5);
@@ -207199,9 +207338,11 @@ async function generateGuards(options) {
207199
207338
  });
207200
207339
  settledKeys.add(k);
207201
207340
  writeWorkingManifest();
207341
+ options.onSectionSettled?.(settledKeys.size, plan.work.length);
207202
207342
  };
207203
207343
  const written = [];
207204
207344
  const birthFindings = [];
207345
+ let entryPreflightFailure = null;
207205
207346
  let birthTotal = 0;
207206
207347
  let birthSettled = 0;
207207
207348
  let birthPassed = 0;
@@ -207248,6 +207389,20 @@ async function generateGuards(options) {
207248
207389
  return;
207249
207390
  settleChain = settleChain.then(() => settleCliSection(sectionByKey.get(k), refsBySection.get(k)));
207250
207391
  };
207392
+ const deadEntry = async () => {
207393
+ const preflight = await preflightEntryOnce();
207394
+ if (!preflight || preflight.ok)
207395
+ return false;
207396
+ if (!entryPreflightFailure) {
207397
+ entryPreflightFailure = { entry: preflight.entry, buildCommand: recipe.build, stderr: preflight.stderr };
207398
+ errors.push({
207399
+ doc: preflight.entry,
207400
+ anchor: ENTRY_PREFLIGHT_ANCHOR,
207401
+ message: formatEntryPreflightError(entryPreflightFailure)
207402
+ });
207403
+ }
207404
+ return true;
207405
+ };
207251
207406
  async function settleCliSection(section, refs) {
207252
207407
  const k = key2(section);
207253
207408
  for (const id of priorIdsOf(k))
@@ -207282,6 +207437,8 @@ async function generateGuards(options) {
207282
207437
  const message = `build failed (\`${build8.command}\`)${build8.timedOut ? " \u2014 timed out" : ""}`;
207283
207438
  for (const c2 of round1)
207284
207439
  localErrors.push(errorFrom({ candidate: c2, result: { failure: { actual: message } } }));
207440
+ } else if (await deadEntry()) {
207441
+ return;
207285
207442
  } else {
207286
207443
  birthTotal += round1.length;
207287
207444
  const r1 = await birthValidate(repoRoot5, round1, { skipBuild: true, onPhase: options.onBirthPhase, onScenarioSettled: bumpBirth });
@@ -207358,6 +207515,8 @@ async function generateGuards(options) {
207358
207515
  const authorTotal = authTasks.length;
207359
207516
  let authorDone = 0;
207360
207517
  const bumpAuthor = (n) => options.onAuthorProgress?.(authorDone += n, authorTotal);
207518
+ if (authorTotal > 0)
207519
+ options.onAuthorProgress?.(authorDone, authorTotal);
207361
207520
  const missTasks = [];
207362
207521
  for (const t2 of authTasks) {
207363
207522
  const cached = await readAuthorCache(repoRoot5, t2.claim, t2.section, recipeFingerprint);
@@ -207429,7 +207588,8 @@ async function generateGuards(options) {
207429
207588
  extractionFailures,
207430
207589
  orphaned,
207431
207590
  birthPassed,
207432
- manifestPath: manifestPath2(repoRoot5)
207591
+ manifestPath: manifestPath2(repoRoot5),
207592
+ ...entryPreflightFailure ? { entryPreflight: entryPreflightFailure } : {}
207433
207593
  };
207434
207594
  }
207435
207595
  var key2 = (s) => `${s.doc}\0${s.anchor}`;
@@ -207818,12 +207978,14 @@ function previewChars() {
207818
207978
  return 60 * 50;
207819
207979
  }
207820
207980
  async function estimateScanTokens(repoRoot5, prices) {
207981
+ const decisions = readCorpusDecisions(repoRoot5);
207982
+ const manualIncludes = decisions.manualIncludes ?? [];
207983
+ const manualExcludes = new Set(decisions.manualExcludes ?? []);
207821
207984
  const docs = discoverDocs(repoRoot5);
207822
- const { toClassify } = prefilterDocs(docs);
207823
- const nClassify = toClassify.length;
207824
- const relevance = await Promise.all(toClassify.map((d3) => readRelevanceCache(repoRoot5, d3)));
207825
- const relevanceMissDocs = toClassify.filter((_2, i) => relevance[i] === null);
207826
- const cachedKeptDocs = toClassify.filter((_2, i) => relevance[i]?.include === true);
207985
+ const plan = await planRelevanceWork(repoRoot5, docs, manualIncludes);
207986
+ const nClassify = plan.toClassify.length;
207987
+ const relevanceMissDocs = plan.needsCall;
207988
+ const cachedKeptDocs = plan.toClassify.filter((d3) => plan.known.get(d3.path)?.include === true && !manualExcludes.has(d3.path));
207827
207989
  const cachedKeptTagged = await Promise.all(cachedKeptDocs.map((d3) => isAreaTagCached(repoRoot5, d3)));
207828
207990
  const cachedKeptTagMisses = cachedKeptTagged.filter((cached) => !cached).length;
207829
207991
  const estChangedKept = Math.round(relevanceMissDocs.length * KEEP_RATE);
@@ -210089,12 +210251,18 @@ async function runSpecDocsSkipped(opts = {}) {
210089
210251
  "Force-include a skipped doc with `spec docs include <path>`; force-exclude a kept doc with `spec docs exclude <path>`."
210090
210252
  );
210091
210253
  }
210092
- async function runSpecDocsInclude(docPath, opts = {}) {
210254
+ async function runSpecDocsInclude(docPaths, opts = {}) {
210093
210255
  const root2 = repoRoot3(opts);
210094
- if (!docPath) return fail3("Missing doc path");
210095
- await addManualInclude(root2, docPath);
210256
+ const paths = docPaths.filter(Boolean);
210257
+ if (paths.length === 0) return fail3("Missing doc path");
210258
+ for (const docPath of paths) {
210259
+ await addManualInclude(root2, docPath);
210260
+ O2.step(`Force-included ${docPath}`);
210261
+ }
210096
210262
  await reScan2(root2);
210097
- gt(`Force-include ${docPath} \u2014 re-scanned. Review \`truecourse spec conflicts list\`.`);
210263
+ gt(
210264
+ paths.length === 1 ? `Force-include ${paths[0]} \u2014 re-scanned. Review \`truecourse spec conflicts list\`.` : `Force-included ${paths.length} docs \u2014 re-scanned once. Review \`truecourse spec conflicts list\`.`
210265
+ );
210098
210266
  }
210099
210267
  async function runSpecDocsUninclude(docPath, opts = {}) {
210100
210268
  const root2 = repoRoot3(opts);
@@ -210103,12 +210271,18 @@ async function runSpecDocsUninclude(docPath, opts = {}) {
210103
210271
  await reScan2(root2);
210104
210272
  gt(`Removed force-include for ${docPath} \u2014 re-scanned.`);
210105
210273
  }
210106
- async function runSpecDocsExclude(docPath, opts = {}) {
210274
+ async function runSpecDocsExclude(docPaths, opts = {}) {
210107
210275
  const root2 = repoRoot3(opts);
210108
- if (!docPath) return fail3("Missing doc path");
210109
- await addManualExclude(root2, docPath);
210276
+ const paths = docPaths.filter(Boolean);
210277
+ if (paths.length === 0) return fail3("Missing doc path");
210278
+ for (const docPath of paths) {
210279
+ await addManualExclude(root2, docPath);
210280
+ O2.step(`Force-excluded ${docPath}`);
210281
+ }
210110
210282
  await reScan2(root2);
210111
- gt(`Force-exclude ${docPath} \u2014 re-scanned. Review \`truecourse spec conflicts list\`.`);
210283
+ gt(
210284
+ paths.length === 1 ? `Force-exclude ${paths[0]} \u2014 re-scanned. Review \`truecourse spec conflicts list\`.` : `Force-excluded ${paths.length} docs \u2014 re-scanned once. Review \`truecourse spec conflicts list\`.`
210285
+ );
210112
210286
  }
210113
210287
  async function runSpecDocsUnexclude(docPath, opts = {}) {
210114
210288
  const root2 = repoRoot3(opts);
@@ -210213,12 +210387,14 @@ var GUARD_GENERATE_STEPS = [
210213
210387
  var GUARD_STEP_STAGES = {
210214
210388
  index: ["guard.recipe"],
210215
210389
  extract: ["guard.extract"],
210216
- author: ["guard.generate"]
210390
+ author: ["guard.generate"],
210391
+ validate: ["guard.retry"]
210217
210392
  };
210218
210393
  function resolveGuardModels(repoRoot5) {
210219
210394
  return {
210220
210395
  extract: resolveModel("guard.extract", void 0, repoRoot5),
210221
210396
  generate: resolveModel("guard.generate", void 0, repoRoot5),
210397
+ retry: resolveModel("guard.retry", void 0, repoRoot5),
210222
210398
  recipe: resolveModel("guard.recipe", void 0, repoRoot5),
210223
210399
  fallback: resolveFallbackModel(repoRoot5) ?? void 0
210224
210400
  };
@@ -210260,10 +210436,20 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210260
210436
  cur = ni;
210261
210437
  };
210262
210438
  const withUsage = (key4, base2) => `${base2}${stageUsageTag(GUARD_STEP_STAGES[key4] ?? [], repoRoot5)}`;
210439
+ let authorDone = 0;
210440
+ let authorTotal = 0;
210441
+ let authorFinished = false;
210442
+ let groundCaptured = 0;
210443
+ let groundPlanned = 0;
210444
+ const authorDetail = () => {
210445
+ const claims = `${authorDone}/${authorTotal} claim${authorTotal === 1 ? "" : "s"}`;
210446
+ const base2 = groundPlanned > 0 ? `grounding probes ${groundCaptured}/${groundPlanned} \xB7 authoring ${claims}` : claims;
210447
+ return withUsage("author", base2);
210448
+ };
210263
210449
  let building = false;
210264
- let birthSeen = false;
210265
210450
  let birthDone = 0;
210266
- let birthTotal = 0;
210451
+ let sectionsDone = 0;
210452
+ let sectionsTotal = 0;
210267
210453
  let retrySeen = false;
210268
210454
  let retryDone = 0;
210269
210455
  let retryTotal = 0;
@@ -210274,10 +210460,10 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210274
210460
  tracker?.start("validate");
210275
210461
  validateStarted = true;
210276
210462
  }
210277
- const parts = [building ? "building\u2026" : `birth ${birthDone}/${birthTotal}`];
210463
+ const parts = [`sections ${sectionsDone}/${sectionsTotal}`, building ? "building\u2026" : `birth ${birthDone}`];
210278
210464
  if (retrySeen)
210279
- parts.push(`retrying failed claims ${retryDone}/${retryTotal}`);
210280
- tracker?.detail("validate", parts.join(" \xB7 "));
210465
+ parts.push(`retrying ${retryDone}/${retryTotal}`);
210466
+ tracker?.detail("validate", withUsage("validate", parts.join(" \xB7 ")));
210281
210467
  };
210282
210468
  tracker?.start("index");
210283
210469
  try {
@@ -210289,6 +210475,7 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210289
210475
  generateRunner: options.generateRunner,
210290
210476
  recipeRunner: options.recipeRunner,
210291
210477
  onPlan: (total, work) => {
210478
+ sectionsTotal = work;
210292
210479
  tracker?.done("index", withUsage("index", `${work} of ${total} section${total === 1 ? "" : "s"} changed`));
210293
210480
  cur = STEPS.indexOf("extract");
210294
210481
  tracker?.start("extract", `0 views`);
@@ -210305,27 +210492,30 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210305
210492
  },
210306
210493
  onAuthorProgress: (done, total) => {
210307
210494
  advanceTo("author");
210308
- const detail = withUsage("author", `${done}/${total} claim${total === 1 ? "" : "s"}`);
210309
- if (done >= total)
210310
- tracker?.done("author", detail);
210311
- else
210312
- tracker?.detail("author", detail);
210313
- },
210314
- onBirthPhase: (phase, total) => {
210315
- if (phase === "build") {
210316
- building = true;
210495
+ authorDone = done;
210496
+ authorTotal = total;
210497
+ if (done >= total) {
210498
+ authorFinished = true;
210499
+ tracker?.done("author", withUsage("author", `${done}/${total} claim${total === 1 ? "" : "s"}`));
210317
210500
  } else {
210318
- building = false;
210319
- if (!birthSeen && total !== void 0)
210320
- birthTotal = total;
210501
+ tracker?.detail("author", authorDetail());
210321
210502
  }
210503
+ },
210504
+ onGroundProgress: (captured, planned) => {
210505
+ groundCaptured = captured;
210506
+ groundPlanned = planned;
210507
+ if (authorFinished)
210508
+ return;
210509
+ advanceTo("author");
210510
+ tracker?.detail("author", authorDetail());
210511
+ },
210512
+ onBirthPhase: (phase) => {
210513
+ building = phase === "build";
210322
210514
  renderValidate();
210323
210515
  },
210324
- onBirthProgress: (done, total) => {
210516
+ onBirthProgress: (done) => {
210325
210517
  building = false;
210326
- birthSeen = true;
210327
210518
  birthDone = done;
210328
- birthTotal = total;
210329
210519
  renderValidate();
210330
210520
  },
210331
210521
  onRetryProgress: (done, total) => {
@@ -210333,6 +210523,12 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210333
210523
  retryDone = done;
210334
210524
  retryTotal = total;
210335
210525
  renderValidate();
210526
+ },
210527
+ onSectionSettled: (settled, total) => {
210528
+ sectionsDone = settled;
210529
+ sectionsTotal = total;
210530
+ if (validateStarted)
210531
+ renderValidate();
210336
210532
  }
210337
210533
  });
210338
210534
  for (let i = cur; i < STEPS.length; i++)
@@ -210356,7 +210552,7 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210356
210552
  }
210357
210553
  }
210358
210554
  }
210359
- var GUARD_USAGE_STAGES = ["guard.recipe", "guard.extract", "guard.generate"];
210555
+ var GUARD_USAGE_STAGES = ["guard.recipe", "guard.extract", "guard.generate", "guard.retry"];
210360
210556
  function sumGuardUsage() {
210361
210557
  const usage = getStageUsage();
210362
210558
  let calls = 0;
@@ -210404,6 +210600,8 @@ async function guardRunInProcess(repoRoot5, options = {}) {
210404
210600
  tracker?.done("run", `${n} scenario${n === 1 ? "" : "s"}`);
210405
210601
  } else if (result.status === "build-failed") {
210406
210602
  tracker?.error("build", `Build failed (\`${result.build.command}\`)${result.build.timedOut ? " \u2014 timed out" : ""}`);
210603
+ } else if (result.status === "entry-preflight-failed") {
210604
+ tracker?.error("build", `Entry failed to start: \`${result.preflight.entry}\` (rebuild via \`${result.buildCommand}\`)`);
210407
210605
  }
210408
210606
  return result;
210409
210607
  }
@@ -210467,6 +210665,15 @@ async function runGuardRun(opts = {}) {
210467
210665
  process.exit(1);
210468
210666
  return;
210469
210667
  }
210668
+ case "entry-preflight-failed": {
210669
+ O2.error(`The recipe entry \`${result.preflight.entry}\` failed to start \u2014 every scenario would fail identically.`);
210670
+ for (const line of result.preflight.stderr.trimEnd().split("\n")) console.log(` ${line}`);
210671
+ O2.step(`Rebuild it with \`${result.buildCommand}\` (its build output is likely stale or incomplete), then re-run \`truecourse guard run\`.`);
210672
+ printLoadErrors(result.loadErrors);
210673
+ gt("Aborted \u2014 the entry could not start; no scenarios ran.");
210674
+ process.exit(1);
210675
+ return;
210676
+ }
210470
210677
  case "ok":
210471
210678
  break;
210472
210679
  }
@@ -210549,6 +210756,13 @@ async function runGuardGenerate(opts = {}) {
210549
210756
  gt("Add or fix `.truecourse/scenarios/recipe.json` and retry.");
210550
210757
  process.exit(1);
210551
210758
  }
210759
+ if (guard.entryPreflight) {
210760
+ O2.error(`The recipe entry \`${guard.entryPreflight.entry}\` failed to start \u2014 every scenario would fail identically, so nothing was validated.`);
210761
+ for (const line of guard.entryPreflight.stderr.trimEnd().split("\n")) console.log(` ${line}`);
210762
+ O2.step(`Rebuild it with \`${guard.entryPreflight.buildCommand}\` (its build output is likely stale or incomplete), then re-run \`truecourse guard generate\`.`);
210763
+ gt("Aborted \u2014 the entry could not start; no scenarios were validated.");
210764
+ process.exit(1);
210765
+ }
210552
210766
  if (guard.recipe?.status === "discovered") {
210553
210767
  O2.step(`recipe wrote ${guard.recipe.wrotePath} \u2014 review and commit it`);
210554
210768
  }
@@ -210772,6 +210986,7 @@ var STAGE_LABEL = {
210772
210986
  "contract.gapJudge": "Contract \xB7 gap judge",
210773
210987
  "guard.extract": "Guard \xB7 claim extract",
210774
210988
  "guard.generate": "Guard \xB7 scenario gen",
210989
+ "guard.retry": "Guard \xB7 scenario retry",
210775
210990
  "guard.recipe": "Guard \xB7 recipe discover",
210776
210991
  "rules.violationGen": "Rules \xB7 violation gen"
210777
210992
  };
@@ -211080,7 +211295,7 @@ async function runHooksRun() {
211080
211295
 
211081
211296
  // tools/cli/src/index.ts
211082
211297
  var program2 = new Command();
211083
- program2.name("truecourse").version("0.7.0-next.0").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
211298
+ program2.name("truecourse").version("0.7.0-next.2").description("TrueCourse CLI \u2014 analyze your repository and open the dashboard");
211084
211299
  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) => {
211085
211300
  if (options.service && options.console) {
211086
211301
  console.error("error: --service and --console are mutually exclusive");
@@ -211193,14 +211408,14 @@ docsCmd.command("list").description("List the kept (corpus) docs with their area
211193
211408
  docsCmd.command("skipped").description("List docs the relevance filter excluded from extraction").action(async () => {
211194
211409
  await runSpecDocsSkipped();
211195
211410
  });
211196
- docsCmd.command("include <path>").description("Force-include a skipped doc and re-scan").action(async (docPath) => {
211197
- await runSpecDocsInclude(docPath);
211411
+ docsCmd.command("include <path...>").description("Force-include one or more skipped docs and re-scan once").action(async (docPaths) => {
211412
+ await runSpecDocsInclude(docPaths);
211198
211413
  });
211199
211414
  docsCmd.command("uninclude <path>").description("Remove a force-include override").action(async (docPath) => {
211200
211415
  await runSpecDocsUninclude(docPath);
211201
211416
  });
211202
- docsCmd.command("exclude <path>").description("Force-exclude a kept doc from the corpus and re-scan").action(async (docPath) => {
211203
- await runSpecDocsExclude(docPath);
211417
+ docsCmd.command("exclude <path...>").description("Force-exclude one or more kept docs from the corpus and re-scan once").action(async (docPaths) => {
211418
+ await runSpecDocsExclude(docPaths);
211204
211419
  });
211205
211420
  docsCmd.command("unexclude <path>").description("Remove a force-exclude override").action(async (docPath) => {
211206
211421
  await runSpecDocsUnexclude(docPath);