truecourse 0.7.0-next.1 → 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.1";
170287
+ cachedVersion = "0.7.0-next.2";
170274
170288
  return cachedVersion;
170275
170289
  }
170276
170290
  try {
@@ -197480,35 +197494,49 @@ function prefilterDocs(docs, manualIncludes = []) {
197480
197494
  skipped: [...reasons].map(([path79, reason]) => ({ path: path79, reason }))
197481
197495
  };
197482
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
+ }
197483
197515
  async function filterByRelevance(repoRoot5, docs, opts = {}) {
197484
197516
  if (opts.enabled === false || docs.length === 0) {
197485
197517
  return { included: docs, skipped: [] };
197486
197518
  }
197487
- const manualSet = new Set(opts.manualIncludes ?? []);
197488
197519
  const runner = opts.runner ?? spawnRelevanceRunner({ transport: opts.transport, model: opts.model, fallbackModel: opts.fallbackModel });
197489
197520
  const concurrency = opts.concurrency ?? defaultConcurrency();
197490
- const { toClassify, skipped: prefilterSkipped } = prefilterDocs(docs, opts.manualIncludes ?? []);
197491
- 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]));
197492
197523
  const total = docs.length;
197493
197524
  let done = 0;
197494
197525
  const markDone = () => opts.onProgress?.(++done, total);
197495
197526
  opts.onProgress?.(0, total);
197496
- for (let i = 0; i < prefilterReason.size; i++)
197527
+ const resolvedUpfront = prefilterReason.size + plan.known.size;
197528
+ for (let i = 0; i < resolvedUpfront; i++)
197497
197529
  markDone();
197498
- const verdicts = /* @__PURE__ */ new Map();
197530
+ const verdicts = new Map(plan.known);
197531
+ const pending = plan.needsCall;
197499
197532
  let cursor = 0;
197500
197533
  let active11 = 0;
197501
197534
  await new Promise((resolve12) => {
197535
+ if (pending.length === 0)
197536
+ return resolve12();
197502
197537
  const launch = () => {
197503
- while (active11 < concurrency && cursor < toClassify.length) {
197504
- const doc = toClassify[cursor++];
197505
- if (manualSet.has(doc.path)) {
197506
- verdicts.set(doc.path, { path: doc.path, include: true, reason: "manual include" });
197507
- markDone();
197508
- if (cursor >= toClassify.length && active11 === 0)
197509
- resolve12();
197510
- continue;
197511
- }
197538
+ while (active11 < concurrency && cursor < pending.length) {
197539
+ const doc = pending[cursor++];
197512
197540
  active11++;
197513
197541
  classifyOne(repoRoot5, doc, runner).then((verdict) => {
197514
197542
  verdicts.set(doc.path, verdict);
@@ -197521,14 +197549,12 @@ async function filterByRelevance(repoRoot5, docs, opts = {}) {
197521
197549
  }).finally(() => {
197522
197550
  markDone();
197523
197551
  active11--;
197524
- if (cursor >= toClassify.length && active11 === 0)
197552
+ if (cursor >= pending.length && active11 === 0)
197525
197553
  resolve12();
197526
197554
  else
197527
197555
  launch();
197528
197556
  });
197529
197557
  }
197530
- if (cursor >= toClassify.length && active11 === 0)
197531
- resolve12();
197532
197558
  };
197533
197559
  launch();
197534
197560
  });
@@ -197725,9 +197751,6 @@ async function readCache4(scope, cacheKey) {
197725
197751
  async function writeCache4(scope, cacheKey, verdict) {
197726
197752
  await setCacheEntry(scope, CACHE_NAME5, cacheKey, verdict);
197727
197753
  }
197728
- async function readRelevanceCache(repoRoot5, doc) {
197729
- return readCache4(repoRoot5, computeCacheKey5(doc));
197730
- }
197731
197754
 
197732
197755
  // packages/spec-consolidator/dist/curate.js
197733
197756
  async function curate(repoRoot5, opts = {}) {
@@ -201835,6 +201858,9 @@ var STAGE_DEFAULTS = {
201835
201858
  // Authoring an executable scenario faithful to a spec claim is the hard,
201836
201859
  // load-bearing call (a weak scenario is false confidence) — opus.
201837
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",
201838
201864
  // Proposing a build/entry recipe is a modest structured task — sonnet.
201839
201865
  "guard.recipe": "sonnet",
201840
201866
  "rules.violationGen": "opus"
@@ -205125,6 +205151,129 @@ function listSandboxFiles(dir) {
205125
205151
  return out.sort();
205126
205152
  }
205127
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
+
205128
205277
  // packages/guard-runner/dist/capabilities/git.js
205129
205278
  import { spawnSync as spawnSync2 } from "node:child_process";
205130
205279
  import fs51 from "node:fs";
@@ -205205,67 +205354,6 @@ function applyCapabilities(setup, ctx) {
205205
205354
  }
205206
205355
  }
205207
205356
 
205208
- // packages/guard-runner/dist/executor.js
205209
- import { spawn as spawn9 } from "node:child_process";
205210
- var DEFAULT_STEP_TIMEOUT_MS = 3e4;
205211
- function executeStep(opts) {
205212
- const timeoutMs = opts.timeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
205213
- const [command, ...args] = opts.argv;
205214
- const start = Date.now();
205215
- return new Promise((resolve12) => {
205216
- const child = spawn9(command, args, {
205217
- cwd: opts.cwd,
205218
- env: opts.env,
205219
- stdio: ["pipe", "pipe", "pipe"]
205220
- });
205221
- let stdout = "";
205222
- let stderr = "";
205223
- let timedOut = false;
205224
- let settled = false;
205225
- const timer = setTimeout(() => {
205226
- timedOut = true;
205227
- child.kill("SIGKILL");
205228
- }, timeoutMs);
205229
- const finish = (capture) => {
205230
- if (settled)
205231
- return;
205232
- settled = true;
205233
- clearTimeout(timer);
205234
- resolve12(capture);
205235
- };
205236
- child.stdout.on("data", (chunk3) => {
205237
- stdout += chunk3.toString("utf-8");
205238
- });
205239
- child.stderr.on("data", (chunk3) => {
205240
- stderr += chunk3.toString("utf-8");
205241
- });
205242
- child.on("error", (err) => {
205243
- finish({
205244
- exitCode: null,
205245
- signal: null,
205246
- stdout,
205247
- stderr,
205248
- timedOut,
205249
- spawnError: err.message,
205250
- durationMs: Date.now() - start
205251
- });
205252
- });
205253
- child.on("close", (code, signal) => {
205254
- finish({
205255
- exitCode: code,
205256
- signal,
205257
- stdout,
205258
- stderr,
205259
- timedOut,
205260
- durationMs: Date.now() - start
205261
- });
205262
- });
205263
- if (opts.stdin !== void 0)
205264
- child.stdin.write(opts.stdin);
205265
- child.stdin.end();
205266
- });
205267
- }
205268
-
205269
205357
  // packages/guard-runner/dist/normalizers.js
205270
205358
  var CANONICAL_ORDER = [
205271
205359
  "abs-paths",
@@ -205896,16 +205984,27 @@ async function runGuard(opts) {
205896
205984
  }));
205897
205985
  const executable = planned.filter((p2) => p2.resolution.kind === "match" || p2.resolution.kind === "remap");
205898
205986
  const nonExecutable = planned.filter((p2) => p2.resolution.kind === "stale" || p2.resolution.kind === "orphaned");
205899
- if (!opts.skipBuild && executable.length > 0) {
205987
+ const buildsOwnEntry = !opts.skipBuild && executable.length > 0;
205988
+ if (buildsOwnEntry) {
205900
205989
  opts.onPhase?.("build");
205901
205990
  const build8 = await runBuild(repoRoot5, loaded.recipe.build, loaded.recipe.env);
205902
205991
  if (!build8.ok)
205903
205992
  return { status: "build-failed", build: build8, loadErrors };
205904
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
+ }
205905
206005
  opts.onPhase?.("run", selected.length);
205906
206006
  const runId = buildRunId();
205907
206007
  const ranAt = (/* @__PURE__ */ new Date()).toISOString();
205908
- const resolvedEntry = resolveEntry(repoRoot5, loaded.recipe.entry);
205909
206008
  const stepTimeoutMs = opts.stepTimeoutMs ?? DEFAULT_STEP_TIMEOUT_MS;
205910
206009
  const concurrency = opts.concurrency ?? defaultRunConcurrency();
205911
206010
  const results = [];
@@ -206450,11 +206549,11 @@ function spawnGenerateRunner2(opts = {}) {
206450
206549
  return async (ctx) => {
206451
206550
  const refs = ctx.claims.map((c2) => c2.ref).join(",");
206452
206551
  const isRetry = ctx.claims.some((c2) => c2.retry);
206453
- const suffix = `${isRetry ? ":retry" : ""}${ctx.correction ? ":correction" : ""}`;
206552
+ const stage = isRetry ? "guard.retry" : "guard.generate";
206454
206553
  const raw = await transport({
206455
- id: `guard.generate:${ctx.doc}:${refs}${suffix}`,
206456
- stage: "guard.generate",
206457
- model: opts.model,
206554
+ id: `${stage}:${ctx.doc}:${refs}${ctx.correction ? ":correction" : ""}`,
206555
+ stage,
206556
+ model: isRetry ? opts.retryModel ?? opts.model : opts.model,
206458
206557
  fallbackModel: opts.fallbackModel,
206459
206558
  system: GENERATE_SYSTEM_PROMPT,
206460
206559
  user: buildAuthorUserPrompt(ctx),
@@ -206766,12 +206865,15 @@ async function captureProbes(opts) {
206766
206865
  const cached = await getCacheEntry(opts.repoRoot, GROUND_CACHE_NAME, key4);
206767
206866
  if (cached) {
206768
206867
  const parsed = ProbeTranscriptSchema.safeParse(cached);
206769
- if (parsed.success)
206868
+ if (parsed.success) {
206869
+ opts.onProbeCaptured?.();
206770
206870
  return parsed.data;
206871
+ }
206771
206872
  }
206772
206873
  const capture = await exec3([...opts.resolvedEntry, ...argv], opts.recipeEnv);
206773
206874
  const transcript = toTranscript(argv, [...opts.displayEntry, ...argv], capture);
206774
206875
  await setCacheEntry(opts.repoRoot, GROUND_CACHE_NAME, key4, transcript);
206876
+ opts.onProbeCaptured?.();
206775
206877
  return transcript;
206776
206878
  }));
206777
206879
  }
@@ -207036,6 +207138,7 @@ function deleteScenarioFiles(repoRoot5, ids) {
207036
207138
 
207037
207139
  // packages/guard-generator/dist/generate.js
207038
207140
  var GENERATE_CACHE_NAME = "guard/generate";
207141
+ var ENTRY_PREFLIGHT_ANCHOR = "(entry preflight)";
207039
207142
  function defaultConcurrency3() {
207040
207143
  const env = process.env.TRUECOURSE_MAX_CONCURRENCY;
207041
207144
  if (env) {
@@ -207105,7 +207208,12 @@ async function generateGuards(options) {
207105
207208
  const limit = pLimit(Math.max(1, options.concurrency ?? defaultConcurrency3()));
207106
207209
  const batchSize = Math.max(1, options.batchSize ?? defaultGenerateBatch2());
207107
207210
  const extractRunner = options.extractRunner ?? spawnExtractRunner({ transport: options.transport, model: options.models?.extract, fallbackModel: options.models?.fallback });
207108
- 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
+ });
207109
207217
  const coverageGaps = [];
207110
207218
  const errors = [];
207111
207219
  const extractionFailures = [];
@@ -207176,11 +207284,15 @@ async function generateGuards(options) {
207176
207284
  return buildPromise;
207177
207285
  };
207178
207286
  let resolvedEntryMemo = null;
207287
+ let groundPlanned = 0;
207288
+ let groundCaptured = 0;
207179
207289
  const groundClaims = async (claimTexts) => {
207180
207290
  const probes = deriveProbes(claimTexts, recipe.entry);
207181
207291
  const build8 = await buildPromise;
207182
- if (!build8.ok)
207292
+ if (!build8.ok || probes.length === 0)
207183
207293
  return [];
207294
+ groundPlanned += probes.length;
207295
+ options.onGroundProgress?.(groundCaptured, groundPlanned);
207184
207296
  resolvedEntryMemo ??= resolveEntry(repoRoot5, recipe.entry);
207185
207297
  return captureProbes({
207186
207298
  repoRoot: repoRoot5,
@@ -207188,9 +207300,21 @@ async function generateGuards(options) {
207188
207300
  resolvedEntry: resolvedEntryMemo,
207189
207301
  displayEntry: recipe.entry,
207190
207302
  recipeFingerprint,
207191
- recipeEnv: recipe.env
207303
+ recipeEnv: recipe.env,
207304
+ onProbeCaptured: () => options.onGroundProgress?.(++groundCaptured, groundPlanned)
207192
207305
  });
207193
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
+ };
207194
207318
  const priorSections = readPriorManifest(repoRoot5);
207195
207319
  const manifestByKey = new Map(priorSections.map((e) => [`${e.doc}\0${e.anchor}`, e]));
207196
207320
  const usedIds = existingScenarioIds(repoRoot5);
@@ -207214,9 +207338,11 @@ async function generateGuards(options) {
207214
207338
  });
207215
207339
  settledKeys.add(k);
207216
207340
  writeWorkingManifest();
207341
+ options.onSectionSettled?.(settledKeys.size, plan.work.length);
207217
207342
  };
207218
207343
  const written = [];
207219
207344
  const birthFindings = [];
207345
+ let entryPreflightFailure = null;
207220
207346
  let birthTotal = 0;
207221
207347
  let birthSettled = 0;
207222
207348
  let birthPassed = 0;
@@ -207263,6 +207389,20 @@ async function generateGuards(options) {
207263
207389
  return;
207264
207390
  settleChain = settleChain.then(() => settleCliSection(sectionByKey.get(k), refsBySection.get(k)));
207265
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
+ };
207266
207406
  async function settleCliSection(section, refs) {
207267
207407
  const k = key2(section);
207268
207408
  for (const id of priorIdsOf(k))
@@ -207297,6 +207437,8 @@ async function generateGuards(options) {
207297
207437
  const message = `build failed (\`${build8.command}\`)${build8.timedOut ? " \u2014 timed out" : ""}`;
207298
207438
  for (const c2 of round1)
207299
207439
  localErrors.push(errorFrom({ candidate: c2, result: { failure: { actual: message } } }));
207440
+ } else if (await deadEntry()) {
207441
+ return;
207300
207442
  } else {
207301
207443
  birthTotal += round1.length;
207302
207444
  const r1 = await birthValidate(repoRoot5, round1, { skipBuild: true, onPhase: options.onBirthPhase, onScenarioSettled: bumpBirth });
@@ -207373,6 +207515,8 @@ async function generateGuards(options) {
207373
207515
  const authorTotal = authTasks.length;
207374
207516
  let authorDone = 0;
207375
207517
  const bumpAuthor = (n) => options.onAuthorProgress?.(authorDone += n, authorTotal);
207518
+ if (authorTotal > 0)
207519
+ options.onAuthorProgress?.(authorDone, authorTotal);
207376
207520
  const missTasks = [];
207377
207521
  for (const t2 of authTasks) {
207378
207522
  const cached = await readAuthorCache(repoRoot5, t2.claim, t2.section, recipeFingerprint);
@@ -207444,7 +207588,8 @@ async function generateGuards(options) {
207444
207588
  extractionFailures,
207445
207589
  orphaned,
207446
207590
  birthPassed,
207447
- manifestPath: manifestPath2(repoRoot5)
207591
+ manifestPath: manifestPath2(repoRoot5),
207592
+ ...entryPreflightFailure ? { entryPreflight: entryPreflightFailure } : {}
207448
207593
  };
207449
207594
  }
207450
207595
  var key2 = (s) => `${s.doc}\0${s.anchor}`;
@@ -207833,12 +207978,14 @@ function previewChars() {
207833
207978
  return 60 * 50;
207834
207979
  }
207835
207980
  async function estimateScanTokens(repoRoot5, prices) {
207981
+ const decisions = readCorpusDecisions(repoRoot5);
207982
+ const manualIncludes = decisions.manualIncludes ?? [];
207983
+ const manualExcludes = new Set(decisions.manualExcludes ?? []);
207836
207984
  const docs = discoverDocs(repoRoot5);
207837
- const { toClassify } = prefilterDocs(docs);
207838
- const nClassify = toClassify.length;
207839
- const relevance = await Promise.all(toClassify.map((d3) => readRelevanceCache(repoRoot5, d3)));
207840
- const relevanceMissDocs = toClassify.filter((_2, i) => relevance[i] === null);
207841
- 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));
207842
207989
  const cachedKeptTagged = await Promise.all(cachedKeptDocs.map((d3) => isAreaTagCached(repoRoot5, d3)));
207843
207990
  const cachedKeptTagMisses = cachedKeptTagged.filter((cached) => !cached).length;
207844
207991
  const estChangedKept = Math.round(relevanceMissDocs.length * KEEP_RATE);
@@ -210240,12 +210387,14 @@ var GUARD_GENERATE_STEPS = [
210240
210387
  var GUARD_STEP_STAGES = {
210241
210388
  index: ["guard.recipe"],
210242
210389
  extract: ["guard.extract"],
210243
- author: ["guard.generate"]
210390
+ author: ["guard.generate"],
210391
+ validate: ["guard.retry"]
210244
210392
  };
210245
210393
  function resolveGuardModels(repoRoot5) {
210246
210394
  return {
210247
210395
  extract: resolveModel("guard.extract", void 0, repoRoot5),
210248
210396
  generate: resolveModel("guard.generate", void 0, repoRoot5),
210397
+ retry: resolveModel("guard.retry", void 0, repoRoot5),
210249
210398
  recipe: resolveModel("guard.recipe", void 0, repoRoot5),
210250
210399
  fallback: resolveFallbackModel(repoRoot5) ?? void 0
210251
210400
  };
@@ -210287,10 +210436,20 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210287
210436
  cur = ni;
210288
210437
  };
210289
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
+ };
210290
210449
  let building = false;
210291
- let birthSeen = false;
210292
210450
  let birthDone = 0;
210293
- let birthTotal = 0;
210451
+ let sectionsDone = 0;
210452
+ let sectionsTotal = 0;
210294
210453
  let retrySeen = false;
210295
210454
  let retryDone = 0;
210296
210455
  let retryTotal = 0;
@@ -210301,10 +210460,10 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210301
210460
  tracker?.start("validate");
210302
210461
  validateStarted = true;
210303
210462
  }
210304
- const parts = [building ? "building\u2026" : `birth ${birthDone}/${birthTotal}`];
210463
+ const parts = [`sections ${sectionsDone}/${sectionsTotal}`, building ? "building\u2026" : `birth ${birthDone}`];
210305
210464
  if (retrySeen)
210306
- parts.push(`retrying failed claims ${retryDone}/${retryTotal}`);
210307
- tracker?.detail("validate", parts.join(" \xB7 "));
210465
+ parts.push(`retrying ${retryDone}/${retryTotal}`);
210466
+ tracker?.detail("validate", withUsage("validate", parts.join(" \xB7 ")));
210308
210467
  };
210309
210468
  tracker?.start("index");
210310
210469
  try {
@@ -210316,6 +210475,7 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210316
210475
  generateRunner: options.generateRunner,
210317
210476
  recipeRunner: options.recipeRunner,
210318
210477
  onPlan: (total, work) => {
210478
+ sectionsTotal = work;
210319
210479
  tracker?.done("index", withUsage("index", `${work} of ${total} section${total === 1 ? "" : "s"} changed`));
210320
210480
  cur = STEPS.indexOf("extract");
210321
210481
  tracker?.start("extract", `0 views`);
@@ -210332,27 +210492,30 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210332
210492
  },
210333
210493
  onAuthorProgress: (done, total) => {
210334
210494
  advanceTo("author");
210335
- const detail = withUsage("author", `${done}/${total} claim${total === 1 ? "" : "s"}`);
210336
- if (done >= total)
210337
- tracker?.done("author", detail);
210338
- else
210339
- tracker?.detail("author", detail);
210340
- },
210341
- onBirthPhase: (phase, total) => {
210342
- if (phase === "build") {
210343
- 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"}`));
210344
210500
  } else {
210345
- building = false;
210346
- if (!birthSeen && total !== void 0)
210347
- birthTotal = total;
210501
+ tracker?.detail("author", authorDetail());
210348
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";
210349
210514
  renderValidate();
210350
210515
  },
210351
- onBirthProgress: (done, total) => {
210516
+ onBirthProgress: (done) => {
210352
210517
  building = false;
210353
- birthSeen = true;
210354
210518
  birthDone = done;
210355
- birthTotal = total;
210356
210519
  renderValidate();
210357
210520
  },
210358
210521
  onRetryProgress: (done, total) => {
@@ -210360,6 +210523,12 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210360
210523
  retryDone = done;
210361
210524
  retryTotal = total;
210362
210525
  renderValidate();
210526
+ },
210527
+ onSectionSettled: (settled, total) => {
210528
+ sectionsDone = settled;
210529
+ sectionsTotal = total;
210530
+ if (validateStarted)
210531
+ renderValidate();
210363
210532
  }
210364
210533
  });
210365
210534
  for (let i = cur; i < STEPS.length; i++)
@@ -210383,7 +210552,7 @@ async function guardGenerateInProcess(repoRoot5, options = {}) {
210383
210552
  }
210384
210553
  }
210385
210554
  }
210386
- var GUARD_USAGE_STAGES = ["guard.recipe", "guard.extract", "guard.generate"];
210555
+ var GUARD_USAGE_STAGES = ["guard.recipe", "guard.extract", "guard.generate", "guard.retry"];
210387
210556
  function sumGuardUsage() {
210388
210557
  const usage = getStageUsage();
210389
210558
  let calls = 0;
@@ -210431,6 +210600,8 @@ async function guardRunInProcess(repoRoot5, options = {}) {
210431
210600
  tracker?.done("run", `${n} scenario${n === 1 ? "" : "s"}`);
210432
210601
  } else if (result.status === "build-failed") {
210433
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}\`)`);
210434
210605
  }
210435
210606
  return result;
210436
210607
  }
@@ -210494,6 +210665,15 @@ async function runGuardRun(opts = {}) {
210494
210665
  process.exit(1);
210495
210666
  return;
210496
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
+ }
210497
210677
  case "ok":
210498
210678
  break;
210499
210679
  }
@@ -210576,6 +210756,13 @@ async function runGuardGenerate(opts = {}) {
210576
210756
  gt("Add or fix `.truecourse/scenarios/recipe.json` and retry.");
210577
210757
  process.exit(1);
210578
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
+ }
210579
210766
  if (guard.recipe?.status === "discovered") {
210580
210767
  O2.step(`recipe wrote ${guard.recipe.wrotePath} \u2014 review and commit it`);
210581
210768
  }
@@ -210799,6 +210986,7 @@ var STAGE_LABEL = {
210799
210986
  "contract.gapJudge": "Contract \xB7 gap judge",
210800
210987
  "guard.extract": "Guard \xB7 claim extract",
210801
210988
  "guard.generate": "Guard \xB7 scenario gen",
210989
+ "guard.retry": "Guard \xB7 scenario retry",
210802
210990
  "guard.recipe": "Guard \xB7 recipe discover",
210803
210991
  "rules.violationGen": "Rules \xB7 violation gen"
210804
210992
  };
@@ -211107,7 +211295,7 @@ async function runHooksRun() {
211107
211295
 
211108
211296
  // tools/cli/src/index.ts
211109
211297
  var program2 = new Command();
211110
- program2.name("truecourse").version("0.7.0-next.1").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");
211111
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) => {
211112
211300
  if (options.service && options.console) {
211113
211301
  console.error("error: --service and --console are mutually exclusive");