codeharness 0.26.4 → 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) {
@@ -1096,25 +1096,33 @@ RUN cargo build --release
1096
1096
  // ── Task 16: getProjectName ───────────────────────────────────────────
1097
1097
  // ── getVerifyDockerfileSection ──────────────────────────────────────
1098
1098
  getVerifyDockerfileSection(projectDir) {
1099
+ let needsBevy = false;
1100
+ const cargoContent = readTextSafe(join6(projectDir, "Cargo.toml"));
1101
+ if (cargoContent) {
1102
+ const depsSection = getCargoDepsSection(cargoContent);
1103
+ needsBevy = hasCargoDep(depsSection, "bevy");
1104
+ }
1105
+ const aptPackages = ["build-essential", "pkg-config", "libssl-dev"];
1106
+ if (needsBevy) {
1107
+ aptPackages.push(
1108
+ "libudev-dev",
1109
+ "libasound2-dev",
1110
+ "libwayland-dev",
1111
+ "libxkbcommon-dev",
1112
+ "libfontconfig1-dev",
1113
+ "libx11-dev"
1114
+ );
1115
+ }
1099
1116
  const lines = [
1100
1117
  "# --- Rust tooling ---",
1118
+ "RUN apt-get update && apt-get install -y --no-install-recommends \\",
1119
+ ` ${aptPackages.join(" ")} \\`,
1120
+ " && rm -rf /var/lib/apt/lists/*",
1101
1121
  'RUN curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable',
1102
1122
  'ENV PATH="/root/.cargo/bin:$PATH"',
1103
1123
  "RUN rustup component add clippy",
1104
1124
  "RUN cargo install cargo-tarpaulin"
1105
1125
  ];
1106
- const cargoContent = readTextSafe(join6(projectDir, "Cargo.toml"));
1107
- if (cargoContent) {
1108
- const depsSection = getCargoDepsSection(cargoContent);
1109
- if (hasCargoDep(depsSection, "bevy")) {
1110
- lines.push(
1111
- "RUN apt-get update && apt-get install -y --no-install-recommends \\",
1112
- " libudev-dev libasound2-dev libwayland-dev libxkbcommon-dev \\",
1113
- " libfontconfig1-dev libx11-dev \\",
1114
- " && rm -rf /var/lib/apt/lists/*"
1115
- );
1116
- }
1117
- }
1118
1126
  return lines.join("\n");
1119
1127
  }
1120
1128
  getProjectName(dir) {
@@ -1337,9 +1345,8 @@ function parseValue(raw) {
1337
1345
  if (raw === "true") return true;
1338
1346
  if (raw === "false") return false;
1339
1347
  if (raw === "null") return null;
1340
- const num = Number(raw);
1341
- if (!Number.isNaN(num) && raw.trim() !== "") return num;
1342
- return raw;
1348
+ const n = Number(raw);
1349
+ return !Number.isNaN(n) && raw.trim() !== "" ? n : raw;
1343
1350
  }
1344
1351
 
1345
1352
  // src/lib/observability/instrument.ts
@@ -1932,9 +1939,7 @@ function handleLocalShared(opts, state) {
1932
1939
  }
1933
1940
  if (!opts.isJson) {
1934
1941
  fail("Observability stack: failed to start");
1935
- if (startResult.error) {
1936
- info(`Error: ${startResult.error}`);
1937
- }
1942
+ if (startResult.error) info(`Error: ${startResult.error}`);
1938
1943
  }
1939
1944
  const docker = {
1940
1945
  compose_file: sharedComposeFile,
@@ -2124,78 +2129,12 @@ function verifyDeps(isJson) {
2124
2129
  }
2125
2130
 
2126
2131
  // src/lib/bmad.ts
2127
- import { execFileSync as execFileSync10 } from "child_process";
2128
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
2129
- import { join as join12 } from "path";
2130
-
2131
- // src/lib/patch-engine.ts
2132
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
2133
- function validatePatchName(patchName) {
2134
- if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(patchName)) {
2135
- throw new Error(
2136
- `Invalid patch name '${patchName}': must be kebab-case (lowercase letters, digits, hyphens)`
2137
- );
2138
- }
2139
- }
2140
- function getPatchMarkers(patchName) {
2141
- validatePatchName(patchName);
2142
- return {
2143
- start: `<!-- CODEHARNESS-PATCH-START:${patchName} -->`,
2144
- end: `<!-- CODEHARNESS-PATCH-END:${patchName} -->`
2145
- };
2146
- }
2147
- function applyPatch(filePath, patchName, patchContent) {
2148
- let content = readFileSync9(filePath, "utf-8");
2149
- const markers = getPatchMarkers(patchName);
2150
- const markerBlock = `${markers.start}
2151
- ${patchContent}
2152
- ${markers.end}`;
2153
- const startIdx = content.indexOf(markers.start);
2154
- const endIdx = content.indexOf(markers.end);
2155
- if (startIdx !== -1 !== (endIdx !== -1)) {
2156
- throw new Error(
2157
- `Corrupted patch markers for '${patchName}': only ${startIdx !== -1 ? "start" : "end"} marker found in ${filePath}`
2158
- );
2159
- }
2160
- if (startIdx !== -1 && endIdx !== -1) {
2161
- if (endIdx < startIdx) {
2162
- throw new Error(
2163
- `Corrupted patch markers for '${patchName}': end marker appears before start marker in ${filePath}`
2164
- );
2165
- }
2166
- const before = content.slice(0, startIdx);
2167
- const after = content.slice(endIdx + markers.end.length);
2168
- content = before + markerBlock + after;
2169
- writeFileSync7(filePath, content, "utf-8");
2170
- return { applied: true, updated: true };
2171
- }
2172
- const trimmed = content.trimEnd();
2173
- content = trimmed + "\n\n" + markerBlock + "\n";
2174
- writeFileSync7(filePath, content, "utf-8");
2175
- return { applied: true, updated: false };
2176
- }
2177
- function removePatch(filePath, patchName) {
2178
- let content = readFileSync9(filePath, "utf-8");
2179
- const markers = getPatchMarkers(patchName);
2180
- const startIdx = content.indexOf(markers.start);
2181
- const endIdx = content.indexOf(markers.end);
2182
- if (startIdx === -1 || endIdx === -1) {
2183
- return false;
2184
- }
2185
- if (endIdx < startIdx) {
2186
- throw new Error(
2187
- `Corrupted patch markers for '${patchName}': end marker appears before start marker in ${filePath}`
2188
- );
2189
- }
2190
- const before = content.slice(0, startIdx);
2191
- const after = content.slice(endIdx + markers.end.length);
2192
- content = before.trimEnd() + "\n" + after.trimStart();
2193
- writeFileSync7(filePath, content, "utf-8");
2194
- return true;
2195
- }
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";
2196
2135
 
2197
2136
  // src/templates/bmad-patches.ts
2198
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
2137
+ import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
2199
2138
  import { join as join10, dirname as dirname2 } from "path";
2200
2139
  import { fileURLToPath as fileURLToPath2 } from "url";
2201
2140
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
@@ -2203,7 +2142,7 @@ function readPatchFile(role, name) {
2203
2142
  const patchPath = join10(__dirname2, "..", "..", "patches", role, `${name}.md`);
2204
2143
  try {
2205
2144
  if (existsSync9(patchPath)) {
2206
- return readFileSync10(patchPath, "utf-8").trim();
2145
+ return readFileSync9(patchPath, "utf-8").trim();
2207
2146
  }
2208
2147
  } catch {
2209
2148
  }
@@ -2278,154 +2217,6 @@ var PATCH_TEMPLATES = {
2278
2217
  "sprint-retro": sprintPlanningRetroPatch
2279
2218
  };
2280
2219
 
2281
- // src/lib/beads.ts
2282
- import { execFileSync as execFileSync9 } from "child_process";
2283
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "fs";
2284
- import { join as join11 } from "path";
2285
- var BeadsError = class extends Error {
2286
- constructor(command, originalMessage) {
2287
- super(`Beads failed: ${originalMessage}. Command: ${command}`);
2288
- this.command = command;
2289
- this.originalMessage = originalMessage;
2290
- this.name = "BeadsError";
2291
- }
2292
- };
2293
- function isBeadsCLIInstalled() {
2294
- try {
2295
- execFileSync9("which", ["bd"], { stdio: "pipe", timeout: 5e3 });
2296
- return true;
2297
- } catch {
2298
- return false;
2299
- }
2300
- }
2301
- function bdCommand(args) {
2302
- const cmdStr = `bd ${args.join(" ")}`;
2303
- let text;
2304
- try {
2305
- const output = execFileSync9("bd", args, {
2306
- stdio: "pipe",
2307
- timeout: 3e4
2308
- });
2309
- text = output.toString().trim();
2310
- } catch (err) {
2311
- const message = err instanceof Error ? err.message : String(err);
2312
- throw new BeadsError(cmdStr, message);
2313
- }
2314
- if (!text) return void 0;
2315
- try {
2316
- return JSON.parse(text);
2317
- } catch {
2318
- throw new BeadsError(cmdStr, `Invalid JSON output from bd: ${text}`);
2319
- }
2320
- }
2321
- function createIssue(title, opts) {
2322
- const args = ["create", title, "--json"];
2323
- if (opts?.type) {
2324
- args.push("--type", opts.type);
2325
- }
2326
- if (opts?.priority !== void 0) {
2327
- args.push("--priority", String(opts.priority));
2328
- }
2329
- if (opts?.description) {
2330
- args.push("--description", opts.description);
2331
- }
2332
- if (opts?.deps && opts.deps.length > 0) {
2333
- for (const dep of opts.deps) {
2334
- args.push("--dep", dep);
2335
- }
2336
- }
2337
- return bdCommand(args);
2338
- }
2339
- function closeIssue(id) {
2340
- bdCommand(["close", id, "--json"]);
2341
- }
2342
- function updateIssue(id, opts) {
2343
- const args = ["update", id, "--json"];
2344
- if (opts.status !== void 0) {
2345
- args.push("--status", opts.status);
2346
- }
2347
- if (opts.priority !== void 0) {
2348
- args.push("--priority", String(opts.priority));
2349
- }
2350
- bdCommand(args);
2351
- }
2352
- function listIssues() {
2353
- return bdCommand(["list", "--json"]);
2354
- }
2355
- function isBeadsInitialized(dir) {
2356
- const beadsDir = join11(dir ?? process.cwd(), ".beads");
2357
- return existsSync10(beadsDir);
2358
- }
2359
- function initBeads(dir) {
2360
- if (isBeadsInitialized(dir)) {
2361
- return;
2362
- }
2363
- const cmdStr = "bd init";
2364
- try {
2365
- execFileSync9("bd", ["init"], {
2366
- stdio: "pipe",
2367
- timeout: 3e4,
2368
- cwd: dir ?? process.cwd()
2369
- });
2370
- } catch (err) {
2371
- const message = err instanceof Error ? err.message : String(err);
2372
- throw new BeadsError(cmdStr, message);
2373
- }
2374
- }
2375
- function detectBeadsHooks(dir) {
2376
- const hooksDir = join11(dir ?? process.cwd(), ".beads", "hooks");
2377
- if (!existsSync10(hooksDir)) {
2378
- return { hasHooks: false, hookTypes: [] };
2379
- }
2380
- try {
2381
- const entries = readdirSync2(hooksDir);
2382
- const hookTypes = entries.filter((e) => !e.startsWith("."));
2383
- return {
2384
- hasHooks: hookTypes.length > 0,
2385
- hookTypes
2386
- };
2387
- } catch {
2388
- return { hasHooks: false, hookTypes: [] };
2389
- }
2390
- }
2391
- function buildGapId(category, identifier) {
2392
- return `[gap:${category}:${identifier}]`;
2393
- }
2394
- function findExistingByGapId(gapId, issues) {
2395
- return issues.find(
2396
- (issue) => issue.status !== "done" && issue.description?.includes(gapId)
2397
- );
2398
- }
2399
- function appendGapId(existingDescription, gapId) {
2400
- if (!existingDescription) {
2401
- return gapId;
2402
- }
2403
- return `${existingDescription}
2404
- ${gapId}`;
2405
- }
2406
- function createOrFindIssue(title, gapId, opts) {
2407
- const issues = listIssues();
2408
- const existing = findExistingByGapId(gapId, issues);
2409
- if (existing) {
2410
- return { issue: existing, created: false };
2411
- }
2412
- const issue = createIssue(title, {
2413
- ...opts,
2414
- description: appendGapId(opts?.description, gapId)
2415
- });
2416
- return { issue, created: true };
2417
- }
2418
- function configureHookCoexistence(dir) {
2419
- const detection = detectBeadsHooks(dir);
2420
- if (!detection.hasHooks) {
2421
- return;
2422
- }
2423
- const gitHooksDir = join11(dir ?? process.cwd(), ".git", "hooks");
2424
- if (!existsSync10(gitHooksDir)) {
2425
- return;
2426
- }
2427
- }
2428
-
2429
2220
  // src/lib/bmad.ts
2430
2221
  var BmadError = class extends Error {
2431
2222
  constructor(command, originalMessage) {
@@ -2444,15 +2235,15 @@ var PATCH_TARGETS = {
2444
2235
  "sprint-retro": "bmm/workflows/4-implementation/sprint-planning/instructions.md"
2445
2236
  };
2446
2237
  function isBmadInstalled(dir) {
2447
- const bmadDir = join12(dir ?? process.cwd(), "_bmad");
2448
- return existsSync11(bmadDir);
2238
+ const bmadDir = join11(dir ?? process.cwd(), "_bmad");
2239
+ return existsSync10(bmadDir);
2449
2240
  }
2450
2241
  function detectBmadVersion(dir) {
2451
2242
  const root = dir ?? process.cwd();
2452
- const moduleYamlPath = join12(root, "_bmad", "core", "module.yaml");
2453
- if (existsSync11(moduleYamlPath)) {
2243
+ const moduleYamlPath = join11(root, "_bmad", "core", "module.yaml");
2244
+ if (existsSync10(moduleYamlPath)) {
2454
2245
  try {
2455
- const content = readFileSync11(moduleYamlPath, "utf-8");
2246
+ const content = readFileSync10(moduleYamlPath, "utf-8");
2456
2247
  const versionMatch = content.match(/version:\s*["']?([^\s"']+)["']?/);
2457
2248
  if (versionMatch) {
2458
2249
  return versionMatch[1];
@@ -2460,17 +2251,17 @@ function detectBmadVersion(dir) {
2460
2251
  } catch {
2461
2252
  }
2462
2253
  }
2463
- const versionFilePath = join12(root, "_bmad", "VERSION");
2464
- if (existsSync11(versionFilePath)) {
2254
+ const versionFilePath = join11(root, "_bmad", "VERSION");
2255
+ if (existsSync10(versionFilePath)) {
2465
2256
  try {
2466
- return readFileSync11(versionFilePath, "utf-8").trim() || null;
2257
+ return readFileSync10(versionFilePath, "utf-8").trim() || null;
2467
2258
  } catch {
2468
2259
  }
2469
2260
  }
2470
- const packageJsonPath = join12(root, "_bmad", "package.json");
2471
- if (existsSync11(packageJsonPath)) {
2261
+ const packageJsonPath = join11(root, "_bmad", "package.json");
2262
+ if (existsSync10(packageJsonPath)) {
2472
2263
  try {
2473
- const pkg = JSON.parse(readFileSync11(packageJsonPath, "utf-8"));
2264
+ const pkg = JSON.parse(readFileSync10(packageJsonPath, "utf-8"));
2474
2265
  return pkg.version ?? null;
2475
2266
  } catch {
2476
2267
  }
@@ -2489,7 +2280,7 @@ function installBmad(dir) {
2489
2280
  }
2490
2281
  const cmdStr = "npx bmad-method install --yes --tools claude-code";
2491
2282
  try {
2492
- execFileSync10("npx", ["bmad-method", "install", "--yes", "--tools", "claude-code"], {
2283
+ execFileSync9("npx", ["bmad-method", "install", "--yes", "--tools", "claude-code"], {
2493
2284
  stdio: "pipe",
2494
2285
  timeout: 12e4,
2495
2286
  // 2 min — npx may need to download the package first time
@@ -2514,8 +2305,8 @@ function applyAllPatches(dir, options) {
2514
2305
  const silent = options?.silent ?? false;
2515
2306
  const results = [];
2516
2307
  for (const [patchName, relativePath] of Object.entries(PATCH_TARGETS)) {
2517
- const targetFile = join12(root, "_bmad", relativePath);
2518
- if (!existsSync11(targetFile)) {
2308
+ const targetFile = join11(root, "_bmad", relativePath);
2309
+ if (!existsSync10(targetFile)) {
2519
2310
  if (!silent) warn(`Patch target not found: ${relativePath}`);
2520
2311
  results.push({
2521
2312
  patchName,
@@ -2538,13 +2329,13 @@ function applyAllPatches(dir, options) {
2538
2329
  continue;
2539
2330
  }
2540
2331
  try {
2541
- const patchContent = templateFn();
2542
- const patchResult = applyPatch(targetFile, patchName, patchContent);
2332
+ const _patchContent = templateFn();
2543
2333
  results.push({
2544
2334
  patchName,
2545
2335
  targetFile,
2546
- applied: patchResult.applied,
2547
- updated: patchResult.updated
2336
+ applied: false,
2337
+ updated: false,
2338
+ error: "Patch engine removed (Story 1.2) \u2014 pending v2 rebuild"
2548
2339
  });
2549
2340
  } catch (err) {
2550
2341
  const message = err instanceof Error ? err.message : String(err);
@@ -2562,12 +2353,12 @@ function applyAllPatches(dir, options) {
2562
2353
  function detectBmalph(dir) {
2563
2354
  const root = dir ?? process.cwd();
2564
2355
  const files = [];
2565
- const ralphRcPath = join12(root, ".ralph", ".ralphrc");
2566
- if (existsSync11(ralphRcPath)) {
2356
+ const ralphRcPath = join11(root, ".ralph", ".ralphrc");
2357
+ if (existsSync10(ralphRcPath)) {
2567
2358
  files.push(".ralph/.ralphrc");
2568
2359
  }
2569
- const dotRalphDir = join12(root, ".ralph");
2570
- if (existsSync11(dotRalphDir)) {
2360
+ const dotRalphDir = join11(root, ".ralph");
2361
+ if (existsSync10(dotRalphDir)) {
2571
2362
  if (files.length === 0) {
2572
2363
  files.push(".ralph/");
2573
2364
  }
@@ -2582,10 +2373,10 @@ function getStoryFilePath(storyKey) {
2582
2373
  return `_bmad-output/implementation-artifacts/${storyKey}.md`;
2583
2374
  }
2584
2375
  function parseEpicsFile(filePath) {
2585
- if (!existsSync11(filePath)) {
2376
+ if (!existsSync10(filePath)) {
2586
2377
  return [];
2587
2378
  }
2588
- const content = readFileSync11(filePath, "utf-8");
2379
+ const content = readFileSync10(filePath, "utf-8");
2589
2380
  if (!content.trim()) {
2590
2381
  return [];
2591
2382
  }
@@ -2671,78 +2462,6 @@ function finalizeStory(raw) {
2671
2462
  technicalNotes
2672
2463
  };
2673
2464
  }
2674
- function importStoriesToBeads(stories, opts, beadsFns) {
2675
- const results = [];
2676
- let existingIssues = [];
2677
- try {
2678
- existingIssues = beadsFns.listIssues();
2679
- } catch {
2680
- }
2681
- const lastBeadsIdByEpic = /* @__PURE__ */ new Map();
2682
- let priority = 1;
2683
- for (const story of stories) {
2684
- const storyFilePath = getStoryFilePath(story.key);
2685
- const gapId = buildGapId("bridge", `${story.epicNumber}.${story.storyNumber}`);
2686
- const existingIssue = findExistingByGapId(gapId, existingIssues);
2687
- if (existingIssue) {
2688
- lastBeadsIdByEpic.set(story.epicNumber, existingIssue.id);
2689
- results.push({
2690
- storyKey: story.key,
2691
- title: story.title,
2692
- beadsId: existingIssue.id,
2693
- status: "exists",
2694
- storyFilePath
2695
- });
2696
- priority++;
2697
- continue;
2698
- }
2699
- if (opts.dryRun) {
2700
- results.push({
2701
- storyKey: story.key,
2702
- title: story.title,
2703
- beadsId: null,
2704
- status: "skipped",
2705
- storyFilePath
2706
- });
2707
- priority++;
2708
- continue;
2709
- }
2710
- const deps = [];
2711
- const prevId = lastBeadsIdByEpic.get(story.epicNumber);
2712
- if (prevId) {
2713
- deps.push(prevId);
2714
- }
2715
- try {
2716
- const description = appendGapId(storyFilePath, gapId);
2717
- const issue = beadsFns.createIssue(story.title, {
2718
- type: "story",
2719
- priority,
2720
- description,
2721
- deps: deps.length > 0 ? deps : void 0
2722
- });
2723
- lastBeadsIdByEpic.set(story.epicNumber, issue.id);
2724
- results.push({
2725
- storyKey: story.key,
2726
- title: story.title,
2727
- beadsId: issue.id,
2728
- status: "created",
2729
- storyFilePath
2730
- });
2731
- } catch (err) {
2732
- const message = err instanceof Error ? err.message : String(err);
2733
- results.push({
2734
- storyKey: story.key,
2735
- title: story.title,
2736
- beadsId: null,
2737
- status: "failed",
2738
- storyFilePath,
2739
- error: message
2740
- });
2741
- }
2742
- priority++;
2743
- }
2744
- return results;
2745
- }
2746
2465
 
2747
2466
  // src/modules/infra/bmad-setup.ts
2748
2467
  function setupBmad(opts) {
@@ -2826,49 +2545,9 @@ function verifyBmadOnRerun(projectDir, isJson) {
2826
2545
  }
2827
2546
  }
2828
2547
 
2829
- // src/modules/infra/beads-init.ts
2830
- function initializeBeads(projectDir, isJson) {
2831
- try {
2832
- let beadsResult;
2833
- if (isBeadsInitialized(projectDir)) {
2834
- beadsResult = { status: "already-initialized", hooks_detected: false };
2835
- if (!isJson) {
2836
- info("Beads: .beads/ already exists");
2837
- }
2838
- } else {
2839
- initBeads(projectDir);
2840
- beadsResult = { status: "initialized", hooks_detected: false };
2841
- if (!isJson) {
2842
- ok("Beads: initialized (.beads/ created)");
2843
- }
2844
- }
2845
- const hookDetection = detectBeadsHooks(projectDir);
2846
- beadsResult = { ...beadsResult, hooks_detected: hookDetection.hasHooks };
2847
- if (hookDetection.hasHooks) {
2848
- configureHookCoexistence(projectDir);
2849
- if (!isJson) {
2850
- info("Beads hooks detected \u2014 coexistence configured");
2851
- }
2852
- }
2853
- return beadsResult;
2854
- } catch (err) {
2855
- const message = err instanceof Error ? err.message : String(err);
2856
- const beadsResult = {
2857
- status: "failed",
2858
- hooks_detected: false,
2859
- error: message
2860
- };
2861
- if (!isJson) {
2862
- warn(`Beads init failed: ${message}`);
2863
- info("Beads is optional \u2014 continuing without it");
2864
- }
2865
- return beadsResult;
2866
- }
2867
- }
2868
-
2869
2548
  // src/modules/infra/docs-scaffold.ts
2870
- import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
2871
- 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";
2872
2551
 
2873
2552
  // src/templates/readme.ts
2874
2553
  function readmeTemplate(config) {
@@ -2900,9 +2579,9 @@ function getSingleInstallCommand(stack) {
2900
2579
  var DO_NOT_EDIT_HEADER = "<!-- DO NOT EDIT MANUALLY -->\n";
2901
2580
  function getProjectName(projectDir) {
2902
2581
  try {
2903
- const pkgPath = join13(projectDir, "package.json");
2904
- if (existsSync12(pkgPath)) {
2905
- 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"));
2906
2585
  if (pkg.name && typeof pkg.name === "string") {
2907
2586
  return pkg.name;
2908
2587
  }
@@ -2910,9 +2589,9 @@ function getProjectName(projectDir) {
2910
2589
  } catch {
2911
2590
  }
2912
2591
  try {
2913
- const cargoPath = join13(projectDir, "Cargo.toml");
2914
- if (existsSync12(cargoPath)) {
2915
- const content = readFileSync12(cargoPath, "utf-8");
2592
+ const cargoPath = join12(projectDir, "Cargo.toml");
2593
+ if (existsSync11(cargoPath)) {
2594
+ const content = readFileSync11(cargoPath, "utf-8");
2916
2595
  const packageMatch = content.match(/\[package\]([\s\S]*?)(?=\n\[|$)/s);
2917
2596
  if (packageMatch) {
2918
2597
  const nameMatch = packageMatch[1].match(/^\s*name\s*=\s*["']([^"']+)["']/m);
@@ -3073,8 +2752,8 @@ async function scaffoldDocs(opts) {
3073
2752
  let agentsMd = "skipped";
3074
2753
  let docsScaffold = "skipped";
3075
2754
  let readme = "skipped";
3076
- const agentsMdPath = join13(opts.projectDir, "AGENTS.md");
3077
- if (!existsSync12(agentsMdPath)) {
2755
+ const agentsMdPath = join12(opts.projectDir, "AGENTS.md");
2756
+ if (!existsSync11(agentsMdPath)) {
3078
2757
  const stackArg = opts.stacks && opts.stacks.length > 1 ? opts.stacks : opts.stack;
3079
2758
  const content = generateAgentsMdContent(opts.projectDir, stackArg);
3080
2759
  generateFile(agentsMdPath, content);
@@ -3082,23 +2761,23 @@ async function scaffoldDocs(opts) {
3082
2761
  } else {
3083
2762
  agentsMd = "exists";
3084
2763
  }
3085
- const docsDir = join13(opts.projectDir, "docs");
3086
- if (!existsSync12(docsDir)) {
3087
- generateFile(join13(docsDir, "index.md"), generateDocsIndexContent());
3088
- generateFile(join13(docsDir, "exec-plans", "active", ".gitkeep"), "");
3089
- generateFile(join13(docsDir, "exec-plans", "completed", ".gitkeep"), "");
3090
- generateFile(join13(docsDir, "quality", ".gitkeep"), DO_NOT_EDIT_HEADER);
3091
- 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);
3092
2771
  docsScaffold = "created";
3093
2772
  } else {
3094
2773
  docsScaffold = "exists";
3095
2774
  }
3096
- const readmePath = join13(opts.projectDir, "README.md");
3097
- if (!existsSync12(readmePath)) {
2775
+ const readmePath = join12(opts.projectDir, "README.md");
2776
+ if (!existsSync11(readmePath)) {
3098
2777
  let cliHelpOutput = "";
3099
2778
  try {
3100
- const { execFileSync: execFileSync13 } = await import("child_process");
3101
- 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"], {
3102
2781
  stdio: "pipe",
3103
2782
  timeout: 1e4
3104
2783
  }).toString();
@@ -3133,8 +2812,8 @@ async function scaffoldDocs(opts) {
3133
2812
  }
3134
2813
 
3135
2814
  // src/modules/infra/dockerfile-template.ts
3136
- import { existsSync as existsSync13, writeFileSync as writeFileSync8 } from "fs";
3137
- import { join as join14 } from "path";
2815
+ import { existsSync as existsSync12, writeFileSync as writeFileSync7 } from "fs";
2816
+ import { join as join13 } from "path";
3138
2817
  function genericTemplate() {
3139
2818
  return renderTemplateFile("templates/dockerfiles/Dockerfile.generic");
3140
2819
  }
@@ -3171,15 +2850,15 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
3171
2850
  if (!projectDir) {
3172
2851
  return fail2("projectDir is required");
3173
2852
  }
3174
- const dockerfilePath = join14(projectDir, "Dockerfile");
3175
- if (existsSync13(dockerfilePath)) {
2853
+ const dockerfilePath = join13(projectDir, "Dockerfile");
2854
+ if (existsSync12(dockerfilePath)) {
3176
2855
  return fail2("Dockerfile already exists");
3177
2856
  }
3178
2857
  if (Array.isArray(stackOrDetections) && stackOrDetections.length > 1) {
3179
2858
  const content2 = multiStageTemplate(stackOrDetections);
3180
2859
  const stacks = stackOrDetections.map((d) => d.stack);
3181
2860
  try {
3182
- writeFileSync8(dockerfilePath, content2, "utf-8");
2861
+ writeFileSync7(dockerfilePath, content2, "utf-8");
3183
2862
  } catch (err) {
3184
2863
  const message = err instanceof Error ? err.message : String(err);
3185
2864
  return fail2(`Failed to write Dockerfile: ${message}`);
@@ -3198,7 +2877,7 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
3198
2877
  resolvedStack = "generic";
3199
2878
  }
3200
2879
  try {
3201
- writeFileSync8(dockerfilePath, content, "utf-8");
2880
+ writeFileSync7(dockerfilePath, content, "utf-8");
3202
2881
  } catch (err) {
3203
2882
  const message = err instanceof Error ? err.message : String(err);
3204
2883
  return fail2(`Failed to write Dockerfile: ${message}`);
@@ -3207,7 +2886,7 @@ function generateDockerfileTemplate(projectDir, stackOrDetections) {
3207
2886
  }
3208
2887
 
3209
2888
  // src/modules/infra/init-project.ts
3210
- var HARNESS_VERSION = true ? "0.26.4" : "0.0.0-dev";
2889
+ var HARNESS_VERSION = true ? "0.27.0" : "0.0.0-dev";
3211
2890
  function failResult(opts, error) {
3212
2891
  return {
3213
2892
  status: "fail",
@@ -3272,6 +2951,19 @@ async function initProjectInner(opts) {
3272
2951
  }
3273
2952
  info(`App type: ${appType}`);
3274
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
+ }
3275
2967
  const dfResult = generateDockerfileTemplate(projectDir, allStacks);
3276
2968
  if (isOk(dfResult)) {
3277
2969
  result.dockerfile = { generated: true, stack: dfResult.data.stack, stacks: dfResult.data.stacks };
@@ -3309,7 +3001,7 @@ async function initProjectInner(opts) {
3309
3001
  return ok2(result);
3310
3002
  }
3311
3003
  result.dependencies = depResult.data;
3312
- result.beads = initializeBeads(projectDir, isJson);
3004
+ result.beads = { status: "skipped", message: "beads removed" };
3313
3005
  const bmadResult = setupBmad({ projectDir, isJson });
3314
3006
  if (isOk(bmadResult)) result.bmad = bmadResult.data;
3315
3007
  let state = getDefaultState(stack);
@@ -3348,7 +3040,7 @@ async function initProjectInner(opts) {
3348
3040
  if (!isJson) info("OTLP: skipped (--no-observability)");
3349
3041
  } else {
3350
3042
  for (const detection of allStacks) {
3351
- const stackDir = detection.dir === "." ? projectDir : join15(projectDir, detection.dir);
3043
+ const stackDir = detection.dir === "." ? projectDir : join14(projectDir, detection.dir);
3352
3044
  const stackOtlp = instrumentProject(stackDir, detection.stack, { json: isJson, appType });
3353
3045
  if (detection.dir === "." && detection.stack === stack) {
3354
3046
  result.otlp = stackOtlp;
@@ -3393,7 +3085,7 @@ async function initProjectInner(opts) {
3393
3085
  }
3394
3086
  function handleRerun(opts, result) {
3395
3087
  const { projectDir, json: isJson = false } = opts;
3396
- if (!existsSync14(getStatePath(projectDir))) return null;
3088
+ if (!existsSync13(getStatePath(projectDir))) return null;
3397
3089
  try {
3398
3090
  const existingState = readState(projectDir);
3399
3091
  const legacyObsDisabled = existingState.enforcement.observability === false;
@@ -3405,6 +3097,23 @@ function handleRerun(opts, result) {
3405
3097
  result.stacks = existingState.stacks ?? [];
3406
3098
  result.enforcement = existingState.enforcement;
3407
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
+ }
3408
3117
  result.dependencies = verifyDeps(isJson);
3409
3118
  result.docker = existingState.docker ? { compose_file: existingState.docker.compose_file, stack_running: existingState.docker.stack_running, services: [], ports: existingState.docker.ports } : null;
3410
3119
  const bmadResult = verifyBmadOnRerun(projectDir, isJson);
@@ -3436,10 +3145,10 @@ function validateRemoteUrls(opts) {
3436
3145
  }
3437
3146
 
3438
3147
  // src/modules/infra/stack-management.ts
3439
- import { execFileSync as execFileSync11 } from "child_process";
3148
+ import { execFileSync as execFileSync10 } from "child_process";
3440
3149
 
3441
3150
  // src/modules/infra/container-cleanup.ts
3442
- import { execFileSync as execFileSync12 } from "child_process";
3151
+ import { execFileSync as execFileSync11 } from "child_process";
3443
3152
  var STALE_PATTERNS = ["codeharness-shared-", "codeharness-collector-", "codeharness-verify-"];
3444
3153
  function cleanupContainers() {
3445
3154
  try {
@@ -3449,7 +3158,7 @@ function cleanupContainers() {
3449
3158
  const staleNames = [];
3450
3159
  for (const pattern of STALE_PATTERNS) {
3451
3160
  try {
3452
- const output = execFileSync12(
3161
+ const output = execFileSync11(
3453
3162
  "docker",
3454
3163
  [
3455
3164
  "ps",
@@ -3476,7 +3185,7 @@ function cleanupContainers() {
3476
3185
  const removed = [];
3477
3186
  for (const name of staleNames) {
3478
3187
  try {
3479
- execFileSync12("docker", ["rm", "-f", name], {
3188
+ execFileSync11("docker", ["rm", "-f", name], {
3480
3189
  stdio: "pipe",
3481
3190
  timeout: 1e4
3482
3191
  });
@@ -3497,8 +3206,8 @@ var DEFAULT_METRICS_URL = `http://localhost:${DEFAULT_PORTS.metrics}`;
3497
3206
  var DEFAULT_TRACES_URL = `http://localhost:${DEFAULT_PORTS.traces}`;
3498
3207
 
3499
3208
  // src/modules/infra/dockerfile-validator.ts
3500
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
3501
- import { join as join16 } from "path";
3209
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
3210
+ import { join as join15 } from "path";
3502
3211
  var DEFAULT_RULES = {
3503
3212
  requirePinnedFrom: true,
3504
3213
  requireBinaryOnPath: true,
@@ -3513,8 +3222,8 @@ function dfGap(rule, description, suggestedFix, line) {
3513
3222
  return g;
3514
3223
  }
3515
3224
  function loadRules(projectDir) {
3516
- const rulesPath = join16(projectDir, "patches", "infra", "dockerfile-rules.md");
3517
- if (!existsSync15(rulesPath)) {
3225
+ const rulesPath = join15(projectDir, "patches", "infra", "dockerfile-rules.md");
3226
+ if (!existsSync14(rulesPath)) {
3518
3227
  return {
3519
3228
  rules: DEFAULT_RULES,
3520
3229
  warnings: ["dockerfile-rules.md not found -- using defaults."]
@@ -3596,13 +3305,13 @@ function checkCacheCleanup(lines) {
3596
3305
  return [];
3597
3306
  }
3598
3307
  function validateDockerfile(projectDir) {
3599
- const dfPath = join16(projectDir, "Dockerfile");
3600
- if (!existsSync15(dfPath)) {
3308
+ const dfPath = join15(projectDir, "Dockerfile");
3309
+ if (!existsSync14(dfPath)) {
3601
3310
  return fail2("No Dockerfile found");
3602
3311
  }
3603
3312
  let content;
3604
3313
  try {
3605
- content = readFileSync13(dfPath, "utf-8");
3314
+ content = readFileSync12(dfPath, "utf-8");
3606
3315
  } catch {
3607
3316
  return fail2("Dockerfile exists but could not be read");
3608
3317
  }
@@ -3651,8 +3360,6 @@ export {
3651
3360
  getStackProvider,
3652
3361
  detectStacks,
3653
3362
  detectStack,
3654
- getPackageRoot,
3655
- renderTemplateFile,
3656
3363
  getStatePath,
3657
3364
  writeState,
3658
3365
  readState,
@@ -3684,19 +3391,9 @@ export {
3684
3391
  stopCollectorOnly,
3685
3392
  cleanupOrphanedContainers,
3686
3393
  cleanupVerifyEnv,
3687
- removePatch,
3688
- isBeadsCLIInstalled,
3689
- createIssue,
3690
- closeIssue,
3691
- updateIssue,
3692
- listIssues,
3693
- isBeadsInitialized,
3694
- buildGapId,
3695
- createOrFindIssue,
3696
- PATCH_TARGETS,
3697
3394
  isBmadInstalled,
3395
+ getStoryFilePath,
3698
3396
  parseEpicsFile,
3699
- importStoriesToBeads,
3700
3397
  validateDockerfile,
3701
3398
  initProject2 as initProject,
3702
3399
  cleanupContainers2 as cleanupContainers