codeharness 0.26.5 → 0.27.0

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.
@@ -371,8 +371,8 @@ function getRunningServices(composeFile, projectName) {
371
371
  }
372
372
 
373
373
  // src/modules/infra/init-project.ts
374
- import { existsSync as existsSync14 } from "fs";
375
- import { basename as basename3, join as join15 } from "path";
374
+ import { copyFileSync, existsSync as existsSync13, mkdirSync as mkdirSync4 } from "fs";
375
+ import { basename as basename3, dirname as dirname3, join as join14 } from "path";
376
376
 
377
377
  // src/lib/output.ts
378
378
  function ok(message, options) {
@@ -2129,78 +2129,12 @@ function verifyDeps(isJson) {
2129
2129
  }
2130
2130
 
2131
2131
  // src/lib/bmad.ts
2132
- import { execFileSync as execFileSync10 } from "child_process";
2133
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
2134
- import { join as join12 } from "path";
2135
-
2136
- // src/lib/patch-engine.ts
2137
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2138
- function validatePatchName(patchName) {
2139
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(patchName)) {
2140
- throw new Error(
2141
- `Invalid patch name '${patchName}': must be kebab-case (lowercase letters, digits, hyphens)`
2142
- );
2143
- }
2144
- }
2145
- function getPatchMarkers(patchName) {
2146
- validatePatchName(patchName);
2147
- return {
2148
- start: `<!-- CODEHARNESS-PATCH-START:${patchName} -->`,
2149
- end: `<!-- CODEHARNESS-PATCH-END:${patchName} -->`
2150
- };
2151
- }
2152
- function applyPatch(filePath, patchName, patchContent) {
2153
- let content = readFileSync9(filePath, "utf-8");
2154
- const markers = getPatchMarkers(patchName);
2155
- const markerBlock = `${markers.start}
2156
- ${patchContent}
2157
- ${markers.end}`;
2158
- const startIdx = content.indexOf(markers.start);
2159
- const endIdx = content.indexOf(markers.end);
2160
- if (startIdx !== -1 !== (endIdx !== -1)) {
2161
- throw new Error(
2162
- `Corrupted patch markers for '${patchName}': only ${startIdx !== -1 ? "start" : "end"} marker found in ${filePath}`
2163
- );
2164
- }
2165
- if (startIdx !== -1 && endIdx !== -1) {
2166
- if (endIdx < startIdx) {
2167
- throw new Error(
2168
- `Corrupted patch markers for '${patchName}': end marker appears before start marker in ${filePath}`
2169
- );
2170
- }
2171
- const before = content.slice(0, startIdx);
2172
- const after = content.slice(endIdx + markers.end.length);
2173
- content = before + markerBlock + after;
2174
- writeFileSync7(filePath, content, "utf-8");
2175
- return { applied: true, updated: true };
2176
- }
2177
- const trimmed = content.trimEnd();
2178
- content = trimmed + "\n\n" + markerBlock + "\n";
2179
- writeFileSync7(filePath, content, "utf-8");
2180
- return { applied: true, updated: false };
2181
- }
2182
- function removePatch(filePath, patchName) {
2183
- let content = readFileSync9(filePath, "utf-8");
2184
- const markers = getPatchMarkers(patchName);
2185
- const startIdx = content.indexOf(markers.start);
2186
- const endIdx = content.indexOf(markers.end);
2187
- if (startIdx === -1 || endIdx === -1) {
2188
- return false;
2189
- }
2190
- if (endIdx < startIdx) {
2191
- throw new Error(
2192
- `Corrupted patch markers for '${patchName}': end marker appears before start marker in ${filePath}`
2193
- );
2194
- }
2195
- const before = content.slice(0, startIdx);
2196
- const after = content.slice(endIdx + markers.end.length);
2197
- content = before.trimEnd() + "\n" + after.trimStart();
2198
- writeFileSync7(filePath, content, "utf-8");
2199
- return true;
2200
- }
2132
+ import { execFileSync as execFileSync9 } from "child_process";
2133
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
2134
+ import { join as join11 } from "path";
2201
2135
 
2202
2136
  // src/templates/bmad-patches.ts
2203
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
2137
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2204
2138
  import { join as join10, dirname as dirname2 } from "path";
2205
2139
  import { fileURLToPath as fileURLToPath2 } from "url";
2206
2140
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
@@ -2208,7 +2142,7 @@ function readPatchFile(role, name) {
2208
2142
  const patchPath = join10(__dirname2, "..", "..", "patches", role, `${name}.md`);
2209
2143
  try {
2210
2144
  if (existsSync9(patchPath)) {
2211
- return readFileSync10(patchPath, "utf-8").trim();
2145
+ return readFileSync9(patchPath, "utf-8").trim();
2212
2146
  }
2213
2147
  } catch {
2214
2148
  }
@@ -2283,154 +2217,6 @@ var PATCH_TEMPLATES = {
2283
2217
  "sprint-retro": sprintPlanningRetroPatch
2284
2218
  };
2285
2219
 
2286
- // src/lib/beads.ts
2287
- import { execFileSync as execFileSync9 } from "child_process";
2288
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
2289
- import { join as join11 } from "path";
2290
- var BeadsError = class extends Error {
2291
- constructor(command, originalMessage) {
2292
- super(`Beads failed: ${originalMessage}. Command: ${command}`);
2293
- this.command = command;
2294
- this.originalMessage = originalMessage;
2295
- this.name = "BeadsError";
2296
- }
2297
- };
2298
- function isBeadsCLIInstalled() {
2299
- try {
2300
- execFileSync9("which", ["bd"], { stdio: "pipe", timeout: 5e3 });
2301
- return true;
2302
- } catch {
2303
- return false;
2304
- }
2305
- }
2306
- function bdCommand(args) {
2307
- const cmdStr = `bd ${args.join(" ")}`;
2308
- let text;
2309
- try {
2310
- const output = execFileSync9("bd", args, {
2311
- stdio: "pipe",
2312
- timeout: 3e4
2313
- });
2314
- text = output.toString().trim();
2315
- } catch (err) {
2316
- const message = err instanceof Error ? err.message : String(err);
2317
- throw new BeadsError(cmdStr, message);
2318
- }
2319
- if (!text) return void 0;
2320
- try {
2321
- return JSON.parse(text);
2322
- } catch {
2323
- throw new BeadsError(cmdStr, `Invalid JSON output from bd: ${text}`);
2324
- }
2325
- }
2326
- function createIssue(title, opts) {
2327
- const args = ["create", title, "--json"];
2328
- if (opts?.type) {
2329
- args.push("--type", opts.type);
2330
- }
2331
- if (opts?.priority !== void 0) {
2332
- args.push("--priority", String(opts.priority));
2333
- }
2334
- if (opts?.description) {
2335
- args.push("--description", opts.description);
2336
- }
2337
- if (opts?.deps && opts.deps.length > 0) {
2338
- for (const dep of opts.deps) {
2339
- args.push("--dep", dep);
2340
- }
2341
- }
2342
- return bdCommand(args);
2343
- }
2344
- function closeIssue(id) {
2345
- bdCommand(["close", id, "--json"]);
2346
- }
2347
- function updateIssue(id, opts) {
2348
- const args = ["update", id, "--json"];
2349
- if (opts.status !== void 0) {
2350
- args.push("--status", opts.status);
2351
- }
2352
- if (opts.priority !== void 0) {
2353
- args.push("--priority", String(opts.priority));
2354
- }
2355
- bdCommand(args);
2356
- }
2357
- function listIssues() {
2358
- return bdCommand(["list", "--json"]);
2359
- }
2360
- function isBeadsInitialized(dir) {
2361
- const beadsDir = join11(dir ?? process.cwd(), ".beads");
2362
- return existsSync10(beadsDir);
2363
- }
2364
- function initBeads(dir) {
2365
- if (isBeadsInitialized(dir)) {
2366
- return;
2367
- }
2368
- const cmdStr = "bd init";
2369
- try {
2370
- execFileSync9("bd", ["init"], {
2371
- stdio: "pipe",
2372
- timeout: 3e4,
2373
- cwd: dir ?? process.cwd()
2374
- });
2375
- } catch (err) {
2376
- const message = err instanceof Error ? err.message : String(err);
2377
- throw new BeadsError(cmdStr, message);
2378
- }
2379
- }
2380
- function detectBeadsHooks(dir) {
2381
- const hooksDir = join11(dir ?? process.cwd(), ".beads", "hooks");
2382
- if (!existsSync10(hooksDir)) {
2383
- return { hasHooks: false, hookTypes: [] };
2384
- }
2385
- try {
2386
- const entries = readdirSync2(hooksDir);
2387
- const hookTypes = entries.filter((e) => !e.startsWith("."));
2388
- return {
2389
- hasHooks: hookTypes.length > 0,
2390
- hookTypes
2391
- };
2392
- } catch {
2393
- return { hasHooks: false, hookTypes: [] };
2394
- }
2395
- }
2396
- function buildGapId(category, identifier) {
2397
- return `[gap:${category}:${identifier}]`;
2398
- }
2399
- function findExistingByGapId(gapId, issues) {
2400
- return issues.find(
2401
- (issue) => issue.status !== "done" && issue.description?.includes(gapId)
2402
- );
2403
- }
2404
- function appendGapId(existingDescription, gapId) {
2405
- if (!existingDescription) {
2406
- return gapId;
2407
- }
2408
- return `${existingDescription}
2409
- ${gapId}`;
2410
- }
2411
- function createOrFindIssue(title, gapId, opts) {
2412
- const issues = listIssues();
2413
- const existing = findExistingByGapId(gapId, issues);
2414
- if (existing) {
2415
- return { issue: existing, created: false };
2416
- }
2417
- const issue = createIssue(title, {
2418
- ...opts,
2419
- description: appendGapId(opts?.description, gapId)
2420
- });
2421
- return { issue, created: true };
2422
- }
2423
- function configureHookCoexistence(dir) {
2424
- const detection = detectBeadsHooks(dir);
2425
- if (!detection.hasHooks) {
2426
- return;
2427
- }
2428
- const gitHooksDir = join11(dir ?? process.cwd(), ".git", "hooks");
2429
- if (!existsSync10(gitHooksDir)) {
2430
- return;
2431
- }
2432
- }
2433
-
2434
2220
  // src/lib/bmad.ts
2435
2221
  var BmadError = class extends Error {
2436
2222
  constructor(command, originalMessage) {
@@ -2449,15 +2235,15 @@ var PATCH_TARGETS = {
2449
2235
  "sprint-retro": "bmm/workflows/4-implementation/sprint-planning/instructions.md"
2450
2236
  };
2451
2237
  function isBmadInstalled(dir) {
2452
- const bmadDir = join12(dir ?? process.cwd(), "_bmad");
2453
- return existsSync11(bmadDir);
2238
+ const bmadDir = join11(dir ?? process.cwd(), "_bmad");
2239
+ return existsSync10(bmadDir);
2454
2240
  }
2455
2241
  function detectBmadVersion(dir) {
2456
2242
  const root = dir ?? process.cwd();
2457
- const moduleYamlPath = join12(root, "_bmad", "core", "module.yaml");
2458
- if (existsSync11(moduleYamlPath)) {
2243
+ const moduleYamlPath = join11(root, "_bmad", "core", "module.yaml");
2244
+ if (existsSync10(moduleYamlPath)) {
2459
2245
  try {
2460
- const content = readFileSync11(moduleYamlPath, "utf-8");
2246
+ const content = readFileSync10(moduleYamlPath, "utf-8");
2461
2247
  const versionMatch = content.match(/version:\s*["']?([^\s"']+)["']?/);
2462
2248
  if (versionMatch) {
2463
2249
  return versionMatch[1];
@@ -2465,17 +2251,17 @@ function detectBmadVersion(dir) {
2465
2251
  } catch {
2466
2252
  }
2467
2253
  }
2468
- const versionFilePath = join12(root, "_bmad", "VERSION");
2469
- if (existsSync11(versionFilePath)) {
2254
+ const versionFilePath = join11(root, "_bmad", "VERSION");
2255
+ if (existsSync10(versionFilePath)) {
2470
2256
  try {
2471
- return readFileSync11(versionFilePath, "utf-8").trim() || null;
2257
+ return readFileSync10(versionFilePath, "utf-8").trim() || null;
2472
2258
  } catch {
2473
2259
  }
2474
2260
  }
2475
- const packageJsonPath = join12(root, "_bmad", "package.json");
2476
- if (existsSync11(packageJsonPath)) {
2261
+ const packageJsonPath = join11(root, "_bmad", "package.json");
2262
+ if (existsSync10(packageJsonPath)) {
2477
2263
  try {
2478
- const pkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
2264
+ const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
2479
2265
  return pkg.version ?? null;
2480
2266
  } catch {
2481
2267
  }
@@ -2494,7 +2280,7 @@ function installBmad(dir) {
2494
2280
  }
2495
2281
  const cmdStr = "npx bmad-method install --yes --tools claude-code";
2496
2282
  try {
2497
- execFileSync10("npx", ["bmad-method", "install", "--yes", "--tools", "claude-code"], {
2283
+ execFileSync9("npx", ["bmad-method", "install", "--yes", "--tools", "claude-code"], {
2498
2284
  stdio: "pipe",
2499
2285
  timeout: 12e4,
2500
2286
  // 2 min — npx may need to download the package first time
@@ -2519,8 +2305,8 @@ function applyAllPatches(dir, options) {
2519
2305
  const silent = options?.silent ?? false;
2520
2306
  const results = [];
2521
2307
  for (const [patchName, relativePath] of Object.entries(PATCH_TARGETS)) {
2522
- const targetFile = join12(root, "_bmad", relativePath);
2523
- if (!existsSync11(targetFile)) {
2308
+ const targetFile = join11(root, "_bmad", relativePath);
2309
+ if (!existsSync10(targetFile)) {
2524
2310
  if (!silent) warn(`Patch target not found: ${relativePath}`);
2525
2311
  results.push({
2526
2312
  patchName,
@@ -2543,13 +2329,13 @@ function applyAllPatches(dir, options) {
2543
2329
  continue;
2544
2330
  }
2545
2331
  try {
2546
- const patchContent = templateFn();
2547
- const patchResult = applyPatch(targetFile, patchName, patchContent);
2332
+ const _patchContent = templateFn();
2548
2333
  results.push({
2549
2334
  patchName,
2550
2335
  targetFile,
2551
- applied: patchResult.applied,
2552
- updated: patchResult.updated
2336
+ applied: false,
2337
+ updated: false,
2338
+ error: "Patch engine removed (Story 1.2) \u2014 pending v2 rebuild"
2553
2339
  });
2554
2340
  } catch (err) {
2555
2341
  const message = err instanceof Error ? err.message : String(err);
@@ -2567,12 +2353,12 @@ function applyAllPatches(dir, options) {
2567
2353
  function detectBmalph(dir) {
2568
2354
  const root = dir ?? process.cwd();
2569
2355
  const files = [];
2570
- const ralphRcPath = join12(root, ".ralph", ".ralphrc");
2571
- if (existsSync11(ralphRcPath)) {
2356
+ const ralphRcPath = join11(root, ".ralph", ".ralphrc");
2357
+ if (existsSync10(ralphRcPath)) {
2572
2358
  files.push(".ralph/.ralphrc");
2573
2359
  }
2574
- const dotRalphDir = join12(root, ".ralph");
2575
- if (existsSync11(dotRalphDir)) {
2360
+ const dotRalphDir = join11(root, ".ralph");
2361
+ if (existsSync10(dotRalphDir)) {
2576
2362
  if (files.length === 0) {
2577
2363
  files.push(".ralph/");
2578
2364
  }
@@ -2587,10 +2373,10 @@ function getStoryFilePath(storyKey) {
2587
2373
  return `_bmad-output/implementation-artifacts/${storyKey}.md`;
2588
2374
  }
2589
2375
  function parseEpicsFile(filePath) {
2590
- if (!existsSync11(filePath)) {
2376
+ if (!existsSync10(filePath)) {
2591
2377
  return [];
2592
2378
  }
2593
- const content = readFileSync11(filePath, "utf-8");
2379
+ const content = readFileSync10(filePath, "utf-8");
2594
2380
  if (!content.trim()) {
2595
2381
  return [];
2596
2382
  }
@@ -2676,78 +2462,6 @@ function finalizeStory(raw) {
2676
2462
  technicalNotes
2677
2463
  };
2678
2464
  }
2679
- function importStoriesToBeads(stories, opts, beadsFns) {
2680
- const results = [];
2681
- let existingIssues = [];
2682
- try {
2683
- existingIssues = beadsFns.listIssues();
2684
- } catch {
2685
- }
2686
- const lastBeadsIdByEpic = /* @__PURE__ */ new Map();
2687
- let priority = 1;
2688
- for (const story of stories) {
2689
- const storyFilePath = getStoryFilePath(story.key);
2690
- const gapId = buildGapId("bridge", `${story.epicNumber}.${story.storyNumber}`);
2691
- const existingIssue = findExistingByGapId(gapId, existingIssues);
2692
- if (existingIssue) {
2693
- lastBeadsIdByEpic.set(story.epicNumber, existingIssue.id);
2694
- results.push({
2695
- storyKey: story.key,
2696
- title: story.title,
2697
- beadsId: existingIssue.id,
2698
- status: "exists",
2699
- storyFilePath
2700
- });
2701
- priority++;
2702
- continue;
2703
- }
2704
- if (opts.dryRun) {
2705
- results.push({
2706
- storyKey: story.key,
2707
- title: story.title,
2708
- beadsId: null,
2709
- status: "skipped",
2710
- storyFilePath
2711
- });
2712
- priority++;
2713
- continue;
2714
- }
2715
- const deps = [];
2716
- const prevId = lastBeadsIdByEpic.get(story.epicNumber);
2717
- if (prevId) {
2718
- deps.push(prevId);
2719
- }
2720
- try {
2721
- const description = appendGapId(storyFilePath, gapId);
2722
- const issue = beadsFns.createIssue(story.title, {
2723
- type: "story",
2724
- priority,
2725
- description,
2726
- deps: deps.length > 0 ? deps : void 0
2727
- });
2728
- lastBeadsIdByEpic.set(story.epicNumber, issue.id);
2729
- results.push({
2730
- storyKey: story.key,
2731
- title: story.title,
2732
- beadsId: issue.id,
2733
- status: "created",
2734
- storyFilePath
2735
- });
2736
- } catch (err) {
2737
- const message = err instanceof Error ? err.message : String(err);
2738
- results.push({
2739
- storyKey: story.key,
2740
- title: story.title,
2741
- beadsId: null,
2742
- status: "failed",
2743
- storyFilePath,
2744
- error: message
2745
- });
2746
- }
2747
- priority++;
2748
- }
2749
- return results;
2750
- }
2751
2465
 
2752
2466
  // src/modules/infra/bmad-setup.ts
2753
2467
  function setupBmad(opts) {
@@ -2831,49 +2545,9 @@ function verifyBmadOnRerun(projectDir, isJson) {
2831
2545
  }
2832
2546
  }
2833
2547
 
2834
- // src/modules/infra/beads-init.ts
2835
- function initializeBeads(projectDir, isJson) {
2836
- try {
2837
- let beadsResult;
2838
- if (isBeadsInitialized(projectDir)) {
2839
- beadsResult = { status: "already-initialized", hooks_detected: false };
2840
- if (!isJson) {
2841
- info("Beads: .beads/ already exists");
2842
- }
2843
- } else {
2844
- initBeads(projectDir);
2845
- beadsResult = { status: "initialized", hooks_detected: false };
2846
- if (!isJson) {
2847
- ok("Beads: initialized (.beads/ created)");
2848
- }
2849
- }
2850
- const hookDetection = detectBeadsHooks(projectDir);
2851
- beadsResult = { ...beadsResult, hooks_detected: hookDetection.hasHooks };
2852
- if (hookDetection.hasHooks) {
2853
- configureHookCoexistence(projectDir);
2854
- if (!isJson) {
2855
- info("Beads hooks detected \u2014 coexistence configured");
2856
- }
2857
- }
2858
- return beadsResult;
2859
- } catch (err) {
2860
- const message = err instanceof Error ? err.message : String(err);
2861
- const beadsResult = {
2862
- status: "failed",
2863
- hooks_detected: false,
2864
- error: message
2865
- };
2866
- if (!isJson) {
2867
- warn(`Beads init failed: ${message}`);
2868
- info("Beads is optional \u2014 continuing without it");
2869
- }
2870
- return beadsResult;
2871
- }
2872
- }
2873
-
2874
2548
  // src/modules/infra/docs-scaffold.ts
2875
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
2876
- import { join as join13, basename as basename2 } from "path";
2549
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
2550
+ import { join as join12, basename as basename2 } from "path";
2877
2551
 
2878
2552
  // src/templates/readme.ts
2879
2553
  function readmeTemplate(config) {
@@ -2905,9 +2579,9 @@ function getSingleInstallCommand(stack) {
2905
2579
  var DO_NOT_EDIT_HEADER = "<!-- DO NOT EDIT MANUALLY -->\n";
2906
2580
  function getProjectName(projectDir) {
2907
2581
  try {
2908
- const pkgPath = join13(projectDir, "package.json");
2909
- if (existsSync12(pkgPath)) {
2910
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
2582
+ const pkgPath = join12(projectDir, "package.json");
2583
+ if (existsSync11(pkgPath)) {
2584
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
2911
2585
  if (pkg.name && typeof pkg.name === "string") {
2912
2586
  return pkg.name;
2913
2587
  }
@@ -2915,9 +2589,9 @@ function getProjectName(projectDir) {
2915
2589
  } catch {
2916
2590
  }
2917
2591
  try {
2918
- const cargoPath = join13(projectDir, "Cargo.toml");
2919
- if (existsSync12(cargoPath)) {
2920
- const content = readFileSync12(cargoPath, "utf-8");
2592
+ const cargoPath = join12(projectDir, "Cargo.toml");
2593
+ if (existsSync11(cargoPath)) {
2594
+ const content = readFileSync11(cargoPath, "utf-8");
2921
2595
  const packageMatch = content.match(/\[package\]([\s\S]*?)(?=\n\[|$)/s);
2922
2596
  if (packageMatch) {
2923
2597
  const nameMatch = packageMatch[1].match(/^\s*name\s*=\s*["']([^"']+)["']/m);
@@ -3078,8 +2752,8 @@ async function scaffoldDocs(opts) {
3078
2752
  let agentsMd = "skipped";
3079
2753
  let docsScaffold = "skipped";
3080
2754
  let readme = "skipped";
3081
- const agentsMdPath = join13(opts.projectDir, "AGENTS.md");
3082
- if (!existsSync12(agentsMdPath)) {
2755
+ const agentsMdPath = join12(opts.projectDir, "AGENTS.md");
2756
+ if (!existsSync11(agentsMdPath)) {
3083
2757
  const stackArg = opts.stacks && opts.stacks.length > 1 ? opts.stacks : opts.stack;
3084
2758
  const content = generateAgentsMdContent(opts.projectDir, stackArg);
3085
2759
  generateFile(agentsMdPath, content);
@@ -3087,23 +2761,23 @@ async function scaffoldDocs(opts) {
3087
2761
  } else {
3088
2762
  agentsMd = "exists";
3089
2763
  }
3090
- const docsDir = join13(opts.projectDir, "docs");
3091
- if (!existsSync12(docsDir)) {
3092
- generateFile(join13(docsDir, "index.md"), generateDocsIndexContent());
3093
- generateFile(join13(docsDir, "exec-plans", "active", ".gitkeep"), "");
3094
- generateFile(join13(docsDir, "exec-plans", "completed", ".gitkeep"), "");
3095
- generateFile(join13(docsDir, "quality", ".gitkeep"), DO_NOT_EDIT_HEADER);
3096
- generateFile(join13(docsDir, "generated", ".gitkeep"), DO_NOT_EDIT_HEADER);
2764
+ const docsDir = join12(opts.projectDir, "docs");
2765
+ if (!existsSync11(docsDir)) {
2766
+ generateFile(join12(docsDir, "index.md"), generateDocsIndexContent());
2767
+ generateFile(join12(docsDir, "exec-plans", "active", ".gitkeep"), "");
2768
+ generateFile(join12(docsDir, "exec-plans", "completed", ".gitkeep"), "");
2769
+ generateFile(join12(docsDir, "quality", ".gitkeep"), DO_NOT_EDIT_HEADER);
2770
+ generateFile(join12(docsDir, "generated", ".gitkeep"), DO_NOT_EDIT_HEADER);
3097
2771
  docsScaffold = "created";
3098
2772
  } else {
3099
2773
  docsScaffold = "exists";
3100
2774
  }
3101
- const readmePath = join13(opts.projectDir, "README.md");
3102
- if (!existsSync12(readmePath)) {
2775
+ const readmePath = join12(opts.projectDir, "README.md");
2776
+ if (!existsSync11(readmePath)) {
3103
2777
  let cliHelpOutput = "";
3104
2778
  try {
3105
- const { execFileSync: execFileSync13 } = await import("child_process");
3106
- cliHelpOutput = execFileSync13(process.execPath, [process.argv[1], "--help"], {
2779
+ const { execFileSync: execFileSync12 } = await import("child_process");
2780
+ cliHelpOutput = execFileSync12(process.execPath, [process.argv[1], "--help"], {
3107
2781
  stdio: "pipe",
3108
2782
  timeout: 1e4
3109
2783
  }).toString();
@@ -3138,8 +2812,8 @@ async function scaffoldDocs(opts) {
3138
2812
  }
3139
2813
 
3140
2814
  // src/modules/infra/dockerfile-template.ts
3141
- import { existsSync as existsSync13, writeFileSync as writeFileSync8 } from "fs";
3142
- import { join as join14 } from "path";
2815
+ import { existsSync as existsSync12, writeFileSync as writeFileSync7 } from "fs";
2816
+ import { join as join13 } from "path";
3143
2817
  function genericTemplate() {
3144
2818
  return renderTemplateFile("templates/dockerfiles/Dockerfile.generic");
3145
2819
  }
@@ -3176,15 +2850,15 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
3176
2850
  if (!projectDir) {
3177
2851
  return fail2("projectDir is required");
3178
2852
  }
3179
- const dockerfilePath = join14(projectDir, "Dockerfile");
3180
- if (existsSync13(dockerfilePath)) {
2853
+ const dockerfilePath = join13(projectDir, "Dockerfile");
2854
+ if (existsSync12(dockerfilePath)) {
3181
2855
  return fail2("Dockerfile already exists");
3182
2856
  }
3183
2857
  if (Array.isArray(stackOrDetections) && stackOrDetections.length > 1) {
3184
2858
  const content2 = multiStageTemplate(stackOrDetections);
3185
2859
  const stacks = stackOrDetections.map((d) => d.stack);
3186
2860
  try {
3187
- writeFileSync8(dockerfilePath, content2, "utf-8");
2861
+ writeFileSync7(dockerfilePath, content2, "utf-8");
3188
2862
  } catch (err) {
3189
2863
  const message = err instanceof Error ? err.message : String(err);
3190
2864
  return fail2(`Failed to write Dockerfile: ${message}`);
@@ -3203,7 +2877,7 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
3203
2877
  resolvedStack = "generic";
3204
2878
  }
3205
2879
  try {
3206
- writeFileSync8(dockerfilePath, content, "utf-8");
2880
+ writeFileSync7(dockerfilePath, content, "utf-8");
3207
2881
  } catch (err) {
3208
2882
  const message = err instanceof Error ? err.message : String(err);
3209
2883
  return fail2(`Failed to write Dockerfile: ${message}`);
@@ -3212,7 +2886,7 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
3212
2886
  }
3213
2887
 
3214
2888
  // src/modules/infra/init-project.ts
3215
- var HARNESS_VERSION = true ? "0.26.5" : "0.0.0-dev";
2889
+ var HARNESS_VERSION = true ? "0.27.0" : "0.0.0-dev";
3216
2890
  function failResult(opts, error) {
3217
2891
  return {
3218
2892
  status: "fail",
@@ -3277,6 +2951,19 @@ async function initProjectInner(opts) {
3277
2951
  }
3278
2952
  info(`App type: ${appType}`);
3279
2953
  }
2954
+ const workflowSrc = join14(getPackageRoot(), "templates/workflows/default.yaml");
2955
+ const workflowDest = join14(projectDir, ".codeharness/workflows/default.yaml");
2956
+ const workflowRelPath = ".codeharness/workflows/default.yaml";
2957
+ if (existsSync13(workflowDest) && !opts.force) {
2958
+ result.workflow = { status: "exists", path: workflowRelPath };
2959
+ if (!isJson) info(`Workflow: ${workflowRelPath} already exists`);
2960
+ } else {
2961
+ const overwriting = existsSync13(workflowDest);
2962
+ mkdirSync4(dirname3(workflowDest), { recursive: true });
2963
+ copyFileSync(workflowSrc, workflowDest);
2964
+ result.workflow = { status: overwriting ? "overwritten" : "created", path: workflowRelPath };
2965
+ if (!isJson) ok(`Workflow: ${workflowRelPath} ${overwriting ? "overwritten" : "created"}`);
2966
+ }
3280
2967
  const dfResult = generateDockerfileTemplate(projectDir, allStacks);
3281
2968
  if (isOk(dfResult)) {
3282
2969
  result.dockerfile = { generated: true, stack: dfResult.data.stack, stacks: dfResult.data.stacks };
@@ -3314,7 +3001,7 @@ async function initProjectInner(opts) {
3314
3001
  return ok2(result);
3315
3002
  }
3316
3003
  result.dependencies = depResult.data;
3317
- result.beads = initializeBeads(projectDir, isJson);
3004
+ result.beads = { status: "skipped", message: "beads removed" };
3318
3005
  const bmadResult = setupBmad({ projectDir, isJson });
3319
3006
  if (isOk(bmadResult)) result.bmad = bmadResult.data;
3320
3007
  let state = getDefaultState(stack);
@@ -3353,7 +3040,7 @@ async function initProjectInner(opts) {
3353
3040
  if (!isJson) info("OTLP: skipped (--no-observability)");
3354
3041
  } else {
3355
3042
  for (const detection of allStacks) {
3356
- const stackDir = detection.dir === "." ? projectDir : join15(projectDir, detection.dir);
3043
+ const stackDir = detection.dir === "." ? projectDir : join14(projectDir, detection.dir);
3357
3044
  const stackOtlp = instrumentProject(stackDir, detection.stack, { json: isJson, appType });
3358
3045
  if (detection.dir === "." && detection.stack === stack) {
3359
3046
  result.otlp = stackOtlp;
@@ -3398,7 +3085,7 @@ async function initProjectInner(opts) {
3398
3085
  }
3399
3086
  function handleRerun(opts, result) {
3400
3087
  const { projectDir, json: isJson = false } = opts;
3401
- if (!existsSync14(getStatePath(projectDir))) return null;
3088
+ if (!existsSync13(getStatePath(projectDir))) return null;
3402
3089
  try {
3403
3090
  const existingState = readState(projectDir);
3404
3091
  const legacyObsDisabled = existingState.enforcement.observability === false;
@@ -3410,6 +3097,23 @@ function handleRerun(opts, result) {
3410
3097
  result.stacks = existingState.stacks ?? [];
3411
3098
  result.enforcement = existingState.enforcement;
3412
3099
  result.documentation = { agents_md: "exists", docs_scaffold: "exists", readme: "exists" };
3100
+ const workflowRelPath = ".codeharness/workflows/default.yaml";
3101
+ const workflowPath = join14(projectDir, workflowRelPath);
3102
+ if (existsSync13(workflowPath) && !opts.force) {
3103
+ result.workflow = { status: "exists", path: workflowRelPath };
3104
+ } else if (existsSync13(workflowPath) && opts.force) {
3105
+ const workflowSrc = join14(getPackageRoot(), "templates/workflows/default.yaml");
3106
+ mkdirSync4(dirname3(workflowPath), { recursive: true });
3107
+ copyFileSync(workflowSrc, workflowPath);
3108
+ result.workflow = { status: "overwritten", path: workflowRelPath };
3109
+ if (!isJson) ok(`Workflow: ${workflowRelPath} overwritten`);
3110
+ } else {
3111
+ const workflowSrc = join14(getPackageRoot(), "templates/workflows/default.yaml");
3112
+ mkdirSync4(dirname3(workflowPath), { recursive: true });
3113
+ copyFileSync(workflowSrc, workflowPath);
3114
+ result.workflow = { status: "created", path: workflowRelPath };
3115
+ if (!isJson) ok(`Workflow: ${workflowRelPath} created`);
3116
+ }
3413
3117
  result.dependencies = verifyDeps(isJson);
3414
3118
  result.docker = existingState.docker ? { compose_file: existingState.docker.compose_file, stack_running: existingState.docker.stack_running, services: [], ports: existingState.docker.ports } : null;
3415
3119
  const bmadResult = verifyBmadOnRerun(projectDir, isJson);
@@ -3441,10 +3145,10 @@ function validateRemoteUrls(opts) {
3441
3145
  }
3442
3146
 
3443
3147
  // src/modules/infra/stack-management.ts
3444
- import { execFileSync as execFileSync11 } from "child_process";
3148
+ import { execFileSync as execFileSync10 } from "child_process";
3445
3149
 
3446
3150
  // src/modules/infra/container-cleanup.ts
3447
- import { execFileSync as execFileSync12 } from "child_process";
3151
+ import { execFileSync as execFileSync11 } from "child_process";
3448
3152
  var STALE_PATTERNS = ["codeharness-shared-", "codeharness-collector-", "codeharness-verify-"];
3449
3153
  function cleanupContainers() {
3450
3154
  try {
@@ -3454,7 +3158,7 @@ function cleanupContainers() {
3454
3158
  const staleNames = [];
3455
3159
  for (const pattern of STALE_PATTERNS) {
3456
3160
  try {
3457
- const output = execFileSync12(
3161
+ const output = execFileSync11(
3458
3162
  "docker",
3459
3163
  [
3460
3164
  "ps",
@@ -3481,7 +3185,7 @@ function cleanupContainers() {
3481
3185
  const removed = [];
3482
3186
  for (const name of staleNames) {
3483
3187
  try {
3484
- execFileSync12("docker", ["rm", "-f", name], {
3188
+ execFileSync11("docker", ["rm", "-f", name], {
3485
3189
  stdio: "pipe",
3486
3190
  timeout: 1e4
3487
3191
  });
@@ -3502,8 +3206,8 @@ var DEFAULT_METRICS_URL = `http://localhost:${DEFAULT_PORTS.metrics}`;
3502
3206
  var DEFAULT_TRACES_URL = `http://localhost:${DEFAULT_PORTS.traces}`;
3503
3207
 
3504
3208
  // src/modules/infra/dockerfile-validator.ts
3505
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
3506
- import { join as join16 } from "path";
3209
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
3210
+ import { join as join15 } from "path";
3507
3211
  var DEFAULT_RULES = {
3508
3212
  requirePinnedFrom: true,
3509
3213
  requireBinaryOnPath: true,
@@ -3518,8 +3222,8 @@ function dfGap(rule, description, suggestedFix, line) {
3518
3222
  return g;
3519
3223
  }
3520
3224
  function loadRules(projectDir) {
3521
- const rulesPath = join16(projectDir, "patches", "infra", "dockerfile-rules.md");
3522
- if (!existsSync15(rulesPath)) {
3225
+ const rulesPath = join15(projectDir, "patches", "infra", "dockerfile-rules.md");
3226
+ if (!existsSync14(rulesPath)) {
3523
3227
  return {
3524
3228
  rules: DEFAULT_RULES,
3525
3229
  warnings: ["dockerfile-rules.md not found -- using defaults."]
@@ -3601,13 +3305,13 @@ function checkCacheCleanup(lines) {
3601
3305
  return [];
3602
3306
  }
3603
3307
  function validateDockerfile(projectDir) {
3604
- const dfPath = join16(projectDir, "Dockerfile");
3605
- if (!existsSync15(dfPath)) {
3308
+ const dfPath = join15(projectDir, "Dockerfile");
3309
+ if (!existsSync14(dfPath)) {
3606
3310
  return fail2("No Dockerfile found");
3607
3311
  }
3608
3312
  let content;
3609
3313
  try {
3610
- content = readFileSync13(dfPath, "utf-8");
3314
+ content = readFileSync12(dfPath, "utf-8");
3611
3315
  } catch {
3612
3316
  return fail2("Dockerfile exists but could not be read");
3613
3317
  }
@@ -3656,8 +3360,6 @@ export {
3656
3360
  getStackProvider,
3657
3361
  detectStacks,
3658
3362
  detectStack,
3659
- getPackageRoot,
3660
- renderTemplateFile,
3661
3363
  getStatePath,
3662
3364
  writeState,
3663
3365
  readState,
@@ -3689,19 +3391,9 @@ export {
3689
3391
  stopCollectorOnly,
3690
3392
  cleanupOrphanedContainers,
3691
3393
  cleanupVerifyEnv,
3692
- removePatch,
3693
- isBeadsCLIInstalled,
3694
- createIssue,
3695
- closeIssue,
3696
- updateIssue,
3697
- listIssues,
3698
- isBeadsInitialized,
3699
- buildGapId,
3700
- createOrFindIssue,
3701
- PATCH_TARGETS,
3702
3394
  isBmadInstalled,
3395
+ getStoryFilePath,
3703
3396
  parseEpicsFile,
3704
- importStoriesToBeads,
3705
3397
  validateDockerfile,
3706
3398
  initProject2 as initProject,
3707
3399
  cleanupContainers2 as cleanupContainers