codeharness 0.25.5 → 0.25.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -51,7 +51,7 @@ import {
51
51
  validateDockerfile,
52
52
  warn,
53
53
  writeState
54
- } from "./chunk-ZFM7IU3A.js";
54
+ } from "./chunk-6RL5EK57.js";
55
55
 
56
56
  // src/index.ts
57
57
  import { Command } from "commander";
@@ -169,600 +169,254 @@ function registerBridgeCommand(program) {
169
169
  }
170
170
 
171
171
  // src/commands/run.ts
172
- import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
173
- import { join as join8, dirname as dirname3 } from "path";
172
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
173
+ import { join as join6, dirname as dirname3 } from "path";
174
174
  import { StringDecoder as StringDecoder2 } from "string_decoder";
175
175
 
176
- // src/lib/sync/story-files.ts
177
- import { existsSync as existsSync2, readFileSync, writeFileSync } from "fs";
178
- var BEADS_TO_STORY_STATUS = {
179
- open: "in-progress",
180
- closed: "done"
181
- };
182
- var STORY_TO_BEADS_STATUS = {
183
- backlog: "open",
184
- "ready-for-dev": "open",
185
- "in-progress": "open",
186
- review: "open",
187
- done: "closed"
176
+ // src/modules/sprint/state.ts
177
+ import { readFileSync as readFileSync2, writeFileSync, renameSync, existsSync as existsSync3, unlinkSync } from "fs";
178
+ import { join as join2, dirname } from "path";
179
+
180
+ // src/modules/sprint/migration.ts
181
+ import { readFileSync, existsSync as existsSync2 } from "fs";
182
+ import { join } from "path";
183
+ var OLD_FILES = {
184
+ storyRetries: "ralph/.story_retries",
185
+ flaggedStories: "ralph/.flagged_stories",
186
+ ralphStatus: "ralph/status.json",
187
+ sprintStatusYaml: "_bmad-output/implementation-artifacts/sprint-status.yaml",
188
+ sessionIssues: "_bmad-output/implementation-artifacts/.session-issues.md"
188
189
  };
189
- function beadsStatusToStoryStatus(beadsStatus) {
190
- return BEADS_TO_STORY_STATUS[beadsStatus] ?? null;
191
- }
192
- function storyStatusToBeadsStatus(storyStatus) {
193
- return STORY_TO_BEADS_STATUS[storyStatus] ?? null;
194
- }
195
- function storyKeyFromPath(filePath) {
196
- const base = filePath.split("/").pop() ?? filePath;
197
- return base.replace(/\.md$/, "");
190
+ function resolve(relative3) {
191
+ return join(process.cwd(), relative3);
198
192
  }
199
- function resolveStoryFilePath(beadsIssue) {
200
- const desc = beadsIssue.description;
201
- if (!desc || !desc.trim()) {
202
- return null;
203
- }
204
- const trimmed = desc.trim();
205
- if (!trimmed.endsWith(".md")) {
193
+ function readIfExists(relative3) {
194
+ const p = resolve(relative3);
195
+ if (!existsSync2(p)) return null;
196
+ try {
197
+ return readFileSync(p, "utf-8");
198
+ } catch {
206
199
  return null;
207
200
  }
208
- return trimmed;
209
201
  }
210
- function readStoryFileStatus(filePath) {
211
- if (!existsSync2(filePath)) {
212
- return null;
213
- }
214
- const content = readFileSync(filePath, "utf-8");
215
- const match = content.match(/^#{0,2}\s*Status:\s*(.+)$/m);
216
- if (!match) {
217
- return null;
218
- }
219
- return match[1].trim();
202
+ function emptyStory() {
203
+ return {
204
+ status: "backlog",
205
+ attempts: 0,
206
+ lastAttempt: null,
207
+ lastError: null,
208
+ proofPath: null,
209
+ acResults: null
210
+ };
220
211
  }
221
- function updateStoryFileStatus(filePath, newStatus) {
222
- const content = readFileSync(filePath, "utf-8");
223
- const statusRegex = /^(#{0,2}\s*)Status:\s*.+$/m;
224
- if (statusRegex.test(content)) {
225
- const updated = content.replace(statusRegex, `$1Status: ${newStatus}`);
226
- writeFileSync(filePath, updated, "utf-8");
227
- } else {
228
- const lines = content.split("\n");
229
- const titleIndex = lines.findIndex((l) => l.startsWith("# "));
230
- if (titleIndex !== -1) {
231
- lines.splice(titleIndex + 1, 0, "", `Status: ${newStatus}`);
232
- } else {
233
- lines.unshift(`Status: ${newStatus}`, "");
234
- }
235
- writeFileSync(filePath, lines.join("\n"), "utf-8");
212
+ function upsertStory(stories, key, patch) {
213
+ stories[key] = { ...stories[key] ?? emptyStory(), ...patch };
214
+ }
215
+ function parseStoryRetries(content, stories) {
216
+ for (const line of content.split("\n")) {
217
+ const trimmed = line.trim();
218
+ if (!trimmed) continue;
219
+ const parts = trimmed.split(/\s+/);
220
+ if (parts.length < 2) continue;
221
+ const count = parseInt(parts[1], 10);
222
+ if (!isNaN(count)) upsertStory(stories, parts[0], { attempts: count });
236
223
  }
237
224
  }
238
-
239
- // src/lib/sync/sprint-yaml.ts
240
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
241
- import { join } from "path";
242
- import { parse } from "yaml";
243
- var SPRINT_STATUS_PATH = "_bmad-output/implementation-artifacts/sprint-status.yaml";
244
- function readSprintStatus(dir) {
245
- const root = dir ?? process.cwd();
246
- const filePath = join(root, SPRINT_STATUS_PATH);
247
- if (!existsSync3(filePath)) {
248
- return {};
225
+ function parseStoryRetriesRecord(content) {
226
+ const result = {};
227
+ for (const line of content.split("\n")) {
228
+ const trimmed = line.trim();
229
+ if (!trimmed) continue;
230
+ const parts = trimmed.split(/\s+/);
231
+ if (parts.length < 2) continue;
232
+ const count = parseInt(parts[1], 10);
233
+ if (!isNaN(count) && count >= 0) result[parts[0]] = count;
249
234
  }
250
- try {
251
- const content = readFileSync2(filePath, "utf-8");
252
- const parsed = parse(content);
253
- if (!parsed || typeof parsed !== "object") {
254
- return {};
255
- }
256
- const devStatus = parsed.development_status;
257
- if (!devStatus || typeof devStatus !== "object") {
258
- return {};
235
+ return result;
236
+ }
237
+ function parseFlaggedStoriesList(content) {
238
+ const seen = /* @__PURE__ */ new Set();
239
+ const result = [];
240
+ for (const line of content.split("\n")) {
241
+ const trimmed = line.trim();
242
+ if (trimmed !== "" && !seen.has(trimmed)) {
243
+ seen.add(trimmed);
244
+ result.push(trimmed);
259
245
  }
260
- return devStatus;
261
- } catch {
262
- return {};
263
246
  }
247
+ return result;
264
248
  }
265
- function updateSprintStatus(storyKey, newStatus, dir) {
266
- const root = dir ?? process.cwd();
267
- const filePath = join(root, SPRINT_STATUS_PATH);
268
- if (!existsSync3(filePath)) {
269
- warn(`sprint-status.yaml not found at ${filePath}, skipping update`);
270
- return;
271
- }
272
- const content = readFileSync2(filePath, "utf-8");
273
- const keyPattern = new RegExp(`^(\\s*${escapeRegExp(storyKey)}:\\s*)\\S+(.*)$`, "m");
274
- if (!keyPattern.test(content)) {
275
- return;
249
+ function parseFlaggedStories(content, stories) {
250
+ for (const line of content.split("\n")) {
251
+ const key = line.trim();
252
+ if (key) upsertStory(stories, key, { status: "blocked" });
276
253
  }
277
- const updated = content.replace(keyPattern, `$1${newStatus}$2`);
278
- writeFileSync2(filePath, updated, "utf-8");
279
254
  }
280
- function escapeRegExp(s) {
281
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
255
+ function mapYamlStatus(value) {
256
+ const mapping = {
257
+ done: "done",
258
+ backlog: "backlog",
259
+ verifying: "verifying",
260
+ "in-progress": "in-progress",
261
+ "ready-for-dev": "ready",
262
+ blocked: "blocked",
263
+ failed: "failed",
264
+ review: "review",
265
+ ready: "ready"
266
+ };
267
+ return mapping[value.trim().toLowerCase()] ?? null;
282
268
  }
283
-
284
- // src/lib/sync/beads.ts
285
- import { existsSync as existsSync4 } from "fs";
286
- import { join as join2 } from "path";
287
- function syncBeadsToStoryFile(beadsId, beadsFns, dir) {
288
- const root = dir ?? process.cwd();
289
- const issues = beadsFns.listIssues();
290
- const issue = issues.find((i) => i.id === beadsId);
291
- if (!issue) {
292
- return {
293
- storyKey: "",
294
- beadsId,
295
- previousStatus: "",
296
- newStatus: "",
297
- synced: false,
298
- error: `Beads issue not found: ${beadsId}`
299
- };
300
- }
301
- const storyFilePath = resolveStoryFilePath(issue);
302
- if (!storyFilePath) {
303
- return {
304
- storyKey: "",
305
- beadsId,
306
- previousStatus: issue.status,
307
- newStatus: "",
308
- synced: false,
309
- error: `No story file path in beads issue description: ${beadsId}`
310
- };
311
- }
312
- const storyKey = storyKeyFromPath(storyFilePath);
313
- const fullPath = join2(root, storyFilePath);
314
- const currentStoryStatus = readStoryFileStatus(fullPath);
315
- if (currentStoryStatus === null) {
316
- return {
317
- storyKey,
318
- beadsId,
319
- previousStatus: issue.status,
320
- newStatus: "",
321
- synced: false,
322
- error: `Story file not found or has no Status line: ${storyFilePath}`
323
- };
324
- }
325
- const targetStoryStatus = beadsStatusToStoryStatus(issue.status);
326
- if (!targetStoryStatus) {
327
- return {
328
- storyKey,
329
- beadsId,
330
- previousStatus: currentStoryStatus,
331
- newStatus: "",
332
- synced: false,
333
- error: `Unknown beads status: ${issue.status}`
334
- };
269
+ function parseSprintStatusYaml(content, stories) {
270
+ for (const line of content.split("\n")) {
271
+ const trimmed = line.trim();
272
+ if (!trimmed || trimmed.startsWith("#")) continue;
273
+ const match = trimmed.match(/^([a-zA-Z0-9_-]+):\s*(.+)$/);
274
+ if (!match) continue;
275
+ const key = match[1];
276
+ if (key === "development_status" || key.startsWith("epic-")) continue;
277
+ const status = mapYamlStatus(match[2]);
278
+ if (status) upsertStory(stories, key, { status });
335
279
  }
336
- if (currentStoryStatus === targetStoryStatus) {
280
+ }
281
+ function parseRalphStatus(content) {
282
+ try {
283
+ const data = JSON.parse(content);
337
284
  return {
338
- storyKey,
339
- beadsId,
340
- previousStatus: currentStoryStatus,
341
- newStatus: currentStoryStatus,
342
- synced: false
285
+ active: data.status === "running",
286
+ startedAt: null,
287
+ iteration: data.loop_count ?? 0,
288
+ cost: 0,
289
+ completed: [],
290
+ failed: [],
291
+ currentStory: null,
292
+ currentPhase: null,
293
+ lastAction: null,
294
+ acProgress: null
343
295
  };
296
+ } catch {
297
+ return null;
344
298
  }
345
- updateStoryFileStatus(fullPath, targetStoryStatus);
346
- updateSprintStatus(storyKey, targetStoryStatus, root);
347
- return {
348
- storyKey,
349
- beadsId,
350
- previousStatus: currentStoryStatus,
351
- newStatus: targetStoryStatus,
352
- synced: true
353
- };
354
299
  }
355
- function syncStoryFileToBeads(storyKey, beadsFns, dir) {
356
- const root = dir ?? process.cwd();
357
- const storyFilePath = `_bmad-output/implementation-artifacts/${storyKey}.md`;
358
- const fullPath = join2(root, storyFilePath);
359
- const currentStoryStatus = readStoryFileStatus(fullPath);
360
- if (currentStoryStatus === null) {
300
+ function parseRalphStatusToSession(content) {
301
+ try {
302
+ const data = JSON.parse(content);
361
303
  return {
362
- storyKey,
363
- beadsId: "",
364
- previousStatus: "",
365
- newStatus: "",
366
- synced: false,
367
- error: `Story file not found or has no Status line: ${storyFilePath}`
304
+ active: data.status === "running",
305
+ startedAt: null,
306
+ iteration: data.loop_count ?? 0,
307
+ elapsedSeconds: data.elapsed_seconds ?? 0
368
308
  };
309
+ } catch {
310
+ return null;
369
311
  }
370
- const issues = beadsFns.listIssues();
371
- const issue = issues.find((i) => {
372
- const path = resolveStoryFilePath(i);
373
- if (!path) return false;
374
- return storyKeyFromPath(path) === storyKey;
375
- });
376
- if (!issue) {
377
- return {
378
- storyKey,
379
- beadsId: "",
380
- previousStatus: currentStoryStatus,
381
- newStatus: "",
382
- synced: false,
383
- error: `No beads issue found for story: ${storyKey}`
384
- };
385
- }
386
- const targetBeadsStatus = storyStatusToBeadsStatus(currentStoryStatus);
387
- if (!targetBeadsStatus) {
388
- return {
389
- storyKey,
390
- beadsId: issue.id,
391
- previousStatus: currentStoryStatus,
392
- newStatus: "",
393
- synced: false,
394
- error: `Unknown story status: ${currentStoryStatus}`
395
- };
396
- }
397
- if (issue.status === targetBeadsStatus) {
398
- return {
399
- storyKey,
400
- beadsId: issue.id,
401
- previousStatus: currentStoryStatus,
402
- newStatus: currentStoryStatus,
403
- synced: false
404
- };
405
- }
406
- if (targetBeadsStatus === "closed") {
407
- beadsFns.closeIssue(issue.id);
408
- } else {
409
- beadsFns.updateIssue(issue.id, { status: targetBeadsStatus });
410
- }
411
- updateSprintStatus(storyKey, currentStoryStatus, root);
412
- return {
413
- storyKey,
414
- beadsId: issue.id,
415
- previousStatus: issue.status,
416
- newStatus: targetBeadsStatus,
417
- synced: true
418
- };
419
312
  }
420
- function syncClose(beadsId, beadsFns, dir) {
421
- const root = dir ?? process.cwd();
422
- const issues = beadsFns.listIssues();
423
- const issue = issues.find((i) => i.id === beadsId);
424
- beadsFns.closeIssue(beadsId);
425
- if (!issue) {
426
- return {
427
- storyKey: "",
428
- beadsId,
429
- previousStatus: "",
430
- newStatus: "closed",
431
- synced: false,
432
- error: `Beads issue not found: ${beadsId}`
433
- };
434
- }
435
- const storyFilePath = resolveStoryFilePath(issue);
436
- if (!storyFilePath) {
437
- return {
438
- storyKey: "",
439
- beadsId,
440
- previousStatus: issue.status,
441
- newStatus: "closed",
442
- synced: false,
443
- error: `No story file path in beads issue description: ${beadsId}`
444
- };
445
- }
446
- const storyKey = storyKeyFromPath(storyFilePath);
447
- const fullPath = join2(root, storyFilePath);
448
- const previousStatus = readStoryFileStatus(fullPath);
449
- if (previousStatus === null) {
450
- if (!existsSync4(fullPath)) {
451
- return {
452
- storyKey,
453
- beadsId,
454
- previousStatus: "",
455
- newStatus: "closed",
456
- synced: false,
457
- error: `Story file not found: ${storyFilePath}`
458
- };
313
+ function parseSessionIssues(content) {
314
+ const items = [];
315
+ let currentStory = "";
316
+ let itemId = 0;
317
+ for (const line of content.split("\n")) {
318
+ const headerMatch = line.match(/^###\s+([a-zA-Z0-9_-]+)\s*[—-]/);
319
+ if (headerMatch) {
320
+ currentStory = headerMatch[1];
321
+ continue;
322
+ }
323
+ const bulletMatch = line.match(/^-\s+(.+)/);
324
+ if (bulletMatch && currentStory) {
325
+ itemId++;
326
+ items.push({
327
+ id: `migrated-${itemId}`,
328
+ story: currentStory,
329
+ description: bulletMatch[1],
330
+ source: "retro",
331
+ resolved: false
332
+ });
459
333
  }
460
334
  }
461
- updateStoryFileStatus(fullPath, "done");
462
- updateSprintStatus(storyKey, "done", root);
335
+ return items;
336
+ }
337
+ function migrateV1ToV2(v1) {
338
+ const defaults = defaultState();
339
+ const retriesContent = readIfExists(OLD_FILES.storyRetries);
340
+ const retries = retriesContent ? parseStoryRetriesRecord(retriesContent) : {};
341
+ const flaggedContent = readIfExists(OLD_FILES.flaggedStories);
342
+ const flagged = flaggedContent ? parseFlaggedStoriesList(flaggedContent) : [];
343
+ const statusContent = readIfExists(OLD_FILES.ralphStatus);
344
+ const session = statusContent ? parseRalphStatusToSession(statusContent) ?? defaults.session : defaults.session;
463
345
  return {
464
- storyKey,
465
- beadsId,
466
- previousStatus: previousStatus ?? "",
467
- newStatus: "done",
468
- synced: true
346
+ version: 2,
347
+ sprint: v1.sprint,
348
+ stories: v1.stories,
349
+ retries,
350
+ flagged,
351
+ epics: {},
352
+ session,
353
+ observability: defaults.observability,
354
+ run: {
355
+ ...defaults.run,
356
+ ...v1.run
357
+ },
358
+ actionItems: v1.actionItems
469
359
  };
470
360
  }
471
- function syncAll(direction, beadsFns, dir) {
472
- const root = dir ?? process.cwd();
473
- const results = [];
474
- let issues;
361
+ function migrateFromOldFormat() {
362
+ const hasAnyOldFile = Object.values(OLD_FILES).some((rel) => existsSync2(resolve(rel)));
363
+ if (!hasAnyOldFile) return fail2("No old format files found for migration");
475
364
  try {
476
- issues = beadsFns.listIssues();
477
- } catch (err) {
478
- const message = err instanceof Error ? err.message : String(err);
479
- return [{
480
- storyKey: "",
481
- beadsId: "",
482
- previousStatus: "",
483
- newStatus: "",
484
- synced: false,
485
- error: `Failed to list beads issues: ${message}`
486
- }];
487
- }
488
- const cachedListIssues = () => issues;
489
- for (const issue of issues) {
490
- const storyFilePath = resolveStoryFilePath(issue);
491
- if (!storyFilePath) {
492
- continue;
493
- }
494
- const storyKey = storyKeyFromPath(storyFilePath);
495
- try {
496
- if (direction === "beads-to-files" || direction === "bidirectional") {
497
- const result = syncBeadsToStoryFile(issue.id, { listIssues: cachedListIssues }, root);
498
- results.push(result);
499
- } else if (direction === "files-to-beads") {
500
- const result = syncStoryFileToBeads(storyKey, {
501
- listIssues: cachedListIssues,
502
- updateIssue: beadsFns.updateIssue,
503
- closeIssue: beadsFns.closeIssue
504
- }, root);
505
- results.push(result);
506
- }
507
- } catch (err) {
508
- const message = err instanceof Error ? err.message : String(err);
509
- results.push({
510
- storyKey,
511
- beadsId: issue.id,
512
- previousStatus: "",
513
- newStatus: "",
514
- synced: false,
515
- error: message
516
- });
365
+ const stories = {};
366
+ let run = defaultState().run;
367
+ let actionItems = [];
368
+ const yamlContent = readIfExists(OLD_FILES.sprintStatusYaml);
369
+ if (yamlContent) parseSprintStatusYaml(yamlContent, stories);
370
+ const retriesContent = readIfExists(OLD_FILES.storyRetries);
371
+ if (retriesContent) parseStoryRetries(retriesContent, stories);
372
+ const flaggedContent = readIfExists(OLD_FILES.flaggedStories);
373
+ if (flaggedContent) parseFlaggedStories(flaggedContent, stories);
374
+ const statusContent = readIfExists(OLD_FILES.ralphStatus);
375
+ let session = defaultState().session;
376
+ if (statusContent) {
377
+ const parsed = parseRalphStatus(statusContent);
378
+ if (parsed) run = parsed;
379
+ const parsedSession = parseRalphStatusToSession(statusContent);
380
+ if (parsedSession) session = parsedSession;
517
381
  }
382
+ const issuesContent = readIfExists(OLD_FILES.sessionIssues);
383
+ if (issuesContent) actionItems = parseSessionIssues(issuesContent);
384
+ const sprint = computeSprintCounts(stories);
385
+ const retries = retriesContent ? parseStoryRetriesRecord(retriesContent) : {};
386
+ const flagged = flaggedContent ? parseFlaggedStoriesList(flaggedContent) : [];
387
+ const migrated = {
388
+ version: 2,
389
+ sprint,
390
+ stories,
391
+ retries,
392
+ flagged,
393
+ epics: {},
394
+ session,
395
+ observability: defaultState().observability,
396
+ run,
397
+ actionItems
398
+ };
399
+ const writeResult = writeStateAtomic(migrated);
400
+ if (!writeResult.success) return fail2(writeResult.error);
401
+ return ok2(migrated);
402
+ } catch (err) {
403
+ const msg = err instanceof Error ? err.message : String(err);
404
+ return fail2(`Migration failed: ${msg}`);
518
405
  }
519
- return results;
520
406
  }
521
407
 
522
408
  // src/modules/sprint/state.ts
523
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, renameSync, existsSync as existsSync6, unlinkSync } from "fs";
524
- import { join as join4, dirname } from "path";
525
-
526
- // src/modules/sprint/migration.ts
527
- import { readFileSync as readFileSync3, existsSync as existsSync5 } from "fs";
528
- import { join as join3 } from "path";
529
- var OLD_FILES = {
530
- storyRetries: "ralph/.story_retries",
531
- flaggedStories: "ralph/.flagged_stories",
532
- ralphStatus: "ralph/status.json",
533
- sprintStatusYaml: "_bmad-output/implementation-artifacts/sprint-status.yaml",
534
- sessionIssues: "_bmad-output/implementation-artifacts/.session-issues.md"
535
- };
536
- function resolve(relative3) {
537
- return join3(process.cwd(), relative3);
409
+ function projectRoot() {
410
+ return process.cwd();
538
411
  }
539
- function readIfExists(relative3) {
540
- const p = resolve(relative3);
541
- if (!existsSync5(p)) return null;
542
- try {
543
- return readFileSync3(p, "utf-8");
544
- } catch {
545
- return null;
546
- }
412
+ function statePath() {
413
+ return join2(projectRoot(), "sprint-state.json");
547
414
  }
548
- function emptyStory() {
549
- return {
550
- status: "backlog",
551
- attempts: 0,
552
- lastAttempt: null,
553
- lastError: null,
554
- proofPath: null,
555
- acResults: null
556
- };
415
+ function tmpPath() {
416
+ return join2(projectRoot(), ".sprint-state.json.tmp");
557
417
  }
558
- function upsertStory(stories, key, patch) {
559
- stories[key] = { ...stories[key] ?? emptyStory(), ...patch };
560
- }
561
- function parseStoryRetries(content, stories) {
562
- for (const line of content.split("\n")) {
563
- const trimmed = line.trim();
564
- if (!trimmed) continue;
565
- const parts = trimmed.split(/\s+/);
566
- if (parts.length < 2) continue;
567
- const count = parseInt(parts[1], 10);
568
- if (!isNaN(count)) upsertStory(stories, parts[0], { attempts: count });
569
- }
570
- }
571
- function parseStoryRetriesRecord(content) {
572
- const result = {};
573
- for (const line of content.split("\n")) {
574
- const trimmed = line.trim();
575
- if (!trimmed) continue;
576
- const parts = trimmed.split(/\s+/);
577
- if (parts.length < 2) continue;
578
- const count = parseInt(parts[1], 10);
579
- if (!isNaN(count) && count >= 0) result[parts[0]] = count;
580
- }
581
- return result;
582
- }
583
- function parseFlaggedStoriesList(content) {
584
- const seen = /* @__PURE__ */ new Set();
585
- const result = [];
586
- for (const line of content.split("\n")) {
587
- const trimmed = line.trim();
588
- if (trimmed !== "" && !seen.has(trimmed)) {
589
- seen.add(trimmed);
590
- result.push(trimmed);
591
- }
592
- }
593
- return result;
594
- }
595
- function parseFlaggedStories(content, stories) {
596
- for (const line of content.split("\n")) {
597
- const key = line.trim();
598
- if (key) upsertStory(stories, key, { status: "blocked" });
599
- }
600
- }
601
- function mapYamlStatus(value) {
602
- const mapping = {
603
- done: "done",
604
- backlog: "backlog",
605
- verifying: "verifying",
606
- "in-progress": "in-progress",
607
- "ready-for-dev": "ready",
608
- blocked: "blocked",
609
- failed: "failed",
610
- review: "review",
611
- ready: "ready"
612
- };
613
- return mapping[value.trim().toLowerCase()] ?? null;
614
- }
615
- function parseSprintStatusYaml(content, stories) {
616
- for (const line of content.split("\n")) {
617
- const trimmed = line.trim();
618
- if (!trimmed || trimmed.startsWith("#")) continue;
619
- const match = trimmed.match(/^([a-zA-Z0-9_-]+):\s*(.+)$/);
620
- if (!match) continue;
621
- const key = match[1];
622
- if (key === "development_status" || key.startsWith("epic-")) continue;
623
- const status = mapYamlStatus(match[2]);
624
- if (status) upsertStory(stories, key, { status });
625
- }
626
- }
627
- function parseRalphStatus(content) {
628
- try {
629
- const data = JSON.parse(content);
630
- return {
631
- active: data.status === "running",
632
- startedAt: null,
633
- iteration: data.loop_count ?? 0,
634
- cost: 0,
635
- completed: [],
636
- failed: [],
637
- currentStory: null,
638
- currentPhase: null,
639
- lastAction: null,
640
- acProgress: null
641
- };
642
- } catch {
643
- return null;
644
- }
645
- }
646
- function parseRalphStatusToSession(content) {
647
- try {
648
- const data = JSON.parse(content);
649
- return {
650
- active: data.status === "running",
651
- startedAt: null,
652
- iteration: data.loop_count ?? 0,
653
- elapsedSeconds: data.elapsed_seconds ?? 0
654
- };
655
- } catch {
656
- return null;
657
- }
658
- }
659
- function parseSessionIssues(content) {
660
- const items = [];
661
- let currentStory = "";
662
- let itemId = 0;
663
- for (const line of content.split("\n")) {
664
- const headerMatch = line.match(/^###\s+([a-zA-Z0-9_-]+)\s*[—-]/);
665
- if (headerMatch) {
666
- currentStory = headerMatch[1];
667
- continue;
668
- }
669
- const bulletMatch = line.match(/^-\s+(.+)/);
670
- if (bulletMatch && currentStory) {
671
- itemId++;
672
- items.push({
673
- id: `migrated-${itemId}`,
674
- story: currentStory,
675
- description: bulletMatch[1],
676
- source: "retro",
677
- resolved: false
678
- });
679
- }
680
- }
681
- return items;
682
- }
683
- function migrateV1ToV2(v1) {
684
- const defaults = defaultState();
685
- const retriesContent = readIfExists(OLD_FILES.storyRetries);
686
- const retries = retriesContent ? parseStoryRetriesRecord(retriesContent) : {};
687
- const flaggedContent = readIfExists(OLD_FILES.flaggedStories);
688
- const flagged = flaggedContent ? parseFlaggedStoriesList(flaggedContent) : [];
689
- const statusContent = readIfExists(OLD_FILES.ralphStatus);
690
- const session = statusContent ? parseRalphStatusToSession(statusContent) ?? defaults.session : defaults.session;
691
- return {
692
- version: 2,
693
- sprint: v1.sprint,
694
- stories: v1.stories,
695
- retries,
696
- flagged,
697
- epics: {},
698
- session,
699
- observability: defaults.observability,
700
- run: {
701
- ...defaults.run,
702
- ...v1.run
703
- },
704
- actionItems: v1.actionItems
705
- };
706
- }
707
- function migrateFromOldFormat() {
708
- const hasAnyOldFile = Object.values(OLD_FILES).some((rel) => existsSync5(resolve(rel)));
709
- if (!hasAnyOldFile) return fail2("No old format files found for migration");
710
- try {
711
- const stories = {};
712
- let run = defaultState().run;
713
- let actionItems = [];
714
- const yamlContent = readIfExists(OLD_FILES.sprintStatusYaml);
715
- if (yamlContent) parseSprintStatusYaml(yamlContent, stories);
716
- const retriesContent = readIfExists(OLD_FILES.storyRetries);
717
- if (retriesContent) parseStoryRetries(retriesContent, stories);
718
- const flaggedContent = readIfExists(OLD_FILES.flaggedStories);
719
- if (flaggedContent) parseFlaggedStories(flaggedContent, stories);
720
- const statusContent = readIfExists(OLD_FILES.ralphStatus);
721
- let session = defaultState().session;
722
- if (statusContent) {
723
- const parsed = parseRalphStatus(statusContent);
724
- if (parsed) run = parsed;
725
- const parsedSession = parseRalphStatusToSession(statusContent);
726
- if (parsedSession) session = parsedSession;
727
- }
728
- const issuesContent = readIfExists(OLD_FILES.sessionIssues);
729
- if (issuesContent) actionItems = parseSessionIssues(issuesContent);
730
- const sprint = computeSprintCounts(stories);
731
- const retries = retriesContent ? parseStoryRetriesRecord(retriesContent) : {};
732
- const flagged = flaggedContent ? parseFlaggedStoriesList(flaggedContent) : [];
733
- const migrated = {
734
- version: 2,
735
- sprint,
736
- stories,
737
- retries,
738
- flagged,
739
- epics: {},
740
- session,
741
- observability: defaultState().observability,
742
- run,
743
- actionItems
744
- };
745
- const writeResult = writeStateAtomic(migrated);
746
- if (!writeResult.success) return fail2(writeResult.error);
747
- return ok2(migrated);
748
- } catch (err) {
749
- const msg = err instanceof Error ? err.message : String(err);
750
- return fail2(`Migration failed: ${msg}`);
751
- }
752
- }
753
-
754
- // src/modules/sprint/state.ts
755
- function projectRoot() {
756
- return process.cwd();
757
- }
758
- function statePath() {
759
- return join4(projectRoot(), "sprint-state.json");
760
- }
761
- function tmpPath() {
762
- return join4(projectRoot(), ".sprint-state.json.tmp");
763
- }
764
- function sprintStatusYamlPath() {
765
- return join4(projectRoot(), "_bmad-output", "implementation-artifacts", "sprint-status.yaml");
418
+ function sprintStatusYamlPath() {
419
+ return join2(projectRoot(), "_bmad-output", "implementation-artifacts", "sprint-status.yaml");
766
420
  }
767
421
  function defaultState() {
768
422
  return {
@@ -884,9 +538,9 @@ function writeSprintStatusYaml(state) {
884
538
  try {
885
539
  const yamlPath = sprintStatusYamlPath();
886
540
  const dir = dirname(yamlPath);
887
- if (!existsSync6(dir)) return;
541
+ if (!existsSync3(dir)) return;
888
542
  const content = generateSprintStatusYaml(state);
889
- writeFileSync3(yamlPath, content, "utf-8");
543
+ writeFileSync(yamlPath, content, "utf-8");
890
544
  } catch {
891
545
  }
892
546
  }
@@ -895,7 +549,7 @@ function writeStateAtomic(state) {
895
549
  const data = JSON.stringify(state, null, 2) + "\n";
896
550
  const tmp = tmpPath();
897
551
  const final = statePath();
898
- writeFileSync3(tmp, data, "utf-8");
552
+ writeFileSync(tmp, data, "utf-8");
899
553
  renameSync(tmp, final);
900
554
  writeSprintStatusYaml(state);
901
555
  return ok2(void 0);
@@ -906,9 +560,9 @@ function writeStateAtomic(state) {
906
560
  }
907
561
  function getSprintState() {
908
562
  const fp = statePath();
909
- if (existsSync6(fp)) {
563
+ if (existsSync3(fp)) {
910
564
  try {
911
- const raw = readFileSync4(fp, "utf-8");
565
+ const raw = readFileSync2(fp, "utf-8");
912
566
  const parsed = JSON.parse(raw);
913
567
  const version = parsed.version;
914
568
  if (version === 2) {
@@ -1048,10 +702,10 @@ function reconcileState() {
1048
702
  const state = JSON.parse(JSON.stringify(stateResult.data));
1049
703
  const corrections = [];
1050
704
  let changed = false;
1051
- const retriesPath = join4(projectRoot(), "ralph", ".story_retries");
1052
- if (existsSync6(retriesPath)) {
705
+ const retriesPath = join2(projectRoot(), "ralph", ".story_retries");
706
+ if (existsSync3(retriesPath)) {
1053
707
  try {
1054
- const content = readFileSync4(retriesPath, "utf-8");
708
+ const content = readFileSync2(retriesPath, "utf-8");
1055
709
  const fileRetries = parseStoryRetriesRecord(content);
1056
710
  const mergedRetries = { ...state.retries };
1057
711
  for (const [key, count] of Object.entries(fileRetries)) {
@@ -1071,10 +725,10 @@ function reconcileState() {
1071
725
  corrections.push("removed malformed .story_retries file");
1072
726
  }
1073
727
  }
1074
- const flaggedPath = join4(projectRoot(), "ralph", ".flagged_stories");
1075
- if (existsSync6(flaggedPath)) {
728
+ const flaggedPath = join2(projectRoot(), "ralph", ".flagged_stories");
729
+ if (existsSync3(flaggedPath)) {
1076
730
  try {
1077
- const content = readFileSync4(flaggedPath, "utf-8");
731
+ const content = readFileSync2(flaggedPath, "utf-8");
1078
732
  const fileKeys = parseFlaggedStoriesList(content);
1079
733
  const existing = new Set(state.flagged);
1080
734
  const merged = [...state.flagged];
@@ -1361,9 +1015,9 @@ function generateReport(state, now) {
1361
1015
  }
1362
1016
 
1363
1017
  // src/modules/sprint/timeout.ts
1364
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync, readdirSync } from "fs";
1018
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync4, mkdirSync, readdirSync } from "fs";
1365
1019
  import { execSync } from "child_process";
1366
- import { join as join5 } from "path";
1020
+ import { join as join3 } from "path";
1367
1021
  var GIT_TIMEOUT_MS = 5e3;
1368
1022
  var DEFAULT_MAX_LINES = 100;
1369
1023
  function captureGitDiff() {
@@ -1392,14 +1046,14 @@ function captureGitDiff() {
1392
1046
  }
1393
1047
  function captureStateDelta(beforePath, afterPath) {
1394
1048
  try {
1395
- if (!existsSync7(beforePath)) {
1049
+ if (!existsSync4(beforePath)) {
1396
1050
  return fail2(`State snapshot not found: ${beforePath}`);
1397
1051
  }
1398
- if (!existsSync7(afterPath)) {
1052
+ if (!existsSync4(afterPath)) {
1399
1053
  return fail2(`Current state file not found: ${afterPath}`);
1400
1054
  }
1401
- const beforeRaw = readFileSync5(beforePath, "utf-8");
1402
- const afterRaw = readFileSync5(afterPath, "utf-8");
1055
+ const beforeRaw = readFileSync3(beforePath, "utf-8");
1056
+ const afterRaw = readFileSync3(afterPath, "utf-8");
1403
1057
  const before = JSON.parse(beforeRaw);
1404
1058
  const after = JSON.parse(afterRaw);
1405
1059
  const beforeStories = before.stories ?? {};
@@ -1424,10 +1078,10 @@ function captureStateDelta(beforePath, afterPath) {
1424
1078
  }
1425
1079
  function capturePartialStderr(outputFile, maxLines = DEFAULT_MAX_LINES) {
1426
1080
  try {
1427
- if (!existsSync7(outputFile)) {
1081
+ if (!existsSync4(outputFile)) {
1428
1082
  return fail2(`Output file not found: ${outputFile}`);
1429
1083
  }
1430
- const content = readFileSync5(outputFile, "utf-8");
1084
+ const content = readFileSync3(outputFile, "utf-8");
1431
1085
  const lines = content.split("\n");
1432
1086
  if (lines.length > 0 && lines[lines.length - 1] === "") {
1433
1087
  lines.pop();
@@ -1475,7 +1129,7 @@ function captureTimeoutReport(opts) {
1475
1129
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1476
1130
  const gitResult = captureGitDiff();
1477
1131
  const gitDiff = gitResult.success ? gitResult.data : `(unavailable: ${gitResult.error})`;
1478
- const statePath2 = join5(process.cwd(), "sprint-state.json");
1132
+ const statePath2 = join3(process.cwd(), "sprint-state.json");
1479
1133
  const deltaResult = captureStateDelta(opts.stateSnapshotPath, statePath2);
1480
1134
  const stateDelta = deltaResult.success ? deltaResult.data : `(unavailable: ${deltaResult.error})`;
1481
1135
  const stderrResult = capturePartialStderr(opts.outputFile);
@@ -1489,15 +1143,15 @@ function captureTimeoutReport(opts) {
1489
1143
  partialStderr,
1490
1144
  timestamp
1491
1145
  };
1492
- const reportDir = join5(process.cwd(), "ralph", "logs");
1146
+ const reportDir = join3(process.cwd(), "ralph", "logs");
1493
1147
  const safeStoryKey = opts.storyKey.replace(/[^a-zA-Z0-9._-]/g, "_");
1494
1148
  const reportFileName = `timeout-report-${opts.iteration}-${safeStoryKey}.md`;
1495
- const reportPath = join5(reportDir, reportFileName);
1496
- if (!existsSync7(reportDir)) {
1149
+ const reportPath = join3(reportDir, reportFileName);
1150
+ if (!existsSync4(reportDir)) {
1497
1151
  mkdirSync(reportDir, { recursive: true });
1498
1152
  }
1499
1153
  const reportContent = formatReport(capture);
1500
- writeFileSync4(reportPath, reportContent, "utf-8");
1154
+ writeFileSync2(reportPath, reportContent, "utf-8");
1501
1155
  return ok2({
1502
1156
  filePath: reportPath,
1503
1157
  capture
@@ -1509,8 +1163,8 @@ function captureTimeoutReport(opts) {
1509
1163
  }
1510
1164
  function findLatestTimeoutReport(storyKey) {
1511
1165
  try {
1512
- const reportDir = join5(process.cwd(), "ralph", "logs");
1513
- if (!existsSync7(reportDir)) {
1166
+ const reportDir = join3(process.cwd(), "ralph", "logs");
1167
+ if (!existsSync4(reportDir)) {
1514
1168
  return ok2(null);
1515
1169
  }
1516
1170
  const safeStoryKey = storyKey.replace(/[^a-zA-Z0-9._-]/g, "_");
@@ -1532,8 +1186,8 @@ function findLatestTimeoutReport(storyKey) {
1532
1186
  }
1533
1187
  matches.sort((a, b) => b.iteration - a.iteration);
1534
1188
  const latest = matches[0];
1535
- const reportPath = join5(reportDir, latest.fileName);
1536
- const content = readFileSync5(reportPath, "utf-8");
1189
+ const reportPath = join3(reportDir, latest.fileName);
1190
+ const content = readFileSync3(reportPath, "utf-8");
1537
1191
  let durationMinutes = 0;
1538
1192
  let filesChanged = 0;
1539
1193
  const durationMatch = content.match(/\*\*Duration:\*\*\s*(\d+)\s*minutes/);
@@ -1562,12 +1216,12 @@ function findLatestTimeoutReport(storyKey) {
1562
1216
  }
1563
1217
 
1564
1218
  // src/modules/sprint/feedback.ts
1565
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync5 } from "fs";
1566
- import { existsSync as existsSync8 } from "fs";
1567
- import { join as join6 } from "path";
1219
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
1220
+ import { existsSync as existsSync5 } from "fs";
1221
+ import { join as join4 } from "path";
1568
1222
 
1569
1223
  // src/modules/sprint/validator.ts
1570
- import { readFileSync as readFileSync7, existsSync as existsSync9 } from "fs";
1224
+ import { readFileSync as readFileSync5, existsSync as existsSync6 } from "fs";
1571
1225
  var VALID_STATUSES = /* @__PURE__ */ new Set([
1572
1226
  "backlog",
1573
1227
  "ready",
@@ -1598,10 +1252,10 @@ function parseSprintStatusKeys(content) {
1598
1252
  }
1599
1253
  function parseStateFile(statePath2) {
1600
1254
  try {
1601
- if (!existsSync9(statePath2)) {
1255
+ if (!existsSync6(statePath2)) {
1602
1256
  return fail2(`State file not found: ${statePath2}`);
1603
1257
  }
1604
- const raw = readFileSync7(statePath2, "utf-8");
1258
+ const raw = readFileSync5(statePath2, "utf-8");
1605
1259
  const parsed = JSON.parse(raw);
1606
1260
  return ok2(parsed);
1607
1261
  } catch (err) {
@@ -1616,10 +1270,10 @@ function validateStateConsistency(statePath2, sprintStatusPath) {
1616
1270
  return fail2(stateResult.error);
1617
1271
  }
1618
1272
  const state = stateResult.data;
1619
- if (!existsSync9(sprintStatusPath)) {
1273
+ if (!existsSync6(sprintStatusPath)) {
1620
1274
  return fail2(`Sprint status file not found: ${sprintStatusPath}`);
1621
1275
  }
1622
- const statusContent = readFileSync7(sprintStatusPath, "utf-8");
1276
+ const statusContent = readFileSync5(sprintStatusPath, "utf-8");
1623
1277
  const keysResult = parseSprintStatusKeys(statusContent);
1624
1278
  if (!keysResult.success) {
1625
1279
  return fail2(keysResult.error);
@@ -1770,12 +1424,7 @@ function readSprintStatusFromState() {
1770
1424
  const stateResult = getSprintState();
1771
1425
  if (!stateResult.success) return {};
1772
1426
  const statuses = getStoryStatusesFromState(stateResult.data);
1773
- if (Object.keys(statuses).length > 0) return statuses;
1774
- try {
1775
- return readSprintStatus(process.cwd());
1776
- } catch {
1777
- return {};
1778
- }
1427
+ return statuses;
1779
1428
  }
1780
1429
  function shouldDeferPhase2(phase, remainingMinutes) {
1781
1430
  return shouldDeferPhase(phase, remainingMinutes);
@@ -2276,8 +1925,8 @@ function parseResultEvent(parsed) {
2276
1925
 
2277
1926
  // src/lib/agents/ralph.ts
2278
1927
  import { spawn } from "child_process";
2279
- import { existsSync as existsSync10 } from "fs";
2280
- import { join as join7 } from "path";
1928
+ import { existsSync as existsSync7 } from "fs";
1929
+ import { join as join5 } from "path";
2281
1930
  var ANSI_ESCAPE = /\x1b\[[0-9;]*m/g;
2282
1931
  var TIMESTAMP_PREFIX = /^\[[\d-]+\s[\d:]+\]\s*/;
2283
1932
  var SUCCESS_STORY = /\[SUCCESS\]\s+Story\s+([\w-]+):\s+DONE(.*)/;
@@ -2365,17 +2014,17 @@ function buildSpawnArgs(opts) {
2365
2014
  return args;
2366
2015
  }
2367
2016
  function resolveRalphPath() {
2368
- return join7(getPackageRoot(), "ralph", "ralph.sh");
2017
+ return join5(getPackageRoot(), "ralph", "ralph.sh");
2369
2018
  }
2370
2019
  var RalphDriver = class {
2371
2020
  name = "ralph";
2372
2021
  config;
2373
2022
  constructor(config) {
2374
- this.config = config ?? { pluginDir: join7(process.cwd(), ".claude") };
2023
+ this.config = config ?? { pluginDir: join5(process.cwd(), ".claude") };
2375
2024
  }
2376
2025
  spawn(opts) {
2377
2026
  const ralphPath = resolveRalphPath();
2378
- if (!existsSync10(ralphPath)) {
2027
+ if (!existsSync7(ralphPath)) {
2379
2028
  throw new Error(`Ralph loop not found at ${ralphPath} \u2014 reinstall codeharness`);
2380
2029
  }
2381
2030
  const args = buildSpawnArgs({
@@ -2517,7 +2166,7 @@ function getDriver(name, config) {
2517
2166
 
2518
2167
  // src/commands/run.ts
2519
2168
  function resolvePluginDir() {
2520
- return join8(process.cwd(), ".claude");
2169
+ return join6(process.cwd(), ".claude");
2521
2170
  }
2522
2171
  function handleAgentEvent(event, rendererHandle, state) {
2523
2172
  switch (event.type) {
@@ -2571,7 +2220,7 @@ function registerRunCommand(program) {
2571
2220
  const isJson = !!globalOpts.json;
2572
2221
  const outputOpts = { json: isJson };
2573
2222
  const pluginDir = resolvePluginDir();
2574
- if (!existsSync11(pluginDir)) {
2223
+ if (!existsSync8(pluginDir)) {
2575
2224
  fail("Plugin directory not found \u2014 run codeharness init first", outputOpts);
2576
2225
  process.exitCode = 1;
2577
2226
  return;
@@ -2596,7 +2245,7 @@ function registerRunCommand(program) {
2596
2245
  info(`[WARN] Container cleanup failed: ${cleanup.error}`, outputOpts);
2597
2246
  }
2598
2247
  const projectDir = process.cwd();
2599
- const sprintStatusPath = join8(projectDir, "_bmad-output", "implementation-artifacts", "sprint-status.yaml");
2248
+ const sprintStatusPath = join6(projectDir, "_bmad-output", "implementation-artifacts", "sprint-status.yaml");
2600
2249
  const statuses = readSprintStatusFromState();
2601
2250
  const counts = countStories(statuses);
2602
2251
  if (counts.total === 0) {
@@ -2615,7 +2264,7 @@ function registerRunCommand(program) {
2615
2264
  process.exitCode = 1;
2616
2265
  return;
2617
2266
  }
2618
- const promptFile = join8(projectDir, "ralph", ".harness-prompt.md");
2267
+ const promptFile = join6(projectDir, "ralph", ".harness-prompt.md");
2619
2268
  let flaggedStories;
2620
2269
  const flaggedState = getSprintState2();
2621
2270
  if (flaggedState.success && flaggedState.data.flagged?.length > 0) {
@@ -2628,7 +2277,7 @@ function registerRunCommand(program) {
2628
2277
  });
2629
2278
  try {
2630
2279
  mkdirSync2(dirname3(promptFile), { recursive: true });
2631
- writeFileSync6(promptFile, promptContent, "utf-8");
2280
+ writeFileSync4(promptFile, promptContent, "utf-8");
2632
2281
  } catch (err) {
2633
2282
  const message = err instanceof Error ? err.message : String(err);
2634
2283
  fail(`Failed to write prompt file: ${message}`, outputOpts);
@@ -2733,12 +2382,12 @@ function registerRunCommand(program) {
2733
2382
  });
2734
2383
  });
2735
2384
  if (isJson) {
2736
- const statusFile = join8(projectDir, driver.getStatusFile());
2385
+ const statusFile = join6(projectDir, driver.getStatusFile());
2737
2386
  let statusData = null;
2738
2387
  let exitReason = "status_file_missing";
2739
- if (existsSync11(statusFile)) {
2388
+ if (existsSync8(statusFile)) {
2740
2389
  try {
2741
- statusData = JSON.parse(readFileSync8(statusFile, "utf-8"));
2390
+ statusData = JSON.parse(readFileSync6(statusFile, "utf-8"));
2742
2391
  exitReason = "";
2743
2392
  } catch {
2744
2393
  exitReason = "status_file_unreadable";
@@ -2775,7 +2424,7 @@ import { join as join20 } from "path";
2775
2424
  import { readFileSync as readFileSync18 } from "fs";
2776
2425
 
2777
2426
  // src/modules/verify/proof.ts
2778
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "fs";
2427
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
2779
2428
  function classifyEvidenceCommands(proofContent) {
2780
2429
  const results = [];
2781
2430
  const codeBlockPattern = /```(?:bash|shell)\n([\s\S]*?)```/g;
@@ -2861,10 +2510,10 @@ function validateProofQuality(proofPath) {
2861
2510
  otherCount: 0,
2862
2511
  blackBoxPass: false
2863
2512
  };
2864
- if (!existsSync12(proofPath)) {
2513
+ if (!existsSync9(proofPath)) {
2865
2514
  return emptyResult;
2866
2515
  }
2867
- const content = readFileSync9(proofPath, "utf-8");
2516
+ const content = readFileSync7(proofPath, "utf-8");
2868
2517
  const bbTierMatch = /\*\*Tier:\*\*\s*(unit-testable|black-box)/i.exec(content);
2869
2518
  const bbIsUnitTestable = bbTierMatch ? bbTierMatch[1].toLowerCase() === "unit-testable" : false;
2870
2519
  const bbEnforcement = bbIsUnitTestable ? { blackBoxPass: true, grepSrcCount: 0, dockerExecCount: 0, observabilityCount: 0, otherCount: 0, grepRatio: 0, acsMissingDockerExec: [] } : checkBlackBoxEnforcement(content);
@@ -3012,7 +2661,7 @@ import { join as join13 } from "path";
3012
2661
 
3013
2662
  // src/lib/doc-health/types.ts
3014
2663
  import { readdirSync as readdirSync2, statSync } from "fs";
3015
- import { join as join9 } from "path";
2664
+ import { join as join7 } from "path";
3016
2665
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".py"]);
3017
2666
  function getExtension(filename) {
3018
2667
  const dot = filename.lastIndexOf(".");
@@ -3033,7 +2682,7 @@ function getNewestSourceMtime(dir) {
3033
2682
  const dirName = current.split("/").pop() ?? "";
3034
2683
  if (dirName === "node_modules" || dirName === ".git") return;
3035
2684
  for (const entry of entries) {
3036
- const fullPath = join9(current, entry);
2685
+ const fullPath = join7(current, entry);
3037
2686
  let stat;
3038
2687
  try {
3039
2688
  stat = statSync(fullPath);
@@ -3060,22 +2709,22 @@ function getNewestSourceMtime(dir) {
3060
2709
 
3061
2710
  // src/lib/doc-health/scanner.ts
3062
2711
  import {
3063
- existsSync as existsSync14,
3064
- readFileSync as readFileSync11,
2712
+ existsSync as existsSync11,
2713
+ readFileSync as readFileSync9,
3065
2714
  readdirSync as readdirSync4,
3066
2715
  statSync as statSync3
3067
2716
  } from "fs";
3068
- import { join as join11, relative as relative2 } from "path";
2717
+ import { join as join9, relative as relative2 } from "path";
3069
2718
 
3070
2719
  // src/lib/doc-health/staleness.ts
3071
2720
  import { execSync as execSync2 } from "child_process";
3072
2721
  import {
3073
- existsSync as existsSync13,
3074
- readFileSync as readFileSync10,
2722
+ existsSync as existsSync10,
2723
+ readFileSync as readFileSync8,
3075
2724
  readdirSync as readdirSync3,
3076
2725
  statSync as statSync2
3077
2726
  } from "fs";
3078
- import { join as join10, relative } from "path";
2727
+ import { join as join8, relative } from "path";
3079
2728
  var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".js", ".py"]);
3080
2729
  var DO_NOT_EDIT_HEADER = "<!-- DO NOT EDIT MANUALLY";
3081
2730
  function getSourceFilesInModule(modulePath) {
@@ -3090,7 +2739,7 @@ function getSourceFilesInModule(modulePath) {
3090
2739
  const dirName = current.split("/").pop() ?? "";
3091
2740
  if (dirName === "node_modules" || dirName === ".git" || dirName === "__tests__" || dirName === "dist" || dirName === "coverage" || dirName.startsWith(".") && current !== modulePath) return;
3092
2741
  for (const entry of entries) {
3093
- const fullPath = join10(current, entry);
2742
+ const fullPath = join8(current, entry);
3094
2743
  let stat;
3095
2744
  try {
3096
2745
  stat = statSync2(fullPath);
@@ -3111,8 +2760,8 @@ function getSourceFilesInModule(modulePath) {
3111
2760
  return files;
3112
2761
  }
3113
2762
  function getMentionedFilesInAgentsMd(agentsPath) {
3114
- if (!existsSync13(agentsPath)) return [];
3115
- const content = readFileSync10(agentsPath, "utf-8");
2763
+ if (!existsSync10(agentsPath)) return [];
2764
+ const content = readFileSync8(agentsPath, "utf-8");
3116
2765
  const mentioned = /* @__PURE__ */ new Set();
3117
2766
  const filenamePattern = /[\w./-]*[\w-]+\.(?:ts|js|py)\b/g;
3118
2767
  let match;
@@ -3136,12 +2785,12 @@ function checkAgentsMdCompleteness(agentsPath, modulePath) {
3136
2785
  }
3137
2786
  function checkAgentsMdForModule(modulePath, dir) {
3138
2787
  const root = dir ?? process.cwd();
3139
- const fullModulePath = join10(root, modulePath);
3140
- let agentsPath = join10(fullModulePath, "AGENTS.md");
3141
- if (!existsSync13(agentsPath)) {
3142
- agentsPath = join10(root, "AGENTS.md");
2788
+ const fullModulePath = join8(root, modulePath);
2789
+ let agentsPath = join8(fullModulePath, "AGENTS.md");
2790
+ if (!existsSync10(agentsPath)) {
2791
+ agentsPath = join8(root, "AGENTS.md");
3143
2792
  }
3144
- if (!existsSync13(agentsPath)) {
2793
+ if (!existsSync10(agentsPath)) {
3145
2794
  return {
3146
2795
  path: relative(root, agentsPath),
3147
2796
  grade: "missing",
@@ -3172,9 +2821,9 @@ function checkAgentsMdForModule(modulePath, dir) {
3172
2821
  };
3173
2822
  }
3174
2823
  function checkDoNotEditHeaders(docPath) {
3175
- if (!existsSync13(docPath)) return false;
2824
+ if (!existsSync10(docPath)) return false;
3176
2825
  try {
3177
- const content = readFileSync10(docPath, "utf-8");
2826
+ const content = readFileSync8(docPath, "utf-8");
3178
2827
  if (content.length === 0) return false;
3179
2828
  return content.trimStart().startsWith(DO_NOT_EDIT_HEADER);
3180
2829
  } catch {
@@ -3204,15 +2853,15 @@ function checkStoryDocFreshness(storyId, dir) {
3204
2853
  for (const mod of modulesToCheck) {
3205
2854
  const result = checkAgentsMdForModule(mod, root);
3206
2855
  documents.push(result);
3207
- const moduleAgentsPath = join10(root, mod, "AGENTS.md");
3208
- const actualAgentsPath = existsSync13(moduleAgentsPath) ? moduleAgentsPath : join10(root, "AGENTS.md");
3209
- if (existsSync13(actualAgentsPath)) {
2856
+ const moduleAgentsPath = join8(root, mod, "AGENTS.md");
2857
+ const actualAgentsPath = existsSync10(moduleAgentsPath) ? moduleAgentsPath : join8(root, "AGENTS.md");
2858
+ if (existsSync10(actualAgentsPath)) {
3210
2859
  checkAgentsMdLineCountInternal(actualAgentsPath, result.path, documents);
3211
2860
  }
3212
2861
  }
3213
2862
  if (modulesToCheck.length === 0) {
3214
- const rootAgentsPath = join10(root, "AGENTS.md");
3215
- if (existsSync13(rootAgentsPath)) {
2863
+ const rootAgentsPath = join8(root, "AGENTS.md");
2864
+ if (existsSync10(rootAgentsPath)) {
3216
2865
  documents.push({
3217
2866
  path: "AGENTS.md",
3218
2867
  grade: "fresh",
@@ -3250,7 +2899,7 @@ function getRecentlyChangedFiles(dir) {
3250
2899
  }
3251
2900
  function checkAgentsMdLineCountInternal(filePath, docPath, documents) {
3252
2901
  try {
3253
- const content = readFileSync10(filePath, "utf-8");
2902
+ const content = readFileSync8(filePath, "utf-8");
3254
2903
  const lineCount = content.split("\n").length;
3255
2904
  if (lineCount > 100) {
3256
2905
  documents.push({
@@ -3286,7 +2935,7 @@ function findModules(dir, threshold) {
3286
2935
  let sourceCount = 0;
3287
2936
  const subdirs = [];
3288
2937
  for (const entry of entries) {
3289
- const fullPath = join11(current, entry);
2938
+ const fullPath = join9(current, entry);
3290
2939
  let stat;
3291
2940
  try {
3292
2941
  stat = statSync3(fullPath);
@@ -3320,17 +2969,17 @@ function scanDocHealth(dir) {
3320
2969
  const root = dir ?? process.cwd();
3321
2970
  const documents = [];
3322
2971
  const modules = findModules(root);
3323
- const rootAgentsPath = join11(root, "AGENTS.md");
3324
- if (existsSync14(rootAgentsPath)) {
2972
+ const rootAgentsPath = join9(root, "AGENTS.md");
2973
+ if (existsSync11(rootAgentsPath)) {
3325
2974
  if (modules.length > 0) {
3326
2975
  const docMtime = statSync3(rootAgentsPath).mtime;
3327
2976
  let allMissing = [];
3328
2977
  let staleModule = "";
3329
2978
  let newestCode = null;
3330
2979
  for (const mod of modules) {
3331
- const fullModPath = join11(root, mod);
3332
- const modAgentsPath = join11(fullModPath, "AGENTS.md");
3333
- if (existsSync14(modAgentsPath)) continue;
2980
+ const fullModPath = join9(root, mod);
2981
+ const modAgentsPath = join9(fullModPath, "AGENTS.md");
2982
+ if (existsSync11(modAgentsPath)) continue;
3334
2983
  const { missing } = checkAgentsMdCompleteness(rootAgentsPath, fullModPath);
3335
2984
  if (missing.length > 0 && staleModule === "") {
3336
2985
  staleModule = mod;
@@ -3378,8 +3027,8 @@ function scanDocHealth(dir) {
3378
3027
  });
3379
3028
  }
3380
3029
  for (const mod of modules) {
3381
- const modAgentsPath = join11(root, mod, "AGENTS.md");
3382
- if (existsSync14(modAgentsPath)) {
3030
+ const modAgentsPath = join9(root, mod, "AGENTS.md");
3031
+ if (existsSync11(modAgentsPath)) {
3383
3032
  const result = checkAgentsMdForModule(mod, root);
3384
3033
  if (result.path !== "AGENTS.md") {
3385
3034
  documents.push(result);
@@ -3387,9 +3036,9 @@ function scanDocHealth(dir) {
3387
3036
  }
3388
3037
  }
3389
3038
  }
3390
- const indexPath = join11(root, "docs", "index.md");
3391
- if (existsSync14(indexPath)) {
3392
- const content = readFileSync11(indexPath, "utf-8");
3039
+ const indexPath = join9(root, "docs", "index.md");
3040
+ if (existsSync11(indexPath)) {
3041
+ const content = readFileSync9(indexPath, "utf-8");
3393
3042
  const hasAbsolutePaths = /https?:\/\/|file:\/\//i.test(content);
3394
3043
  documents.push({
3395
3044
  path: "docs/index.md",
@@ -3399,11 +3048,11 @@ function scanDocHealth(dir) {
3399
3048
  reason: hasAbsolutePaths ? "Contains absolute URLs (may violate NFR25)" : "Uses relative paths"
3400
3049
  });
3401
3050
  }
3402
- const activeDir = join11(root, "docs", "exec-plans", "active");
3403
- if (existsSync14(activeDir)) {
3051
+ const activeDir = join9(root, "docs", "exec-plans", "active");
3052
+ if (existsSync11(activeDir)) {
3404
3053
  const files = readdirSync4(activeDir).filter((f) => f.endsWith(".md"));
3405
3054
  for (const file of files) {
3406
- const filePath = join11(activeDir, file);
3055
+ const filePath = join9(activeDir, file);
3407
3056
  documents.push({
3408
3057
  path: `docs/exec-plans/active/${file}`,
3409
3058
  grade: "fresh",
@@ -3414,11 +3063,11 @@ function scanDocHealth(dir) {
3414
3063
  }
3415
3064
  }
3416
3065
  for (const subdir of ["quality", "generated"]) {
3417
- const dirPath = join11(root, "docs", subdir);
3418
- if (!existsSync14(dirPath)) continue;
3066
+ const dirPath = join9(root, "docs", subdir);
3067
+ if (!existsSync11(dirPath)) continue;
3419
3068
  const files = readdirSync4(dirPath).filter((f) => !f.startsWith("."));
3420
3069
  for (const file of files) {
3421
- const filePath = join11(dirPath, file);
3070
+ const filePath = join9(dirPath, file);
3422
3071
  let stat;
3423
3072
  try {
3424
3073
  stat = statSync3(filePath);
@@ -3451,7 +3100,7 @@ function scanDocHealth(dir) {
3451
3100
  }
3452
3101
  function checkAgentsMdLineCount(filePath, docPath, documents) {
3453
3102
  try {
3454
- const content = readFileSync11(filePath, "utf-8");
3103
+ const content = readFileSync9(filePath, "utf-8");
3455
3104
  const lineCount = content.split("\n").length;
3456
3105
  if (lineCount > 100) {
3457
3106
  documents.push({
@@ -3468,13 +3117,13 @@ function checkAgentsMdLineCount(filePath, docPath, documents) {
3468
3117
 
3469
3118
  // src/lib/doc-health/report.ts
3470
3119
  import {
3471
- existsSync as existsSync15,
3120
+ existsSync as existsSync12,
3472
3121
  mkdirSync as mkdirSync3,
3473
- readFileSync as readFileSync12,
3122
+ readFileSync as readFileSync10,
3474
3123
  unlinkSync as unlinkSync2,
3475
- writeFileSync as writeFileSync7
3124
+ writeFileSync as writeFileSync5
3476
3125
  } from "fs";
3477
- import { join as join12 } from "path";
3126
+ import { join as join10 } from "path";
3478
3127
  function printDocHealthOutput(report) {
3479
3128
  for (const doc of report.documents) {
3480
3129
  switch (doc.grade) {
@@ -3495,11 +3144,11 @@ function printDocHealthOutput(report) {
3495
3144
  }
3496
3145
  function completeExecPlan(storyId, dir) {
3497
3146
  const root = dir ?? process.cwd();
3498
- const activePath = join12(root, "docs", "exec-plans", "active", `${storyId}.md`);
3499
- if (!existsSync15(activePath)) {
3147
+ const activePath = join10(root, "docs", "exec-plans", "active", `${storyId}.md`);
3148
+ if (!existsSync12(activePath)) {
3500
3149
  return null;
3501
3150
  }
3502
- let content = readFileSync12(activePath, "utf-8");
3151
+ let content = readFileSync10(activePath, "utf-8");
3503
3152
  content = content.replace(/^Status:\s*active$/m, "Status: completed");
3504
3153
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3505
3154
  content = content.replace(
@@ -3507,10 +3156,10 @@ function completeExecPlan(storyId, dir) {
3507
3156
  `$1
3508
3157
  Completed: ${timestamp}`
3509
3158
  );
3510
- const completedDir = join12(root, "docs", "exec-plans", "completed");
3159
+ const completedDir = join10(root, "docs", "exec-plans", "completed");
3511
3160
  mkdirSync3(completedDir, { recursive: true });
3512
- const completedPath = join12(completedDir, `${storyId}.md`);
3513
- writeFileSync7(completedPath, content, "utf-8");
3161
+ const completedPath = join10(completedDir, `${storyId}.md`);
3162
+ writeFileSync5(completedPath, content, "utf-8");
3514
3163
  try {
3515
3164
  unlinkSync2(activePath);
3516
3165
  } catch {
@@ -3518,6 +3167,331 @@ Completed: ${timestamp}`
3518
3167
  return completedPath;
3519
3168
  }
3520
3169
 
3170
+ // src/lib/sync/story-files.ts
3171
+ import { existsSync as existsSync13, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
3172
+ var BEADS_TO_STORY_STATUS = {
3173
+ open: "in-progress",
3174
+ closed: "done"
3175
+ };
3176
+ var STORY_TO_BEADS_STATUS = {
3177
+ backlog: "open",
3178
+ "ready-for-dev": "open",
3179
+ "in-progress": "open",
3180
+ review: "open",
3181
+ done: "closed"
3182
+ };
3183
+ function beadsStatusToStoryStatus(beadsStatus) {
3184
+ return BEADS_TO_STORY_STATUS[beadsStatus] ?? null;
3185
+ }
3186
+ function storyStatusToBeadsStatus(storyStatus) {
3187
+ return STORY_TO_BEADS_STATUS[storyStatus] ?? null;
3188
+ }
3189
+ function storyKeyFromPath(filePath) {
3190
+ const base = filePath.split("/").pop() ?? filePath;
3191
+ return base.replace(/\.md$/, "");
3192
+ }
3193
+ function resolveStoryFilePath(beadsIssue) {
3194
+ const desc = beadsIssue.description;
3195
+ if (!desc || !desc.trim()) {
3196
+ return null;
3197
+ }
3198
+ const trimmed = desc.trim();
3199
+ if (!trimmed.endsWith(".md")) {
3200
+ return null;
3201
+ }
3202
+ return trimmed;
3203
+ }
3204
+ function readStoryFileStatus(filePath) {
3205
+ if (!existsSync13(filePath)) {
3206
+ return null;
3207
+ }
3208
+ const content = readFileSync11(filePath, "utf-8");
3209
+ const match = content.match(/^#{0,2}\s*Status:\s*(.+)$/m);
3210
+ if (!match) {
3211
+ return null;
3212
+ }
3213
+ return match[1].trim();
3214
+ }
3215
+ function updateStoryFileStatus(filePath, newStatus) {
3216
+ const content = readFileSync11(filePath, "utf-8");
3217
+ const statusRegex = /^(#{0,2}\s*)Status:\s*.+$/m;
3218
+ if (statusRegex.test(content)) {
3219
+ const updated = content.replace(statusRegex, `$1Status: ${newStatus}`);
3220
+ writeFileSync6(filePath, updated, "utf-8");
3221
+ } else {
3222
+ const lines = content.split("\n");
3223
+ const titleIndex = lines.findIndex((l) => l.startsWith("# "));
3224
+ if (titleIndex !== -1) {
3225
+ lines.splice(titleIndex + 1, 0, "", `Status: ${newStatus}`);
3226
+ } else {
3227
+ lines.unshift(`Status: ${newStatus}`, "");
3228
+ }
3229
+ writeFileSync6(filePath, lines.join("\n"), "utf-8");
3230
+ }
3231
+ }
3232
+
3233
+ // src/lib/sync/sprint-yaml.ts
3234
+ import { existsSync as existsSync14, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
3235
+ import { join as join11 } from "path";
3236
+ import { parse } from "yaml";
3237
+ var SPRINT_STATUS_PATH = "_bmad-output/implementation-artifacts/sprint-status.yaml";
3238
+ function updateSprintStatus(storyKey, newStatus, dir) {
3239
+ const root = dir ?? process.cwd();
3240
+ const filePath = join11(root, SPRINT_STATUS_PATH);
3241
+ if (!existsSync14(filePath)) {
3242
+ warn(`sprint-status.yaml not found at ${filePath}, skipping update`);
3243
+ return;
3244
+ }
3245
+ const content = readFileSync12(filePath, "utf-8");
3246
+ const keyPattern = new RegExp(`^(\\s*${escapeRegExp(storyKey)}:\\s*)\\S+(.*)$`, "m");
3247
+ if (!keyPattern.test(content)) {
3248
+ return;
3249
+ }
3250
+ const updated = content.replace(keyPattern, `$1${newStatus}$2`);
3251
+ writeFileSync7(filePath, updated, "utf-8");
3252
+ }
3253
+ function escapeRegExp(s) {
3254
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3255
+ }
3256
+
3257
+ // src/lib/sync/beads.ts
3258
+ import { existsSync as existsSync15 } from "fs";
3259
+ import { join as join12 } from "path";
3260
+ function syncBeadsToStoryFile(beadsId, beadsFns, dir) {
3261
+ const root = dir ?? process.cwd();
3262
+ const issues = beadsFns.listIssues();
3263
+ const issue = issues.find((i) => i.id === beadsId);
3264
+ if (!issue) {
3265
+ return {
3266
+ storyKey: "",
3267
+ beadsId,
3268
+ previousStatus: "",
3269
+ newStatus: "",
3270
+ synced: false,
3271
+ error: `Beads issue not found: ${beadsId}`
3272
+ };
3273
+ }
3274
+ const storyFilePath = resolveStoryFilePath(issue);
3275
+ if (!storyFilePath) {
3276
+ return {
3277
+ storyKey: "",
3278
+ beadsId,
3279
+ previousStatus: issue.status,
3280
+ newStatus: "",
3281
+ synced: false,
3282
+ error: `No story file path in beads issue description: ${beadsId}`
3283
+ };
3284
+ }
3285
+ const storyKey = storyKeyFromPath(storyFilePath);
3286
+ const fullPath = join12(root, storyFilePath);
3287
+ const currentStoryStatus = readStoryFileStatus(fullPath);
3288
+ if (currentStoryStatus === null) {
3289
+ return {
3290
+ storyKey,
3291
+ beadsId,
3292
+ previousStatus: issue.status,
3293
+ newStatus: "",
3294
+ synced: false,
3295
+ error: `Story file not found or has no Status line: ${storyFilePath}`
3296
+ };
3297
+ }
3298
+ const targetStoryStatus = beadsStatusToStoryStatus(issue.status);
3299
+ if (!targetStoryStatus) {
3300
+ return {
3301
+ storyKey,
3302
+ beadsId,
3303
+ previousStatus: currentStoryStatus,
3304
+ newStatus: "",
3305
+ synced: false,
3306
+ error: `Unknown beads status: ${issue.status}`
3307
+ };
3308
+ }
3309
+ if (currentStoryStatus === targetStoryStatus) {
3310
+ return {
3311
+ storyKey,
3312
+ beadsId,
3313
+ previousStatus: currentStoryStatus,
3314
+ newStatus: currentStoryStatus,
3315
+ synced: false
3316
+ };
3317
+ }
3318
+ updateStoryFileStatus(fullPath, targetStoryStatus);
3319
+ updateSprintStatus(storyKey, targetStoryStatus, root);
3320
+ return {
3321
+ storyKey,
3322
+ beadsId,
3323
+ previousStatus: currentStoryStatus,
3324
+ newStatus: targetStoryStatus,
3325
+ synced: true
3326
+ };
3327
+ }
3328
+ function syncStoryFileToBeads(storyKey, beadsFns, dir) {
3329
+ const root = dir ?? process.cwd();
3330
+ const storyFilePath = `_bmad-output/implementation-artifacts/${storyKey}.md`;
3331
+ const fullPath = join12(root, storyFilePath);
3332
+ const currentStoryStatus = readStoryFileStatus(fullPath);
3333
+ if (currentStoryStatus === null) {
3334
+ return {
3335
+ storyKey,
3336
+ beadsId: "",
3337
+ previousStatus: "",
3338
+ newStatus: "",
3339
+ synced: false,
3340
+ error: `Story file not found or has no Status line: ${storyFilePath}`
3341
+ };
3342
+ }
3343
+ const issues = beadsFns.listIssues();
3344
+ const issue = issues.find((i) => {
3345
+ const path = resolveStoryFilePath(i);
3346
+ if (!path) return false;
3347
+ return storyKeyFromPath(path) === storyKey;
3348
+ });
3349
+ if (!issue) {
3350
+ return {
3351
+ storyKey,
3352
+ beadsId: "",
3353
+ previousStatus: currentStoryStatus,
3354
+ newStatus: "",
3355
+ synced: false,
3356
+ error: `No beads issue found for story: ${storyKey}`
3357
+ };
3358
+ }
3359
+ const targetBeadsStatus = storyStatusToBeadsStatus(currentStoryStatus);
3360
+ if (!targetBeadsStatus) {
3361
+ return {
3362
+ storyKey,
3363
+ beadsId: issue.id,
3364
+ previousStatus: currentStoryStatus,
3365
+ newStatus: "",
3366
+ synced: false,
3367
+ error: `Unknown story status: ${currentStoryStatus}`
3368
+ };
3369
+ }
3370
+ if (issue.status === targetBeadsStatus) {
3371
+ return {
3372
+ storyKey,
3373
+ beadsId: issue.id,
3374
+ previousStatus: currentStoryStatus,
3375
+ newStatus: currentStoryStatus,
3376
+ synced: false
3377
+ };
3378
+ }
3379
+ if (targetBeadsStatus === "closed") {
3380
+ beadsFns.closeIssue(issue.id);
3381
+ } else {
3382
+ beadsFns.updateIssue(issue.id, { status: targetBeadsStatus });
3383
+ }
3384
+ updateSprintStatus(storyKey, currentStoryStatus, root);
3385
+ return {
3386
+ storyKey,
3387
+ beadsId: issue.id,
3388
+ previousStatus: issue.status,
3389
+ newStatus: targetBeadsStatus,
3390
+ synced: true
3391
+ };
3392
+ }
3393
+ function syncClose(beadsId, beadsFns, dir) {
3394
+ const root = dir ?? process.cwd();
3395
+ const issues = beadsFns.listIssues();
3396
+ const issue = issues.find((i) => i.id === beadsId);
3397
+ beadsFns.closeIssue(beadsId);
3398
+ if (!issue) {
3399
+ return {
3400
+ storyKey: "",
3401
+ beadsId,
3402
+ previousStatus: "",
3403
+ newStatus: "closed",
3404
+ synced: false,
3405
+ error: `Beads issue not found: ${beadsId}`
3406
+ };
3407
+ }
3408
+ const storyFilePath = resolveStoryFilePath(issue);
3409
+ if (!storyFilePath) {
3410
+ return {
3411
+ storyKey: "",
3412
+ beadsId,
3413
+ previousStatus: issue.status,
3414
+ newStatus: "closed",
3415
+ synced: false,
3416
+ error: `No story file path in beads issue description: ${beadsId}`
3417
+ };
3418
+ }
3419
+ const storyKey = storyKeyFromPath(storyFilePath);
3420
+ const fullPath = join12(root, storyFilePath);
3421
+ const previousStatus = readStoryFileStatus(fullPath);
3422
+ if (previousStatus === null) {
3423
+ if (!existsSync15(fullPath)) {
3424
+ return {
3425
+ storyKey,
3426
+ beadsId,
3427
+ previousStatus: "",
3428
+ newStatus: "closed",
3429
+ synced: false,
3430
+ error: `Story file not found: ${storyFilePath}`
3431
+ };
3432
+ }
3433
+ }
3434
+ updateStoryFileStatus(fullPath, "done");
3435
+ updateSprintStatus(storyKey, "done", root);
3436
+ return {
3437
+ storyKey,
3438
+ beadsId,
3439
+ previousStatus: previousStatus ?? "",
3440
+ newStatus: "done",
3441
+ synced: true
3442
+ };
3443
+ }
3444
+ function syncAll(direction, beadsFns, dir) {
3445
+ const root = dir ?? process.cwd();
3446
+ const results = [];
3447
+ let issues;
3448
+ try {
3449
+ issues = beadsFns.listIssues();
3450
+ } catch (err) {
3451
+ const message = err instanceof Error ? err.message : String(err);
3452
+ return [{
3453
+ storyKey: "",
3454
+ beadsId: "",
3455
+ previousStatus: "",
3456
+ newStatus: "",
3457
+ synced: false,
3458
+ error: `Failed to list beads issues: ${message}`
3459
+ }];
3460
+ }
3461
+ const cachedListIssues = () => issues;
3462
+ for (const issue of issues) {
3463
+ const storyFilePath = resolveStoryFilePath(issue);
3464
+ if (!storyFilePath) {
3465
+ continue;
3466
+ }
3467
+ const storyKey = storyKeyFromPath(storyFilePath);
3468
+ try {
3469
+ if (direction === "beads-to-files" || direction === "bidirectional") {
3470
+ const result = syncBeadsToStoryFile(issue.id, { listIssues: cachedListIssues }, root);
3471
+ results.push(result);
3472
+ } else if (direction === "files-to-beads") {
3473
+ const result = syncStoryFileToBeads(storyKey, {
3474
+ listIssues: cachedListIssues,
3475
+ updateIssue: beadsFns.updateIssue,
3476
+ closeIssue: beadsFns.closeIssue
3477
+ }, root);
3478
+ results.push(result);
3479
+ }
3480
+ } catch (err) {
3481
+ const message = err instanceof Error ? err.message : String(err);
3482
+ results.push({
3483
+ storyKey,
3484
+ beadsId: issue.id,
3485
+ previousStatus: "",
3486
+ newStatus: "",
3487
+ synced: false,
3488
+ error: message
3489
+ });
3490
+ }
3491
+ }
3492
+ return results;
3493
+ }
3494
+
3521
3495
  // src/templates/showboat-template.ts
3522
3496
  function verificationSummaryBlock(criteria) {
3523
3497
  const total = criteria.length;
@@ -7558,7 +7532,7 @@ function registerTeardownCommand(program) {
7558
7532
  } else if (otlpMode === "remote-routed") {
7559
7533
  if (!options.keepDocker) {
7560
7534
  try {
7561
- const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-XPXVRUOR.js");
7535
+ const { stopCollectorOnly: stopCollectorOnly2 } = await import("./docker-F4U5KY6M.js");
7562
7536
  stopCollectorOnly2();
7563
7537
  result.docker.stopped = true;
7564
7538
  if (!isJson) {
@@ -7590,7 +7564,7 @@ function registerTeardownCommand(program) {
7590
7564
  info("Shared stack: kept running (other projects may use it)");
7591
7565
  }
7592
7566
  } else if (isLegacyStack) {
7593
- const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-XPXVRUOR.js");
7567
+ const { isStackRunning: isStackRunning2, stopStack } = await import("./docker-F4U5KY6M.js");
7594
7568
  let stackRunning = false;
7595
7569
  try {
7596
7570
  stackRunning = isStackRunning2(composeFile);
@@ -9489,7 +9463,7 @@ function registerAuditCommand(program) {
9489
9463
  }
9490
9464
 
9491
9465
  // src/index.ts
9492
- var VERSION = true ? "0.25.5" : "0.0.0-dev";
9466
+ var VERSION = true ? "0.25.6" : "0.0.0-dev";
9493
9467
  function createProgram() {
9494
9468
  const program = new Command();
9495
9469
  program.name("codeharness").description("Makes autonomous coding agents produce software that actually works").version(VERSION).option("--json", "Output in machine-readable JSON format");