codeharness 0.18.1 → 0.19.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/dist/index.js CHANGED
@@ -940,14 +940,9 @@ import { join as join5, dirname as dirname2 } from "path";
940
940
  import { fileURLToPath } from "url";
941
941
  var __dirname = dirname2(fileURLToPath(import.meta.url));
942
942
  function readPatchFile(name) {
943
- const projectRoot = join5(__dirname, "..", "..");
944
- const projectPath = join5(projectRoot, "patches", `${name}.md`);
945
- if (existsSync5(projectPath)) {
946
- return readFileSync5(projectPath, "utf-8").trim();
947
- }
948
- const pkgPath = join5(__dirname, "..", "..", "patches", `${name}.md`);
949
- if (existsSync5(pkgPath)) {
950
- return readFileSync5(pkgPath, "utf-8").trim();
943
+ const patchPath = join5(__dirname, "..", "..", "patches", `${name}.md`);
944
+ if (existsSync5(patchPath)) {
945
+ return readFileSync5(patchPath, "utf-8").trim();
951
946
  }
952
947
  return null;
953
948
  }
@@ -1392,7 +1387,7 @@ function getInstallCommand(stack) {
1392
1387
  }
1393
1388
 
1394
1389
  // src/commands/init.ts
1395
- var HARNESS_VERSION = true ? "0.18.1" : "0.0.0-dev";
1390
+ var HARNESS_VERSION = true ? "0.19.2" : "0.0.0-dev";
1396
1391
  function getProjectName(projectDir) {
1397
1392
  try {
1398
1393
  const pkgPath = join7(projectDir, "package.json");
@@ -1506,8 +1501,8 @@ function registerInitCommand(program) {
1506
1501
  readme: "skipped"
1507
1502
  }
1508
1503
  };
1509
- const statePath = getStatePath(projectDir);
1510
- if (existsSync7(statePath)) {
1504
+ const statePath2 = getStatePath(projectDir);
1505
+ if (existsSync7(statePath2)) {
1511
1506
  try {
1512
1507
  const existingState = readState(projectDir);
1513
1508
  const legacyObsDisabled = existingState.enforcement.observability === false;
@@ -2591,7 +2586,7 @@ function buildSpawnArgs(opts) {
2591
2586
  return args;
2592
2587
  }
2593
2588
  function registerRunCommand(program) {
2594
- program.command("run").description("Execute the autonomous coding loop").option("--max-iterations <n>", "Maximum loop iterations", "50").option("--timeout <seconds>", "Total loop timeout in seconds", "14400").option("--iteration-timeout <minutes>", "Per-iteration timeout in minutes", "30").option("--live", "Show live output streaming", false).option("--calls <n>", "Max API calls per hour", "100").option("--max-story-retries <n>", "Max retries per story before flagging", "3").option("--reset", "Clear retry counters, flagged stories, and circuit breaker before starting", false).action(async (options, cmd) => {
2589
+ program.command("run").description("Execute the autonomous coding loop").option("--max-iterations <n>", "Maximum loop iterations", "50").option("--timeout <seconds>", "Total loop timeout in seconds", "14400").option("--iteration-timeout <minutes>", "Per-iteration timeout in minutes", "30").option("--live", "Show live output streaming", false).option("--calls <n>", "Max API calls per hour", "100").option("--max-story-retries <n>", "Max retries per story before flagging", "10").option("--reset", "Clear retry counters, flagged stories, and circuit breaker before starting", false).action(async (options, cmd) => {
2595
2590
  const globalOpts = cmd.optsWithGlobals();
2596
2591
  const isJson = !!globalOpts.json;
2597
2592
  const outputOpts = { json: isJson };
@@ -2673,12 +2668,12 @@ function registerRunCommand(program) {
2673
2668
  cwd: projectDir,
2674
2669
  env
2675
2670
  });
2676
- const exitCode = await new Promise((resolve, reject) => {
2671
+ const exitCode = await new Promise((resolve2, reject) => {
2677
2672
  child.on("error", (err) => {
2678
2673
  reject(err);
2679
2674
  });
2680
2675
  child.on("close", (code) => {
2681
- resolve(code ?? 1);
2676
+ resolve2(code ?? 1);
2682
2677
  });
2683
2678
  });
2684
2679
  if (isJson) {
@@ -4324,8 +4319,8 @@ function printCoverageOutput(result, evaluation) {
4324
4319
 
4325
4320
  // src/lib/onboard-checks.ts
4326
4321
  function checkHarnessInitialized(dir) {
4327
- const statePath = getStatePath(dir ?? process.cwd());
4328
- return { ok: existsSync16(statePath) };
4322
+ const statePath2 = getStatePath(dir ?? process.cwd());
4323
+ return { ok: existsSync16(statePath2) };
4329
4324
  }
4330
4325
  function checkBmadInstalled(dir) {
4331
4326
  return { ok: isBmadInstalled(dir) };
@@ -4502,6 +4497,644 @@ function filterTrackedGaps(stories, beadsFns) {
4502
4497
  return { untracked, trackedCount };
4503
4498
  }
4504
4499
 
4500
+ // src/types/result.ts
4501
+ function ok2(data) {
4502
+ return { success: true, data };
4503
+ }
4504
+ function fail2(error, context) {
4505
+ if (context !== void 0) {
4506
+ return { success: false, error, context };
4507
+ }
4508
+ return { success: false, error };
4509
+ }
4510
+
4511
+ // src/modules/sprint/state.ts
4512
+ import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, renameSync, existsSync as existsSync18 } from "fs";
4513
+ import { join as join16 } from "path";
4514
+
4515
+ // src/modules/sprint/migration.ts
4516
+ import { readFileSync as readFileSync15, existsSync as existsSync17 } from "fs";
4517
+ import { join as join15 } from "path";
4518
+ var OLD_FILES = {
4519
+ storyRetries: "ralph/.story_retries",
4520
+ flaggedStories: "ralph/.flagged_stories",
4521
+ ralphStatus: "ralph/status.json",
4522
+ sprintStatusYaml: "_bmad-output/implementation-artifacts/sprint-status.yaml",
4523
+ sessionIssues: "_bmad-output/implementation-artifacts/.session-issues.md"
4524
+ };
4525
+ function resolve(relative3) {
4526
+ return join15(process.cwd(), relative3);
4527
+ }
4528
+ function readIfExists(relative3) {
4529
+ const p = resolve(relative3);
4530
+ if (!existsSync17(p)) return null;
4531
+ try {
4532
+ return readFileSync15(p, "utf-8");
4533
+ } catch {
4534
+ return null;
4535
+ }
4536
+ }
4537
+ function emptyStory() {
4538
+ return {
4539
+ status: "backlog",
4540
+ attempts: 0,
4541
+ lastAttempt: null,
4542
+ lastError: null,
4543
+ proofPath: null,
4544
+ acResults: null
4545
+ };
4546
+ }
4547
+ function upsertStory(stories, key, patch) {
4548
+ stories[key] = { ...stories[key] ?? emptyStory(), ...patch };
4549
+ }
4550
+ function parseStoryRetries(content, stories) {
4551
+ for (const line of content.split("\n")) {
4552
+ const trimmed = line.trim();
4553
+ if (!trimmed) continue;
4554
+ const parts = trimmed.split(/\s+/);
4555
+ if (parts.length < 2) continue;
4556
+ const count = parseInt(parts[1], 10);
4557
+ if (!isNaN(count)) upsertStory(stories, parts[0], { attempts: count });
4558
+ }
4559
+ }
4560
+ function parseFlaggedStories(content, stories) {
4561
+ for (const line of content.split("\n")) {
4562
+ const key = line.trim();
4563
+ if (key) upsertStory(stories, key, { status: "blocked" });
4564
+ }
4565
+ }
4566
+ function mapYamlStatus(value) {
4567
+ const mapping = {
4568
+ done: "done",
4569
+ backlog: "backlog",
4570
+ verifying: "verifying",
4571
+ "in-progress": "in-progress",
4572
+ "ready-for-dev": "ready",
4573
+ blocked: "blocked",
4574
+ failed: "failed",
4575
+ review: "review",
4576
+ ready: "ready"
4577
+ };
4578
+ return mapping[value.trim().toLowerCase()] ?? null;
4579
+ }
4580
+ function parseSprintStatusYaml(content, stories) {
4581
+ for (const line of content.split("\n")) {
4582
+ const trimmed = line.trim();
4583
+ if (!trimmed || trimmed.startsWith("#")) continue;
4584
+ const match = trimmed.match(/^([a-zA-Z0-9_-]+):\s*(.+)$/);
4585
+ if (!match) continue;
4586
+ const key = match[1];
4587
+ if (key === "development_status" || key.startsWith("epic-")) continue;
4588
+ const status = mapYamlStatus(match[2]);
4589
+ if (status) upsertStory(stories, key, { status });
4590
+ }
4591
+ }
4592
+ function parseRalphStatus(content) {
4593
+ try {
4594
+ const data = JSON.parse(content);
4595
+ return {
4596
+ active: data.status === "running",
4597
+ startedAt: null,
4598
+ iteration: data.loop_count ?? 0,
4599
+ cost: 0,
4600
+ completed: [],
4601
+ failed: []
4602
+ };
4603
+ } catch {
4604
+ return null;
4605
+ }
4606
+ }
4607
+ function parseSessionIssues(content) {
4608
+ const items = [];
4609
+ let currentStory = "";
4610
+ let itemId = 0;
4611
+ for (const line of content.split("\n")) {
4612
+ const headerMatch = line.match(/^###\s+([a-zA-Z0-9_-]+)\s*[—-]/);
4613
+ if (headerMatch) {
4614
+ currentStory = headerMatch[1];
4615
+ continue;
4616
+ }
4617
+ const bulletMatch = line.match(/^-\s+(.+)/);
4618
+ if (bulletMatch && currentStory) {
4619
+ itemId++;
4620
+ items.push({
4621
+ id: `migrated-${itemId}`,
4622
+ story: currentStory,
4623
+ description: bulletMatch[1],
4624
+ source: "retro",
4625
+ resolved: false
4626
+ });
4627
+ }
4628
+ }
4629
+ return items;
4630
+ }
4631
+ function migrateFromOldFormat() {
4632
+ const hasAnyOldFile = Object.values(OLD_FILES).some((rel) => existsSync17(resolve(rel)));
4633
+ if (!hasAnyOldFile) return fail2("No old format files found for migration");
4634
+ try {
4635
+ const stories = {};
4636
+ let run = defaultState().run;
4637
+ let actionItems = [];
4638
+ const yamlContent = readIfExists(OLD_FILES.sprintStatusYaml);
4639
+ if (yamlContent) parseSprintStatusYaml(yamlContent, stories);
4640
+ const retriesContent = readIfExists(OLD_FILES.storyRetries);
4641
+ if (retriesContent) parseStoryRetries(retriesContent, stories);
4642
+ const flaggedContent = readIfExists(OLD_FILES.flaggedStories);
4643
+ if (flaggedContent) parseFlaggedStories(flaggedContent, stories);
4644
+ const statusContent = readIfExists(OLD_FILES.ralphStatus);
4645
+ if (statusContent) {
4646
+ const parsed = parseRalphStatus(statusContent);
4647
+ if (parsed) run = parsed;
4648
+ }
4649
+ const issuesContent = readIfExists(OLD_FILES.sessionIssues);
4650
+ if (issuesContent) actionItems = parseSessionIssues(issuesContent);
4651
+ const sprint = computeSprintCounts(stories);
4652
+ const migrated = {
4653
+ version: 1,
4654
+ sprint,
4655
+ stories,
4656
+ run,
4657
+ actionItems
4658
+ };
4659
+ const writeResult = writeStateAtomic(migrated);
4660
+ if (!writeResult.success) return fail2(writeResult.error);
4661
+ return ok2(migrated);
4662
+ } catch (err) {
4663
+ const msg = err instanceof Error ? err.message : String(err);
4664
+ return fail2(`Migration failed: ${msg}`);
4665
+ }
4666
+ }
4667
+
4668
+ // src/modules/sprint/state.ts
4669
+ function projectRoot() {
4670
+ return process.cwd();
4671
+ }
4672
+ function statePath() {
4673
+ return join16(projectRoot(), "sprint-state.json");
4674
+ }
4675
+ function tmpPath() {
4676
+ return join16(projectRoot(), ".sprint-state.json.tmp");
4677
+ }
4678
+ function defaultState() {
4679
+ return {
4680
+ version: 1,
4681
+ sprint: {
4682
+ total: 0,
4683
+ done: 0,
4684
+ failed: 0,
4685
+ blocked: 0,
4686
+ inProgress: null
4687
+ },
4688
+ stories: {},
4689
+ run: {
4690
+ active: false,
4691
+ startedAt: null,
4692
+ iteration: 0,
4693
+ cost: 0,
4694
+ completed: [],
4695
+ failed: []
4696
+ },
4697
+ actionItems: []
4698
+ };
4699
+ }
4700
+ function writeStateAtomic(state) {
4701
+ try {
4702
+ const data = JSON.stringify(state, null, 2) + "\n";
4703
+ const tmp = tmpPath();
4704
+ const final = statePath();
4705
+ writeFileSync9(tmp, data, "utf-8");
4706
+ renameSync(tmp, final);
4707
+ return ok2(void 0);
4708
+ } catch (err) {
4709
+ const msg = err instanceof Error ? err.message : String(err);
4710
+ return fail2(`Failed to write sprint state: ${msg}`);
4711
+ }
4712
+ }
4713
+ function getSprintState() {
4714
+ const fp = statePath();
4715
+ if (existsSync18(fp)) {
4716
+ try {
4717
+ const raw = readFileSync16(fp, "utf-8");
4718
+ const parsed = JSON.parse(raw);
4719
+ return ok2(parsed);
4720
+ } catch (err) {
4721
+ const msg = err instanceof Error ? err.message : String(err);
4722
+ return fail2(`Failed to read sprint state: ${msg}`);
4723
+ }
4724
+ }
4725
+ const migrationResult = migrateFromOldFormat();
4726
+ if (migrationResult.success) {
4727
+ return migrationResult;
4728
+ }
4729
+ return ok2(defaultState());
4730
+ }
4731
+ function computeSprintCounts(stories) {
4732
+ let total = 0;
4733
+ let done = 0;
4734
+ let failed = 0;
4735
+ let blocked = 0;
4736
+ let inProgress = null;
4737
+ for (const [key, story] of Object.entries(stories)) {
4738
+ total++;
4739
+ if (story.status === "done") done++;
4740
+ else if (story.status === "failed") failed++;
4741
+ else if (story.status === "blocked") blocked++;
4742
+ else if (story.status === "in-progress") inProgress = key;
4743
+ }
4744
+ return { total, done, failed, blocked, inProgress };
4745
+ }
4746
+
4747
+ // src/modules/sprint/selector.ts
4748
+ var MAX_STORY_ATTEMPTS = 10;
4749
+
4750
+ // src/modules/sprint/drill-down.ts
4751
+ var MAX_ATTEMPTS = MAX_STORY_ATTEMPTS;
4752
+ function epicPrefix(key) {
4753
+ const dashIdx = key.indexOf("-");
4754
+ if (dashIdx === -1) return key;
4755
+ return key.slice(0, dashIdx);
4756
+ }
4757
+ function buildAcDetails(story) {
4758
+ if (!story.acResults) return [];
4759
+ return story.acResults.map((ac) => {
4760
+ const detail = { id: ac.id, verdict: ac.verdict };
4761
+ if (ac.verdict === "fail" && story.lastError) {
4762
+ return { ...detail, reason: story.lastError };
4763
+ }
4764
+ return detail;
4765
+ });
4766
+ }
4767
+ function buildAttemptHistory(story) {
4768
+ const records = [];
4769
+ if (story.attempts === 0) return records;
4770
+ for (let i = 1; i < story.attempts; i++) {
4771
+ records.push({
4772
+ number: i,
4773
+ outcome: "details unavailable"
4774
+ });
4775
+ }
4776
+ const lastOutcome = story.status === "done" ? "passed" : story.status === "failed" ? "verify failed" : story.status === "blocked" ? "blocked" : story.status;
4777
+ const lastRecord = {
4778
+ number: story.attempts,
4779
+ outcome: lastOutcome,
4780
+ ...story.lastAttempt ? { timestamp: story.lastAttempt } : {}
4781
+ };
4782
+ if (story.acResults) {
4783
+ const failingAc = story.acResults.find((ac) => ac.verdict === "fail");
4784
+ if (failingAc) {
4785
+ records.push({ ...lastRecord, failingAc: failingAc.id });
4786
+ return records;
4787
+ }
4788
+ }
4789
+ records.push(lastRecord);
4790
+ return records;
4791
+ }
4792
+ function buildProofSummary(story) {
4793
+ if (!story.proofPath) return null;
4794
+ let passCount = 0;
4795
+ let failCount = 0;
4796
+ let escalateCount = 0;
4797
+ let pendingCount = 0;
4798
+ if (story.acResults) {
4799
+ for (const ac of story.acResults) {
4800
+ if (ac.verdict === "pass") passCount++;
4801
+ else if (ac.verdict === "fail") failCount++;
4802
+ else if (ac.verdict === "escalate") escalateCount++;
4803
+ else if (ac.verdict === "pending") pendingCount++;
4804
+ }
4805
+ }
4806
+ return { path: story.proofPath, passCount, failCount, escalateCount, pendingCount };
4807
+ }
4808
+ function getStoryDrillDown(state, key) {
4809
+ try {
4810
+ const story = state.stories[key];
4811
+ if (!story) {
4812
+ return fail2(`Story '${key}' not found in sprint state`);
4813
+ }
4814
+ const epic = epicPrefix(key);
4815
+ const acDetails = buildAcDetails(story);
4816
+ const attemptHistory = buildAttemptHistory(story);
4817
+ const proofSummary = buildProofSummary(story);
4818
+ return ok2({
4819
+ key,
4820
+ status: story.status,
4821
+ epic,
4822
+ attempts: story.attempts,
4823
+ maxAttempts: MAX_ATTEMPTS,
4824
+ lastAttempt: story.lastAttempt,
4825
+ acDetails,
4826
+ attemptHistory,
4827
+ proofSummary
4828
+ });
4829
+ } catch (err) {
4830
+ const msg = err instanceof Error ? err.message : String(err);
4831
+ return fail2(`Failed to get story drill-down: ${msg}`);
4832
+ }
4833
+ }
4834
+
4835
+ // src/modules/sprint/reporter.ts
4836
+ var MAX_ATTEMPTS2 = MAX_STORY_ATTEMPTS;
4837
+ function formatDuration(ms) {
4838
+ const clamped = Math.max(0, ms);
4839
+ const totalMinutes = Math.floor(clamped / 6e4);
4840
+ const hours = Math.floor(totalMinutes / 60);
4841
+ const minutes = totalMinutes % 60;
4842
+ if (hours > 0) return `${hours}h${minutes}m`;
4843
+ return `${minutes}m`;
4844
+ }
4845
+ function computeEpicProgress(stories) {
4846
+ const epicGroups = /* @__PURE__ */ new Map();
4847
+ for (const [key, story] of Object.entries(stories)) {
4848
+ const prefix = epicPrefix(key);
4849
+ const group = epicGroups.get(prefix) ?? { total: 0, done: 0 };
4850
+ group.total++;
4851
+ if (story.status === "done") group.done++;
4852
+ epicGroups.set(prefix, group);
4853
+ }
4854
+ let epicsTotal = 0;
4855
+ let epicsDone = 0;
4856
+ for (const group of epicGroups.values()) {
4857
+ epicsTotal++;
4858
+ if (group.total > 0 && group.done === group.total) epicsDone++;
4859
+ }
4860
+ return { epicsTotal, epicsDone };
4861
+ }
4862
+ function buildFailedDetails(stories) {
4863
+ const details = [];
4864
+ for (const [key, story] of Object.entries(stories)) {
4865
+ if (story.status !== "failed") continue;
4866
+ let acNumber = null;
4867
+ if (story.acResults) {
4868
+ for (const ac of story.acResults) {
4869
+ if (ac.verdict === "fail") {
4870
+ const num = parseInt(ac.id.replace(/\D/g, ""), 10);
4871
+ if (!isNaN(num)) {
4872
+ acNumber = num;
4873
+ break;
4874
+ }
4875
+ }
4876
+ }
4877
+ }
4878
+ details.push({
4879
+ key,
4880
+ acNumber,
4881
+ errorLine: story.lastError ?? "unknown error",
4882
+ attempts: story.attempts,
4883
+ maxAttempts: MAX_ATTEMPTS2
4884
+ });
4885
+ }
4886
+ return details;
4887
+ }
4888
+ function buildActionItemsLabeled(state) {
4889
+ const runStories = /* @__PURE__ */ new Set([
4890
+ ...state.run.completed,
4891
+ ...state.run.failed
4892
+ ]);
4893
+ return state.actionItems.map((item) => {
4894
+ const isNew = item.source === "verification" && runStories.has(item.story);
4895
+ return { item, label: isNew ? "NEW" : "CARRIED" };
4896
+ });
4897
+ }
4898
+ function buildRunSummary(state, now) {
4899
+ if (!state.run.startedAt) return null;
4900
+ const startedAt = new Date(state.run.startedAt);
4901
+ const elapsed = now.getTime() - startedAt.getTime();
4902
+ const blocked = [];
4903
+ const skipped = [];
4904
+ for (const [key, story] of Object.entries(state.stories)) {
4905
+ if (story.status === "blocked") {
4906
+ blocked.push(key);
4907
+ if (story.attempts >= MAX_ATTEMPTS2) {
4908
+ skipped.push(key);
4909
+ }
4910
+ }
4911
+ }
4912
+ return {
4913
+ duration: formatDuration(elapsed),
4914
+ cost: state.run.cost,
4915
+ iterations: state.run.iteration,
4916
+ completed: [...state.run.completed],
4917
+ failed: [...state.run.failed],
4918
+ blocked,
4919
+ skipped
4920
+ };
4921
+ }
4922
+ function generateReport(state, now) {
4923
+ try {
4924
+ const effectiveNow = now ?? /* @__PURE__ */ new Date();
4925
+ const stories = state.stories;
4926
+ let total = 0;
4927
+ let done = 0;
4928
+ let failed = 0;
4929
+ let blocked = 0;
4930
+ const storyStatuses = [];
4931
+ for (const [key, story] of Object.entries(stories)) {
4932
+ total++;
4933
+ if (story.status === "done") done++;
4934
+ else if (story.status === "failed") failed++;
4935
+ else if (story.status === "blocked") blocked++;
4936
+ storyStatuses.push({ key, status: story.status });
4937
+ }
4938
+ const sprintPercent = total > 0 ? Math.round(done / total * 100) : 0;
4939
+ const { epicsTotal, epicsDone } = computeEpicProgress(stories);
4940
+ const runSummary = buildRunSummary(state, effectiveNow);
4941
+ const activeRun = state.run.active ? runSummary : null;
4942
+ const lastRun = !state.run.active ? runSummary : null;
4943
+ const failedDetails = buildFailedDetails(stories);
4944
+ const actionItemsLabeled = buildActionItemsLabeled(state);
4945
+ return ok2({
4946
+ total,
4947
+ done,
4948
+ failed,
4949
+ blocked,
4950
+ inProgress: state.sprint.inProgress,
4951
+ storyStatuses,
4952
+ epicsTotal,
4953
+ epicsDone,
4954
+ sprintPercent,
4955
+ activeRun,
4956
+ lastRun,
4957
+ failedDetails,
4958
+ actionItemsLabeled
4959
+ });
4960
+ } catch (err) {
4961
+ const msg = err instanceof Error ? err.message : String(err);
4962
+ return fail2(`Failed to generate report: ${msg}`);
4963
+ }
4964
+ }
4965
+
4966
+ // src/modules/sprint/timeout.ts
4967
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync10, existsSync as existsSync19, mkdirSync as mkdirSync6 } from "fs";
4968
+ import { execSync as execSync3 } from "child_process";
4969
+ import { join as join17 } from "path";
4970
+ var GIT_TIMEOUT_MS = 5e3;
4971
+ var DEFAULT_MAX_LINES = 100;
4972
+ function captureGitDiff() {
4973
+ try {
4974
+ const unstaged = execSync3("git diff --stat", {
4975
+ timeout: GIT_TIMEOUT_MS,
4976
+ encoding: "utf-8",
4977
+ stdio: ["pipe", "pipe", "pipe"]
4978
+ }).trim();
4979
+ const staged = execSync3("git diff --cached --stat", {
4980
+ timeout: GIT_TIMEOUT_MS,
4981
+ encoding: "utf-8",
4982
+ stdio: ["pipe", "pipe", "pipe"]
4983
+ }).trim();
4984
+ const parts = [];
4985
+ if (unstaged) parts.push("Unstaged:\n" + unstaged);
4986
+ if (staged) parts.push("Staged:\n" + staged);
4987
+ if (parts.length === 0) {
4988
+ return ok2("No changes detected");
4989
+ }
4990
+ return ok2(parts.join("\n\n"));
4991
+ } catch (err) {
4992
+ const msg = err instanceof Error ? err.message : String(err);
4993
+ return fail2(`Failed to capture git diff: ${msg}`);
4994
+ }
4995
+ }
4996
+ function captureStateDelta(beforePath, afterPath) {
4997
+ try {
4998
+ if (!existsSync19(beforePath)) {
4999
+ return fail2(`State snapshot not found: ${beforePath}`);
5000
+ }
5001
+ if (!existsSync19(afterPath)) {
5002
+ return fail2(`Current state file not found: ${afterPath}`);
5003
+ }
5004
+ const beforeRaw = readFileSync17(beforePath, "utf-8");
5005
+ const afterRaw = readFileSync17(afterPath, "utf-8");
5006
+ const before = JSON.parse(beforeRaw);
5007
+ const after = JSON.parse(afterRaw);
5008
+ const beforeStories = before.stories ?? {};
5009
+ const afterStories = after.stories ?? {};
5010
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(beforeStories), ...Object.keys(afterStories)]);
5011
+ const changes = [];
5012
+ for (const key of allKeys) {
5013
+ const beforeStatus = beforeStories[key]?.status ?? "(absent)";
5014
+ const afterStatus = afterStories[key]?.status ?? "(absent)";
5015
+ if (beforeStatus !== afterStatus) {
5016
+ changes.push(`${key}: ${beforeStatus} \u2192 ${afterStatus}`);
5017
+ }
5018
+ }
5019
+ if (changes.length === 0) {
5020
+ return ok2("No state changes");
5021
+ }
5022
+ return ok2(changes.join("\n"));
5023
+ } catch (err) {
5024
+ const msg = err instanceof Error ? err.message : String(err);
5025
+ return fail2(`Failed to capture state delta: ${msg}`);
5026
+ }
5027
+ }
5028
+ function capturePartialStderr(outputFile, maxLines = DEFAULT_MAX_LINES) {
5029
+ try {
5030
+ if (!existsSync19(outputFile)) {
5031
+ return fail2(`Output file not found: ${outputFile}`);
5032
+ }
5033
+ const content = readFileSync17(outputFile, "utf-8");
5034
+ const lines = content.split("\n");
5035
+ if (lines.length > 0 && lines[lines.length - 1] === "") {
5036
+ lines.pop();
5037
+ }
5038
+ const lastLines = lines.slice(-maxLines).join("\n");
5039
+ return ok2(lastLines);
5040
+ } catch (err) {
5041
+ const msg = err instanceof Error ? err.message : String(err);
5042
+ return fail2(`Failed to capture partial stderr: ${msg}`);
5043
+ }
5044
+ }
5045
+ function formatReport(capture) {
5046
+ const lines = [
5047
+ `# Timeout Report: Iteration ${capture.iteration}`,
5048
+ "",
5049
+ `- **Story:** ${capture.storyKey}`,
5050
+ `- **Duration:** ${capture.durationMinutes} minutes (timeout)`,
5051
+ `- **Timestamp:** ${capture.timestamp}`,
5052
+ "",
5053
+ "## Git Changes",
5054
+ "",
5055
+ capture.gitDiff,
5056
+ "",
5057
+ "## State Delta",
5058
+ "",
5059
+ capture.stateDelta,
5060
+ "",
5061
+ "## Partial Output (last 100 lines)",
5062
+ "",
5063
+ "```",
5064
+ capture.partialStderr,
5065
+ "```",
5066
+ ""
5067
+ ];
5068
+ return lines.join("\n");
5069
+ }
5070
+ function captureTimeoutReport(opts) {
5071
+ try {
5072
+ if (opts.iteration < 1 || !Number.isInteger(opts.iteration)) {
5073
+ return fail2(`Invalid iteration number: ${opts.iteration}`);
5074
+ }
5075
+ if (opts.durationMinutes < 0) {
5076
+ return fail2(`Invalid duration: ${opts.durationMinutes}`);
5077
+ }
5078
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
5079
+ const gitResult = captureGitDiff();
5080
+ const gitDiff = gitResult.success ? gitResult.data : `(unavailable: ${gitResult.error})`;
5081
+ const statePath2 = join17(process.cwd(), "sprint-state.json");
5082
+ const deltaResult = captureStateDelta(opts.stateSnapshotPath, statePath2);
5083
+ const stateDelta = deltaResult.success ? deltaResult.data : `(unavailable: ${deltaResult.error})`;
5084
+ const stderrResult = capturePartialStderr(opts.outputFile);
5085
+ const partialStderr = stderrResult.success ? stderrResult.data : `(unavailable: ${stderrResult.error})`;
5086
+ const capture = {
5087
+ storyKey: opts.storyKey,
5088
+ iteration: opts.iteration,
5089
+ durationMinutes: opts.durationMinutes,
5090
+ gitDiff,
5091
+ stateDelta,
5092
+ partialStderr,
5093
+ timestamp
5094
+ };
5095
+ const reportDir = join17(process.cwd(), "ralph", "logs");
5096
+ const safeStoryKey = opts.storyKey.replace(/[^a-zA-Z0-9._-]/g, "_");
5097
+ const reportFileName = `timeout-report-${opts.iteration}-${safeStoryKey}.md`;
5098
+ const reportPath = join17(reportDir, reportFileName);
5099
+ if (!existsSync19(reportDir)) {
5100
+ mkdirSync6(reportDir, { recursive: true });
5101
+ }
5102
+ const reportContent = formatReport(capture);
5103
+ writeFileSync10(reportPath, reportContent, "utf-8");
5104
+ return ok2({
5105
+ filePath: reportPath,
5106
+ capture
5107
+ });
5108
+ } catch (err) {
5109
+ const msg = err instanceof Error ? err.message : String(err);
5110
+ return fail2(`Failed to capture timeout report: ${msg}`);
5111
+ }
5112
+ }
5113
+
5114
+ // src/modules/sprint/feedback.ts
5115
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
5116
+ import { existsSync as existsSync20 } from "fs";
5117
+ import { join as join18 } from "path";
5118
+
5119
+ // src/modules/sprint/index.ts
5120
+ function generateReport2() {
5121
+ const stateResult = getSprintState();
5122
+ if (!stateResult.success) {
5123
+ return fail2(stateResult.error);
5124
+ }
5125
+ return generateReport(stateResult.data);
5126
+ }
5127
+ function getStoryDrillDown2(key) {
5128
+ const stateResult = getSprintState();
5129
+ if (!stateResult.success) {
5130
+ return fail2(stateResult.error);
5131
+ }
5132
+ return getStoryDrillDown(stateResult.data, key);
5133
+ }
5134
+ function captureTimeoutReport2(opts) {
5135
+ return captureTimeoutReport(opts);
5136
+ }
5137
+
4505
5138
  // src/commands/status.ts
4506
5139
  function buildScopedEndpoints(endpoints, serviceName) {
4507
5140
  const encoded = encodeURIComponent(serviceName);
@@ -4518,9 +5151,13 @@ var DEFAULT_ENDPOINTS = {
4518
5151
  otel_http: "http://localhost:4318"
4519
5152
  };
4520
5153
  function registerStatusCommand(program) {
4521
- program.command("status").description("Show current harness status and health").option("--check-docker", "Check Docker stack health").option("--check", "Run health checks with pass/fail exit code").action(async (options, cmd) => {
5154
+ program.command("status").description("Show current harness status and health").option("--check-docker", "Check Docker stack health").option("--check", "Run health checks with pass/fail exit code").option("--story <id>", "Show detailed status for a specific story").action(async (options, cmd) => {
4522
5155
  const opts = cmd.optsWithGlobals();
4523
5156
  const isJson = opts.json === true;
5157
+ if (options.story) {
5158
+ handleStoryDrillDown(options.story, isJson);
5159
+ return;
5160
+ }
4524
5161
  if (options.checkDocker) {
4525
5162
  await handleDockerCheck(isJson);
4526
5163
  return;
@@ -4552,6 +5189,7 @@ function handleFullStatus(isJson) {
4552
5189
  handleFullStatusJson(state);
4553
5190
  return;
4554
5191
  }
5192
+ printSprintState();
4555
5193
  console.log(`Harness: codeharness v${state.harness_version}`);
4556
5194
  console.log(`Stack: ${state.stack ?? "unknown"}`);
4557
5195
  if (state.app_type) {
@@ -4688,10 +5326,12 @@ function handleFullStatusJson(state) {
4688
5326
  const scoped_endpoints = serviceName ? buildScopedEndpoints(endpoints, serviceName) : void 0;
4689
5327
  const beads = getBeadsData();
4690
5328
  const onboarding = getOnboardingProgressData();
5329
+ const sprint = getSprintReportData();
4691
5330
  jsonOutput({
4692
5331
  version: state.harness_version,
4693
5332
  stack: state.stack,
4694
5333
  ...state.app_type ? { app_type: state.app_type } : {},
5334
+ ...sprint ? { sprint } : {},
4695
5335
  enforcement: state.enforcement,
4696
5336
  docker,
4697
5337
  endpoints,
@@ -4883,6 +5523,116 @@ async function handleDockerCheck(isJson) {
4883
5523
  }
4884
5524
  }
4885
5525
  }
5526
+ function handleStoryDrillDown(storyId, isJson) {
5527
+ const result = getStoryDrillDown2(storyId);
5528
+ if (!result.success) {
5529
+ if (isJson) {
5530
+ jsonOutput({ status: "fail", message: result.error });
5531
+ } else {
5532
+ fail(result.error);
5533
+ }
5534
+ process.exitCode = 1;
5535
+ return;
5536
+ }
5537
+ const d = result.data;
5538
+ if (isJson) {
5539
+ jsonOutput({
5540
+ key: d.key,
5541
+ status: d.status,
5542
+ epic: d.epic,
5543
+ attempts: d.attempts,
5544
+ maxAttempts: d.maxAttempts,
5545
+ lastAttempt: d.lastAttempt,
5546
+ acResults: d.acDetails,
5547
+ attemptHistory: d.attemptHistory,
5548
+ proof: d.proofSummary
5549
+ });
5550
+ return;
5551
+ }
5552
+ console.log(`Story: ${d.key}`);
5553
+ console.log(`Status: ${d.status} (attempt ${d.attempts}/${d.maxAttempts})`);
5554
+ console.log(`Epic: ${d.epic}`);
5555
+ console.log(`Last attempt: ${d.lastAttempt ?? "none"}`);
5556
+ console.log("");
5557
+ console.log("-- AC Results -------------------------------------------------------");
5558
+ if (d.acDetails.length === 0) {
5559
+ console.log("No AC results recorded");
5560
+ } else {
5561
+ for (const ac of d.acDetails) {
5562
+ const tag = ac.verdict.toUpperCase();
5563
+ console.log(`${ac.id}: [${tag}]`);
5564
+ if (ac.verdict === "fail") {
5565
+ if (ac.command) console.log(` Command: ${ac.command}`);
5566
+ if (ac.expected) console.log(` Expected: ${ac.expected}`);
5567
+ if (ac.actual) console.log(` Actual: ${ac.actual}`);
5568
+ if (ac.reason) console.log(` Reason: ${ac.reason}`);
5569
+ if (ac.suggestedFix) console.log(` Suggest: ${ac.suggestedFix}`);
5570
+ }
5571
+ }
5572
+ }
5573
+ if (d.attemptHistory.length > 0) {
5574
+ console.log("");
5575
+ console.log("-- History ----------------------------------------------------------");
5576
+ for (const attempt of d.attemptHistory) {
5577
+ const acPart = attempt.failingAc ? ` (${attempt.failingAc})` : "";
5578
+ console.log(`Attempt ${attempt.number}: ${attempt.outcome}${acPart}`);
5579
+ }
5580
+ }
5581
+ if (d.proofSummary) {
5582
+ console.log("");
5583
+ const p = d.proofSummary;
5584
+ const total = p.passCount + p.failCount + p.escalateCount + p.pendingCount;
5585
+ console.log(
5586
+ `Proof: ${p.path} (${p.passCount}/${total} pass, ${p.failCount} fail, ${p.escalateCount} escalate)`
5587
+ );
5588
+ }
5589
+ }
5590
+ function printSprintState() {
5591
+ const reportResult = generateReport2();
5592
+ if (!reportResult.success) {
5593
+ console.log("Sprint state: unavailable");
5594
+ return;
5595
+ }
5596
+ const r = reportResult.data;
5597
+ console.log(`\u2500\u2500 Project State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
5598
+ console.log(`Sprint: ${r.done}/${r.total} done (${r.sprintPercent}%) | ${r.epicsDone}/${r.epicsTotal} epics complete`);
5599
+ if (r.activeRun) {
5600
+ console.log("");
5601
+ console.log(`\u2500\u2500 Active Run \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
5602
+ const currentStory = r.inProgress ?? "none";
5603
+ console.log(`Status: running (iteration ${r.activeRun.iterations}, ${r.activeRun.duration} elapsed)`);
5604
+ console.log(`Current: ${currentStory}`);
5605
+ console.log(`Budget: $${r.activeRun.cost.toFixed(2)} spent`);
5606
+ } else if (r.lastRun) {
5607
+ console.log("");
5608
+ console.log(`\u2500\u2500 Last Run Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
5609
+ console.log(`Duration: ${r.lastRun.duration} | Cost: $${r.lastRun.cost.toFixed(2)} | Iterations: ${r.lastRun.iterations}`);
5610
+ console.log(`Completed: ${r.lastRun.completed.length} stories${r.lastRun.completed.length > 0 ? ` (${r.lastRun.completed.join(", ")})` : ""}`);
5611
+ if (r.failedDetails.length > 0) {
5612
+ console.log(`Failed: ${r.failedDetails.length} stor${r.failedDetails.length === 1 ? "y" : "ies"}`);
5613
+ for (const fd of r.failedDetails) {
5614
+ const acPart = fd.acNumber !== null ? `AC ${fd.acNumber}` : "unknown AC";
5615
+ console.log(` \u2514 ${fd.key}: ${acPart} \u2014 ${fd.errorLine} (attempt ${fd.attempts}/${fd.maxAttempts})`);
5616
+ }
5617
+ }
5618
+ if (r.lastRun.blocked.length > 0) {
5619
+ console.log(`Blocked: ${r.lastRun.blocked.length} stories (retry-exhausted)`);
5620
+ }
5621
+ }
5622
+ if (r.actionItemsLabeled.length > 0) {
5623
+ console.log("");
5624
+ console.log(`\u2500\u2500 Action Items \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
5625
+ for (const la of r.actionItemsLabeled) {
5626
+ console.log(` [${la.label}] ${la.item.story}: ${la.item.description}`);
5627
+ }
5628
+ }
5629
+ console.log("");
5630
+ }
5631
+ function getSprintReportData() {
5632
+ const reportResult = generateReport2();
5633
+ if (!reportResult.success) return null;
5634
+ return reportResult.data;
5635
+ }
4886
5636
  function printBeadsSummary() {
4887
5637
  if (!isBeadsInitialized()) {
4888
5638
  console.log("Beads: not initialized");
@@ -4949,16 +5699,16 @@ function getBeadsData() {
4949
5699
  }
4950
5700
 
4951
5701
  // src/commands/onboard.ts
4952
- import { join as join18 } from "path";
5702
+ import { join as join22 } from "path";
4953
5703
 
4954
5704
  // src/lib/scanner.ts
4955
5705
  import {
4956
- existsSync as existsSync17,
5706
+ existsSync as existsSync21,
4957
5707
  readdirSync as readdirSync3,
4958
- readFileSync as readFileSync15,
5708
+ readFileSync as readFileSync19,
4959
5709
  statSync as statSync2
4960
5710
  } from "fs";
4961
- import { join as join15, relative as relative2 } from "path";
5711
+ import { join as join19, relative as relative2 } from "path";
4962
5712
  var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".js", ".py"]);
4963
5713
  var DEFAULT_MIN_MODULE_SIZE = 3;
4964
5714
  function getExtension2(filename) {
@@ -4983,7 +5733,7 @@ function countSourceFiles(dir) {
4983
5733
  for (const entry of entries) {
4984
5734
  if (isSkippedDir(entry)) continue;
4985
5735
  if (entry.startsWith(".") && current !== dir) continue;
4986
- const fullPath = join15(current, entry);
5736
+ const fullPath = join19(current, entry);
4987
5737
  let stat;
4988
5738
  try {
4989
5739
  stat = statSync2(fullPath);
@@ -5006,7 +5756,7 @@ function countSourceFiles(dir) {
5006
5756
  return count;
5007
5757
  }
5008
5758
  function countModuleFiles(modulePath, rootDir) {
5009
- const fullModulePath = join15(rootDir, modulePath);
5759
+ const fullModulePath = join19(rootDir, modulePath);
5010
5760
  let sourceFiles = 0;
5011
5761
  let testFiles = 0;
5012
5762
  function walk(current) {
@@ -5018,7 +5768,7 @@ function countModuleFiles(modulePath, rootDir) {
5018
5768
  }
5019
5769
  for (const entry of entries) {
5020
5770
  if (isSkippedDir(entry)) continue;
5021
- const fullPath = join15(current, entry);
5771
+ const fullPath = join19(current, entry);
5022
5772
  let stat;
5023
5773
  try {
5024
5774
  stat = statSync2(fullPath);
@@ -5043,8 +5793,8 @@ function countModuleFiles(modulePath, rootDir) {
5043
5793
  return { sourceFiles, testFiles };
5044
5794
  }
5045
5795
  function detectArtifacts(dir) {
5046
- const bmadPath = join15(dir, "_bmad");
5047
- const hasBmad = existsSync17(bmadPath);
5796
+ const bmadPath = join19(dir, "_bmad");
5797
+ const hasBmad = existsSync21(bmadPath);
5048
5798
  return {
5049
5799
  hasBmad,
5050
5800
  bmadPath: hasBmad ? relative2(dir, bmadPath) || "_bmad" : null
@@ -5126,10 +5876,10 @@ function readPerFileCoverage(dir, format) {
5126
5876
  return null;
5127
5877
  }
5128
5878
  function readVitestPerFileCoverage(dir) {
5129
- const reportPath = join15(dir, "coverage", "coverage-summary.json");
5130
- if (!existsSync17(reportPath)) return null;
5879
+ const reportPath = join19(dir, "coverage", "coverage-summary.json");
5880
+ if (!existsSync21(reportPath)) return null;
5131
5881
  try {
5132
- const report = JSON.parse(readFileSync15(reportPath, "utf-8"));
5882
+ const report = JSON.parse(readFileSync19(reportPath, "utf-8"));
5133
5883
  const result = /* @__PURE__ */ new Map();
5134
5884
  for (const [key, value] of Object.entries(report)) {
5135
5885
  if (key === "total") continue;
@@ -5141,10 +5891,10 @@ function readVitestPerFileCoverage(dir) {
5141
5891
  }
5142
5892
  }
5143
5893
  function readPythonPerFileCoverage(dir) {
5144
- const reportPath = join15(dir, "coverage.json");
5145
- if (!existsSync17(reportPath)) return null;
5894
+ const reportPath = join19(dir, "coverage.json");
5895
+ if (!existsSync21(reportPath)) return null;
5146
5896
  try {
5147
- const report = JSON.parse(readFileSync15(reportPath, "utf-8"));
5897
+ const report = JSON.parse(readFileSync19(reportPath, "utf-8"));
5148
5898
  if (!report.files) return null;
5149
5899
  const result = /* @__PURE__ */ new Map();
5150
5900
  for (const [key, value] of Object.entries(report.files)) {
@@ -5160,13 +5910,13 @@ function auditDocumentation(dir) {
5160
5910
  const root = dir ?? process.cwd();
5161
5911
  const documents = [];
5162
5912
  for (const docName of AUDIT_DOCUMENTS) {
5163
- const docPath = join15(root, docName);
5164
- if (!existsSync17(docPath)) {
5913
+ const docPath = join19(root, docName);
5914
+ if (!existsSync21(docPath)) {
5165
5915
  documents.push({ name: docName, grade: "missing", path: null });
5166
5916
  continue;
5167
5917
  }
5168
- const srcDir = join15(root, "src");
5169
- const codeDir = existsSync17(srcDir) ? srcDir : root;
5918
+ const srcDir = join19(root, "src");
5919
+ const codeDir = existsSync21(srcDir) ? srcDir : root;
5170
5920
  const stale = isDocStale(docPath, codeDir);
5171
5921
  documents.push({
5172
5922
  name: docName,
@@ -5174,8 +5924,8 @@ function auditDocumentation(dir) {
5174
5924
  path: docName
5175
5925
  });
5176
5926
  }
5177
- const docsDir = join15(root, "docs");
5178
- if (existsSync17(docsDir)) {
5927
+ const docsDir = join19(root, "docs");
5928
+ if (existsSync21(docsDir)) {
5179
5929
  try {
5180
5930
  const stat = statSync2(docsDir);
5181
5931
  if (stat.isDirectory()) {
@@ -5187,10 +5937,10 @@ function auditDocumentation(dir) {
5187
5937
  } else {
5188
5938
  documents.push({ name: "docs/", grade: "missing", path: null });
5189
5939
  }
5190
- const indexPath = join15(root, "docs", "index.md");
5191
- if (existsSync17(indexPath)) {
5192
- const srcDir = join15(root, "src");
5193
- const indexCodeDir = existsSync17(srcDir) ? srcDir : root;
5940
+ const indexPath = join19(root, "docs", "index.md");
5941
+ if (existsSync21(indexPath)) {
5942
+ const srcDir = join19(root, "src");
5943
+ const indexCodeDir = existsSync21(srcDir) ? srcDir : root;
5194
5944
  const indexStale = isDocStale(indexPath, indexCodeDir);
5195
5945
  documents.push({
5196
5946
  name: "docs/index.md",
@@ -5207,8 +5957,8 @@ function auditDocumentation(dir) {
5207
5957
 
5208
5958
  // src/lib/epic-generator.ts
5209
5959
  import { createInterface } from "readline";
5210
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, writeFileSync as writeFileSync9 } from "fs";
5211
- import { dirname as dirname5, join as join16 } from "path";
5960
+ import { existsSync as existsSync22, mkdirSync as mkdirSync7, writeFileSync as writeFileSync12 } from "fs";
5961
+ import { dirname as dirname6, join as join20 } from "path";
5212
5962
  var PRIORITY_BY_TYPE = {
5213
5963
  observability: 1,
5214
5964
  coverage: 2,
@@ -5246,8 +5996,8 @@ function generateOnboardingEpic(scan, coverage, audit, rootDir) {
5246
5996
  storyNum++;
5247
5997
  }
5248
5998
  for (const mod of scan.modules) {
5249
- const agentsPath = join16(root, mod.path, "AGENTS.md");
5250
- if (!existsSync18(agentsPath)) {
5999
+ const agentsPath = join20(root, mod.path, "AGENTS.md");
6000
+ if (!existsSync22(agentsPath)) {
5251
6001
  stories.push({
5252
6002
  key: `0.${storyNum}`,
5253
6003
  title: `Create ${mod.path}/AGENTS.md`,
@@ -5313,7 +6063,7 @@ function generateOnboardingEpic(scan, coverage, audit, rootDir) {
5313
6063
  };
5314
6064
  }
5315
6065
  function writeOnboardingEpic(epic, outputPath) {
5316
- mkdirSync6(dirname5(outputPath), { recursive: true });
6066
+ mkdirSync7(dirname6(outputPath), { recursive: true });
5317
6067
  const lines = [];
5318
6068
  lines.push(`# ${epic.title}`);
5319
6069
  lines.push("");
@@ -5349,14 +6099,14 @@ function writeOnboardingEpic(epic, outputPath) {
5349
6099
  lines.push("");
5350
6100
  lines.push("Review and approve before execution.");
5351
6101
  lines.push("");
5352
- writeFileSync9(outputPath, lines.join("\n"), "utf-8");
6102
+ writeFileSync12(outputPath, lines.join("\n"), "utf-8");
5353
6103
  }
5354
6104
  function formatEpicSummary(epic) {
5355
6105
  const { totalStories, coverageStories, docStories, verificationStories, observabilityStories } = epic.summary;
5356
6106
  return `Onboarding plan: ${totalStories} stories (${coverageStories} coverage, ${docStories} documentation, ${verificationStories} verification, ${observabilityStories} observability)`;
5357
6107
  }
5358
6108
  function promptApproval() {
5359
- return new Promise((resolve) => {
6109
+ return new Promise((resolve2) => {
5360
6110
  let answered = false;
5361
6111
  const rl = createInterface({
5362
6112
  input: process.stdin,
@@ -5365,14 +6115,14 @@ function promptApproval() {
5365
6115
  rl.on("close", () => {
5366
6116
  if (!answered) {
5367
6117
  answered = true;
5368
- resolve(false);
6118
+ resolve2(false);
5369
6119
  }
5370
6120
  });
5371
6121
  rl.question("Review the onboarding plan. Approve? [Y/n] ", (answer) => {
5372
6122
  answered = true;
5373
6123
  rl.close();
5374
6124
  const trimmed = answer.trim().toLowerCase();
5375
- resolve(trimmed === "" || trimmed === "y");
6125
+ resolve2(trimmed === "" || trimmed === "y");
5376
6126
  });
5377
6127
  });
5378
6128
  }
@@ -5445,29 +6195,29 @@ function getGapIdFromTitle(title) {
5445
6195
  }
5446
6196
 
5447
6197
  // src/lib/scan-cache.ts
5448
- import { existsSync as existsSync19, mkdirSync as mkdirSync7, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
5449
- import { join as join17 } from "path";
6198
+ import { existsSync as existsSync23, mkdirSync as mkdirSync8, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
6199
+ import { join as join21 } from "path";
5450
6200
  var CACHE_DIR = ".harness";
5451
6201
  var CACHE_FILE = "last-onboard-scan.json";
5452
6202
  var DEFAULT_MAX_AGE_MS = 864e5;
5453
6203
  function saveScanCache(entry, dir) {
5454
6204
  try {
5455
6205
  const root = dir ?? process.cwd();
5456
- const cacheDir = join17(root, CACHE_DIR);
5457
- mkdirSync7(cacheDir, { recursive: true });
5458
- const cachePath = join17(cacheDir, CACHE_FILE);
5459
- writeFileSync10(cachePath, JSON.stringify(entry, null, 2), "utf-8");
6206
+ const cacheDir = join21(root, CACHE_DIR);
6207
+ mkdirSync8(cacheDir, { recursive: true });
6208
+ const cachePath = join21(cacheDir, CACHE_FILE);
6209
+ writeFileSync13(cachePath, JSON.stringify(entry, null, 2), "utf-8");
5460
6210
  } catch {
5461
6211
  }
5462
6212
  }
5463
6213
  function loadScanCache(dir) {
5464
6214
  const root = dir ?? process.cwd();
5465
- const cachePath = join17(root, CACHE_DIR, CACHE_FILE);
5466
- if (!existsSync19(cachePath)) {
6215
+ const cachePath = join21(root, CACHE_DIR, CACHE_FILE);
6216
+ if (!existsSync23(cachePath)) {
5467
6217
  return null;
5468
6218
  }
5469
6219
  try {
5470
- const raw = readFileSync16(cachePath, "utf-8");
6220
+ const raw = readFileSync20(cachePath, "utf-8");
5471
6221
  return JSON.parse(raw);
5472
6222
  } catch {
5473
6223
  return null;
@@ -5640,7 +6390,7 @@ function registerOnboardCommand(program) {
5640
6390
  }
5641
6391
  coverage = lastCoverageResult ?? runCoverageAnalysis(scan);
5642
6392
  audit = lastAuditResult ?? runAudit();
5643
- const epicPath = join18(process.cwd(), "ralph", "onboarding-epic.md");
6393
+ const epicPath = join22(process.cwd(), "ralph", "onboarding-epic.md");
5644
6394
  const epic = generateOnboardingEpic(scan, coverage, audit);
5645
6395
  mergeExtendedGaps(epic);
5646
6396
  if (!isFull) {
@@ -5713,7 +6463,7 @@ function registerOnboardCommand(program) {
5713
6463
  coverage,
5714
6464
  audit
5715
6465
  });
5716
- const epicPath = join18(process.cwd(), "ralph", "onboarding-epic.md");
6466
+ const epicPath = join22(process.cwd(), "ralph", "onboarding-epic.md");
5717
6467
  const epic = generateOnboardingEpic(scan, coverage, audit);
5718
6468
  mergeExtendedGaps(epic);
5719
6469
  if (!isFull) {
@@ -5821,8 +6571,8 @@ function printEpicOutput(epic) {
5821
6571
  }
5822
6572
 
5823
6573
  // src/commands/teardown.ts
5824
- import { existsSync as existsSync20, unlinkSync as unlinkSync2, readFileSync as readFileSync17, writeFileSync as writeFileSync11, rmSync } from "fs";
5825
- import { join as join19 } from "path";
6574
+ import { existsSync as existsSync24, unlinkSync as unlinkSync2, readFileSync as readFileSync21, writeFileSync as writeFileSync14, rmSync } from "fs";
6575
+ import { join as join23 } from "path";
5826
6576
  function buildDefaultResult() {
5827
6577
  return {
5828
6578
  status: "ok",
@@ -5925,16 +6675,16 @@ function registerTeardownCommand(program) {
5925
6675
  info("Docker stack: not running, skipping");
5926
6676
  }
5927
6677
  }
5928
- const composeFilePath = join19(projectDir, composeFile);
5929
- if (existsSync20(composeFilePath)) {
6678
+ const composeFilePath = join23(projectDir, composeFile);
6679
+ if (existsSync24(composeFilePath)) {
5930
6680
  unlinkSync2(composeFilePath);
5931
6681
  result.removed.push(composeFile);
5932
6682
  if (!isJson) {
5933
6683
  ok(`Removed: ${composeFile}`);
5934
6684
  }
5935
6685
  }
5936
- const otelConfigPath = join19(projectDir, "otel-collector-config.yaml");
5937
- if (existsSync20(otelConfigPath)) {
6686
+ const otelConfigPath = join23(projectDir, "otel-collector-config.yaml");
6687
+ if (existsSync24(otelConfigPath)) {
5938
6688
  unlinkSync2(otelConfigPath);
5939
6689
  result.removed.push("otel-collector-config.yaml");
5940
6690
  if (!isJson) {
@@ -5944,8 +6694,8 @@ function registerTeardownCommand(program) {
5944
6694
  }
5945
6695
  let patchesRemoved = 0;
5946
6696
  for (const [patchName, relativePath] of Object.entries(PATCH_TARGETS)) {
5947
- const filePath = join19(projectDir, "_bmad", relativePath);
5948
- if (!existsSync20(filePath)) {
6697
+ const filePath = join23(projectDir, "_bmad", relativePath);
6698
+ if (!existsSync24(filePath)) {
5949
6699
  continue;
5950
6700
  }
5951
6701
  try {
@@ -5965,10 +6715,10 @@ function registerTeardownCommand(program) {
5965
6715
  }
5966
6716
  }
5967
6717
  if (state.otlp?.enabled && state.stack === "nodejs") {
5968
- const pkgPath = join19(projectDir, "package.json");
5969
- if (existsSync20(pkgPath)) {
6718
+ const pkgPath = join23(projectDir, "package.json");
6719
+ if (existsSync24(pkgPath)) {
5970
6720
  try {
5971
- const raw = readFileSync17(pkgPath, "utf-8");
6721
+ const raw = readFileSync21(pkgPath, "utf-8");
5972
6722
  const pkg = JSON.parse(raw);
5973
6723
  const scripts = pkg["scripts"];
5974
6724
  if (scripts) {
@@ -5982,7 +6732,7 @@ function registerTeardownCommand(program) {
5982
6732
  for (const key of keysToRemove) {
5983
6733
  delete scripts[key];
5984
6734
  }
5985
- writeFileSync11(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
6735
+ writeFileSync14(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
5986
6736
  result.otlp_cleaned = true;
5987
6737
  if (!isJson) {
5988
6738
  ok("OTLP: removed instrumented scripts from package.json");
@@ -6008,17 +6758,17 @@ function registerTeardownCommand(program) {
6008
6758
  }
6009
6759
  }
6010
6760
  }
6011
- const harnessDir = join19(projectDir, ".harness");
6012
- if (existsSync20(harnessDir)) {
6761
+ const harnessDir = join23(projectDir, ".harness");
6762
+ if (existsSync24(harnessDir)) {
6013
6763
  rmSync(harnessDir, { recursive: true, force: true });
6014
6764
  result.removed.push(".harness/");
6015
6765
  if (!isJson) {
6016
6766
  ok("Removed: .harness/");
6017
6767
  }
6018
6768
  }
6019
- const statePath = getStatePath(projectDir);
6020
- if (existsSync20(statePath)) {
6021
- unlinkSync2(statePath);
6769
+ const statePath2 = getStatePath(projectDir);
6770
+ if (existsSync24(statePath2)) {
6771
+ unlinkSync2(statePath2);
6022
6772
  result.removed.push(".claude/codeharness.local.md");
6023
6773
  if (!isJson) {
6024
6774
  ok("Removed: .claude/codeharness.local.md");
@@ -6761,8 +7511,8 @@ function registerQueryCommand(program) {
6761
7511
  }
6762
7512
 
6763
7513
  // src/commands/retro-import.ts
6764
- import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
6765
- import { join as join20 } from "path";
7514
+ import { existsSync as existsSync25, readFileSync as readFileSync22 } from "fs";
7515
+ import { join as join24 } from "path";
6766
7516
 
6767
7517
  // src/lib/retro-parser.ts
6768
7518
  var KNOWN_TOOLS = ["showboat", "ralph", "beads", "bmad"];
@@ -6931,15 +7681,15 @@ function registerRetroImportCommand(program) {
6931
7681
  return;
6932
7682
  }
6933
7683
  const retroFile = `epic-${epicNum}-retrospective.md`;
6934
- const retroPath = join20(root, STORY_DIR2, retroFile);
6935
- if (!existsSync21(retroPath)) {
7684
+ const retroPath = join24(root, STORY_DIR2, retroFile);
7685
+ if (!existsSync25(retroPath)) {
6936
7686
  fail(`Retro file not found: ${retroFile}`, { json: isJson });
6937
7687
  process.exitCode = 1;
6938
7688
  return;
6939
7689
  }
6940
7690
  let content;
6941
7691
  try {
6942
- content = readFileSync18(retroPath, "utf-8");
7692
+ content = readFileSync22(retroPath, "utf-8");
6943
7693
  } catch (err) {
6944
7694
  const message = err instanceof Error ? err.message : String(err);
6945
7695
  fail(`Failed to read retro file: ${message}`, { json: isJson });
@@ -7207,8 +7957,8 @@ function registerGithubImportCommand(program) {
7207
7957
 
7208
7958
  // src/lib/verify-env.ts
7209
7959
  import { execFileSync as execFileSync7 } from "child_process";
7210
- import { existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync4, readFileSync as readFileSync19, cpSync, rmSync as rmSync2, statSync as statSync3 } from "fs";
7211
- import { join as join21, basename as basename3 } from "path";
7960
+ import { existsSync as existsSync26, mkdirSync as mkdirSync9, readdirSync as readdirSync4, readFileSync as readFileSync23, cpSync, rmSync as rmSync2, statSync as statSync3 } from "fs";
7961
+ import { join as join25, basename as basename3 } from "path";
7212
7962
  import { createHash } from "crypto";
7213
7963
  var IMAGE_TAG = "codeharness-verify";
7214
7964
  var STORY_DIR3 = "_bmad-output/implementation-artifacts";
@@ -7221,14 +7971,14 @@ function isValidStoryKey(storyKey) {
7221
7971
  return /^[a-zA-Z0-9_-]+$/.test(storyKey);
7222
7972
  }
7223
7973
  function computeDistHash(projectDir) {
7224
- const distDir = join21(projectDir, "dist");
7225
- if (!existsSync22(distDir)) {
7974
+ const distDir = join25(projectDir, "dist");
7975
+ if (!existsSync26(distDir)) {
7226
7976
  return null;
7227
7977
  }
7228
7978
  const hash = createHash("sha256");
7229
7979
  const files = collectFiles(distDir).sort();
7230
7980
  for (const file of files) {
7231
- const content = readFileSync19(file);
7981
+ const content = readFileSync23(file);
7232
7982
  hash.update(file.slice(distDir.length));
7233
7983
  hash.update(content);
7234
7984
  }
@@ -7238,7 +7988,7 @@ function collectFiles(dir) {
7238
7988
  const results = [];
7239
7989
  const entries = readdirSync4(dir, { withFileTypes: true });
7240
7990
  for (const entry of entries) {
7241
- const fullPath = join21(dir, entry.name);
7991
+ const fullPath = join25(dir, entry.name);
7242
7992
  if (entry.isDirectory()) {
7243
7993
  results.push(...collectFiles(fullPath));
7244
7994
  } else {
@@ -7310,13 +8060,13 @@ function buildNodeImage(projectDir) {
7310
8060
  throw new Error("npm pack produced no output \u2014 cannot determine tarball filename.");
7311
8061
  }
7312
8062
  const tarballName = basename3(lastLine);
7313
- const tarballPath = join21("/tmp", tarballName);
7314
- const buildContext = join21("/tmp", `codeharness-verify-build-${Date.now()}`);
7315
- mkdirSync8(buildContext, { recursive: true });
8063
+ const tarballPath = join25("/tmp", tarballName);
8064
+ const buildContext = join25("/tmp", `codeharness-verify-build-${Date.now()}`);
8065
+ mkdirSync9(buildContext, { recursive: true });
7316
8066
  try {
7317
- cpSync(tarballPath, join21(buildContext, tarballName));
8067
+ cpSync(tarballPath, join25(buildContext, tarballName));
7318
8068
  const dockerfileSrc = resolveDockerfileTemplate(projectDir);
7319
- cpSync(dockerfileSrc, join21(buildContext, "Dockerfile"));
8069
+ cpSync(dockerfileSrc, join25(buildContext, "Dockerfile"));
7320
8070
  execFileSync7("docker", [
7321
8071
  "build",
7322
8072
  "-t",
@@ -7335,7 +8085,7 @@ function buildNodeImage(projectDir) {
7335
8085
  }
7336
8086
  }
7337
8087
  function buildPythonImage(projectDir) {
7338
- const distDir = join21(projectDir, "dist");
8088
+ const distDir = join25(projectDir, "dist");
7339
8089
  const distFiles = readdirSync4(distDir).filter(
7340
8090
  (f) => f.endsWith(".tar.gz") || f.endsWith(".whl")
7341
8091
  );
@@ -7343,12 +8093,12 @@ function buildPythonImage(projectDir) {
7343
8093
  throw new Error("No distribution files found in dist/. Run your build command first (e.g., python -m build).");
7344
8094
  }
7345
8095
  const distFile = distFiles.filter((f) => f.endsWith(".tar.gz"))[0] ?? distFiles[0];
7346
- const buildContext = join21("/tmp", `codeharness-verify-build-${Date.now()}`);
7347
- mkdirSync8(buildContext, { recursive: true });
8096
+ const buildContext = join25("/tmp", `codeharness-verify-build-${Date.now()}`);
8097
+ mkdirSync9(buildContext, { recursive: true });
7348
8098
  try {
7349
- cpSync(join21(distDir, distFile), join21(buildContext, distFile));
8099
+ cpSync(join25(distDir, distFile), join25(buildContext, distFile));
7350
8100
  const dockerfileSrc = resolveDockerfileTemplate(projectDir);
7351
- cpSync(dockerfileSrc, join21(buildContext, "Dockerfile"));
8101
+ cpSync(dockerfileSrc, join25(buildContext, "Dockerfile"));
7352
8102
  execFileSync7("docker", [
7353
8103
  "build",
7354
8104
  "-t",
@@ -7370,25 +8120,25 @@ function prepareVerifyWorkspace(storyKey, projectDir) {
7370
8120
  if (!isValidStoryKey(storyKey)) {
7371
8121
  throw new Error(`Invalid story key: ${storyKey}. Keys must contain only alphanumeric characters, hyphens, and underscores.`);
7372
8122
  }
7373
- const storyFile = join21(root, STORY_DIR3, `${storyKey}.md`);
7374
- if (!existsSync22(storyFile)) {
8123
+ const storyFile = join25(root, STORY_DIR3, `${storyKey}.md`);
8124
+ if (!existsSync26(storyFile)) {
7375
8125
  throw new Error(`Story file not found: ${storyFile}`);
7376
8126
  }
7377
8127
  const workspace = `${TEMP_PREFIX}${storyKey}`;
7378
- if (existsSync22(workspace)) {
8128
+ if (existsSync26(workspace)) {
7379
8129
  rmSync2(workspace, { recursive: true, force: true });
7380
8130
  }
7381
- mkdirSync8(workspace, { recursive: true });
7382
- cpSync(storyFile, join21(workspace, "story.md"));
7383
- const readmePath = join21(root, "README.md");
7384
- if (existsSync22(readmePath)) {
7385
- cpSync(readmePath, join21(workspace, "README.md"));
8131
+ mkdirSync9(workspace, { recursive: true });
8132
+ cpSync(storyFile, join25(workspace, "story.md"));
8133
+ const readmePath = join25(root, "README.md");
8134
+ if (existsSync26(readmePath)) {
8135
+ cpSync(readmePath, join25(workspace, "README.md"));
7386
8136
  }
7387
- const docsDir = join21(root, "docs");
7388
- if (existsSync22(docsDir) && statSync3(docsDir).isDirectory()) {
7389
- cpSync(docsDir, join21(workspace, "docs"), { recursive: true });
8137
+ const docsDir = join25(root, "docs");
8138
+ if (existsSync26(docsDir) && statSync3(docsDir).isDirectory()) {
8139
+ cpSync(docsDir, join25(workspace, "docs"), { recursive: true });
7390
8140
  }
7391
- mkdirSync8(join21(workspace, "verification"), { recursive: true });
8141
+ mkdirSync9(join25(workspace, "verification"), { recursive: true });
7392
8142
  return workspace;
7393
8143
  }
7394
8144
  function checkVerifyEnv() {
@@ -7437,7 +8187,7 @@ function cleanupVerifyEnv(storyKey) {
7437
8187
  }
7438
8188
  const workspace = `${TEMP_PREFIX}${storyKey}`;
7439
8189
  const containerName = `codeharness-verify-${storyKey}`;
7440
- if (existsSync22(workspace)) {
8190
+ if (existsSync26(workspace)) {
7441
8191
  rmSync2(workspace, { recursive: true, force: true });
7442
8192
  }
7443
8193
  try {
@@ -7456,11 +8206,11 @@ function cleanupVerifyEnv(storyKey) {
7456
8206
  }
7457
8207
  }
7458
8208
  function resolveDockerfileTemplate(projectDir) {
7459
- const local = join21(projectDir, "templates", "Dockerfile.verify");
7460
- if (existsSync22(local)) return local;
8209
+ const local = join25(projectDir, "templates", "Dockerfile.verify");
8210
+ if (existsSync26(local)) return local;
7461
8211
  const pkgDir = new URL("../../", import.meta.url).pathname;
7462
- const pkg = join21(pkgDir, "templates", "Dockerfile.verify");
7463
- if (existsSync22(pkg)) return pkg;
8212
+ const pkg = join25(pkgDir, "templates", "Dockerfile.verify");
8213
+ if (existsSync26(pkg)) return pkg;
7464
8214
  throw new Error("Dockerfile.verify not found. Ensure templates/Dockerfile.verify exists in the project or installed package.");
7465
8215
  }
7466
8216
  function dockerImageExists(tag) {
@@ -7606,26 +8356,26 @@ function registerVerifyEnvCommand(program) {
7606
8356
  }
7607
8357
 
7608
8358
  // src/commands/retry.ts
7609
- import { join as join23 } from "path";
8359
+ import { join as join27 } from "path";
7610
8360
 
7611
8361
  // src/lib/retry-state.ts
7612
- import { existsSync as existsSync23, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
7613
- import { join as join22 } from "path";
8362
+ import { existsSync as existsSync27, readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "fs";
8363
+ import { join as join26 } from "path";
7614
8364
  var RETRIES_FILE = ".story_retries";
7615
8365
  var FLAGGED_FILE = ".flagged_stories";
7616
8366
  var LINE_PATTERN = /^([^=]+)=(\d+)$/;
7617
8367
  function retriesPath(dir) {
7618
- return join22(dir, RETRIES_FILE);
8368
+ return join26(dir, RETRIES_FILE);
7619
8369
  }
7620
8370
  function flaggedPath(dir) {
7621
- return join22(dir, FLAGGED_FILE);
8371
+ return join26(dir, FLAGGED_FILE);
7622
8372
  }
7623
8373
  function readRetries(dir) {
7624
8374
  const filePath = retriesPath(dir);
7625
- if (!existsSync23(filePath)) {
8375
+ if (!existsSync27(filePath)) {
7626
8376
  return /* @__PURE__ */ new Map();
7627
8377
  }
7628
- const raw = readFileSync20(filePath, "utf-8");
8378
+ const raw = readFileSync24(filePath, "utf-8");
7629
8379
  const result = /* @__PURE__ */ new Map();
7630
8380
  for (const line of raw.split("\n")) {
7631
8381
  const trimmed = line.trim();
@@ -7647,7 +8397,7 @@ function writeRetries(dir, retries) {
7647
8397
  for (const [key, count] of retries) {
7648
8398
  lines.push(`${key}=${count}`);
7649
8399
  }
7650
- writeFileSync12(filePath, lines.length > 0 ? lines.join("\n") + "\n" : "", "utf-8");
8400
+ writeFileSync15(filePath, lines.length > 0 ? lines.join("\n") + "\n" : "", "utf-8");
7651
8401
  }
7652
8402
  function resetRetry(dir, storyKey) {
7653
8403
  if (storyKey) {
@@ -7662,15 +8412,15 @@ function resetRetry(dir, storyKey) {
7662
8412
  }
7663
8413
  function readFlaggedStories(dir) {
7664
8414
  const filePath = flaggedPath(dir);
7665
- if (!existsSync23(filePath)) {
8415
+ if (!existsSync27(filePath)) {
7666
8416
  return [];
7667
8417
  }
7668
- const raw = readFileSync20(filePath, "utf-8");
8418
+ const raw = readFileSync24(filePath, "utf-8");
7669
8419
  return raw.split("\n").map((l) => l.trim()).filter((l) => l !== "");
7670
8420
  }
7671
8421
  function writeFlaggedStories(dir, stories) {
7672
8422
  const filePath = flaggedPath(dir);
7673
- writeFileSync12(filePath, stories.length > 0 ? stories.join("\n") + "\n" : "", "utf-8");
8423
+ writeFileSync15(filePath, stories.length > 0 ? stories.join("\n") + "\n" : "", "utf-8");
7674
8424
  }
7675
8425
  function removeFlaggedStory(dir, key) {
7676
8426
  const stories = readFlaggedStories(dir);
@@ -7690,7 +8440,7 @@ function registerRetryCommand(program) {
7690
8440
  program.command("retry").description("Manage retry state for stories").option("--reset", "Clear retry counters and flagged stories").option("--story <key>", "Target a specific story key (used with --reset or --status)").option("--status", "Show retry status for all stories").action((_options, cmd) => {
7691
8441
  const opts = cmd.optsWithGlobals();
7692
8442
  const isJson = opts.json === true;
7693
- const dir = join23(process.cwd(), RALPH_SUBDIR);
8443
+ const dir = join27(process.cwd(), RALPH_SUBDIR);
7694
8444
  if (opts.story && !isValidStoryKey3(opts.story)) {
7695
8445
  if (isJson) {
7696
8446
  jsonOutput({ status: "fail", message: `Invalid story key: ${opts.story}` });
@@ -7763,8 +8513,53 @@ function handleStatus(dir, isJson, filterStory) {
7763
8513
  }
7764
8514
  }
7765
8515
 
8516
+ // src/commands/timeout-report.ts
8517
+ function registerTimeoutReportCommand(program) {
8518
+ program.command("timeout-report").description("Capture diagnostic data from a timed-out iteration").requiredOption("--story <key>", "Story key").requiredOption("--iteration <n>", "Iteration number").requiredOption("--duration <minutes>", "Timeout duration in minutes").requiredOption("--output-file <path>", "Path to iteration output log").requiredOption("--state-snapshot <path>", "Path to pre-iteration state snapshot").action((options, cmd) => {
8519
+ const opts = cmd.optsWithGlobals();
8520
+ const isJson = opts.json === true;
8521
+ const iteration = parseInt(options.iteration, 10);
8522
+ const duration = parseInt(options.duration, 10);
8523
+ if (isNaN(iteration) || isNaN(duration)) {
8524
+ if (isJson) {
8525
+ jsonOutput({ status: "fail", message: "iteration and duration must be numbers" });
8526
+ } else {
8527
+ fail("iteration and duration must be numbers");
8528
+ }
8529
+ process.exitCode = 1;
8530
+ return;
8531
+ }
8532
+ const result = captureTimeoutReport2({
8533
+ storyKey: options.story,
8534
+ iteration,
8535
+ durationMinutes: duration,
8536
+ outputFile: options.outputFile,
8537
+ stateSnapshotPath: options.stateSnapshot
8538
+ });
8539
+ if (!result.success) {
8540
+ if (isJson) {
8541
+ jsonOutput({ status: "fail", message: result.error });
8542
+ } else {
8543
+ fail(result.error);
8544
+ }
8545
+ process.exitCode = 1;
8546
+ return;
8547
+ }
8548
+ if (isJson) {
8549
+ jsonOutput({
8550
+ status: "ok",
8551
+ reportPath: result.data.filePath,
8552
+ storyKey: result.data.capture.storyKey,
8553
+ iteration: result.data.capture.iteration
8554
+ });
8555
+ } else {
8556
+ ok(`Timeout report written: ${result.data.filePath}`);
8557
+ }
8558
+ });
8559
+ }
8560
+
7766
8561
  // src/index.ts
7767
- var VERSION = true ? "0.18.1" : "0.0.0-dev";
8562
+ var VERSION = true ? "0.19.2" : "0.0.0-dev";
7768
8563
  function createProgram() {
7769
8564
  const program = new Command();
7770
8565
  program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");
@@ -7785,6 +8580,7 @@ function createProgram() {
7785
8580
  registerGithubImportCommand(program);
7786
8581
  registerVerifyEnvCommand(program);
7787
8582
  registerRetryCommand(program);
8583
+ registerTimeoutReportCommand(program);
7788
8584
  return program;
7789
8585
  }
7790
8586
  if (!process.env["VITEST"]) {