codeharness 0.26.5 → 0.28.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.
- package/dist/{chunk-F6L7CXLK.js → chunk-2BBYPR57.js} +104 -411
- package/dist/{docker-VHOP56YP.js → docker-72QTSBOK.js} +1 -1
- package/dist/index.js +5015 -2014
- package/package.json +5 -3
- package/templates/agents/analyst.yaml +10 -0
- package/templates/agents/architect.yaml +11 -0
- package/templates/agents/dev.yaml +10 -0
- package/templates/agents/evaluator.yaml +92 -0
- package/templates/agents/pm.yaml +12 -0
- package/templates/agents/qa.yaml +15 -0
- package/templates/agents/retro.yaml +63 -0
- package/templates/agents/reviewer.yaml +76 -0
- package/templates/agents/sm.yaml +10 -0
- package/templates/agents/tech-writer.yaml +11 -0
- package/templates/agents/ux-designer.yaml +13 -0
- package/templates/workflows/default.yaml +41 -0
- package/ralph/AGENTS.md +0 -48
- package/ralph/bridge.sh +0 -424
- package/ralph/db_schema_gen.sh +0 -109
- package/ralph/drivers/claude-code.sh +0 -140
- package/ralph/exec_plans.sh +0 -252
- package/ralph/harness_status.sh +0 -147
- package/ralph/lib/circuit_breaker.sh +0 -210
- package/ralph/lib/date_utils.sh +0 -60
- package/ralph/lib/timeout_utils.sh +0 -77
- package/ralph/onboard.sh +0 -83
- package/ralph/ralph.sh +0 -1407
- package/ralph/validate_epic_docs.sh +0 -129
- package/ralph/verify_gates.sh +0 -210
|
@@ -371,8 +371,8 @@ function getRunningServices(composeFile, projectName) {
|
|
|
371
371
|
}
|
|
372
372
|
|
|
373
373
|
// src/modules/infra/init-project.ts
|
|
374
|
-
import { existsSync as
|
|
375
|
-
import { basename as basename3, join as
|
|
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
|
|
2133
|
-
import { existsSync as
|
|
2134
|
-
import { join as
|
|
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
|
|
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
|
|
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 =
|
|
2453
|
-
return
|
|
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 =
|
|
2458
|
-
if (
|
|
2243
|
+
const moduleYamlPath = join11(root, "_bmad", "core", "module.yaml");
|
|
2244
|
+
if (existsSync10(moduleYamlPath)) {
|
|
2459
2245
|
try {
|
|
2460
|
-
const content =
|
|
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 =
|
|
2469
|
-
if (
|
|
2254
|
+
const versionFilePath = join11(root, "_bmad", "VERSION");
|
|
2255
|
+
if (existsSync10(versionFilePath)) {
|
|
2470
2256
|
try {
|
|
2471
|
-
return
|
|
2257
|
+
return readFileSync10(versionFilePath, "utf-8").trim() || null;
|
|
2472
2258
|
} catch {
|
|
2473
2259
|
}
|
|
2474
2260
|
}
|
|
2475
|
-
const packageJsonPath =
|
|
2476
|
-
if (
|
|
2261
|
+
const packageJsonPath = join11(root, "_bmad", "package.json");
|
|
2262
|
+
if (existsSync10(packageJsonPath)) {
|
|
2477
2263
|
try {
|
|
2478
|
-
const pkg = JSON.parse(
|
|
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
|
-
|
|
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 =
|
|
2523
|
-
if (!
|
|
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
|
|
2547
|
-
const patchResult = applyPatch(targetFile, patchName, patchContent);
|
|
2332
|
+
const _patchContent = templateFn();
|
|
2548
2333
|
results.push({
|
|
2549
2334
|
patchName,
|
|
2550
2335
|
targetFile,
|
|
2551
|
-
applied:
|
|
2552
|
-
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 =
|
|
2571
|
-
if (
|
|
2356
|
+
const ralphRcPath = join11(root, ".ralph", ".ralphrc");
|
|
2357
|
+
if (existsSync10(ralphRcPath)) {
|
|
2572
2358
|
files.push(".ralph/.ralphrc");
|
|
2573
2359
|
}
|
|
2574
|
-
const dotRalphDir =
|
|
2575
|
-
if (
|
|
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 (!
|
|
2376
|
+
if (!existsSync10(filePath)) {
|
|
2591
2377
|
return [];
|
|
2592
2378
|
}
|
|
2593
|
-
const content =
|
|
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
|
|
2876
|
-
import { join as
|
|
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 =
|
|
2909
|
-
if (
|
|
2910
|
-
const pkg = JSON.parse(
|
|
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 =
|
|
2919
|
-
if (
|
|
2920
|
-
const content =
|
|
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 =
|
|
3082
|
-
if (!
|
|
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 =
|
|
3091
|
-
if (!
|
|
3092
|
-
generateFile(
|
|
3093
|
-
generateFile(
|
|
3094
|
-
generateFile(
|
|
3095
|
-
generateFile(
|
|
3096
|
-
generateFile(
|
|
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 =
|
|
3102
|
-
if (!
|
|
2775
|
+
const readmePath = join12(opts.projectDir, "README.md");
|
|
2776
|
+
if (!existsSync11(readmePath)) {
|
|
3103
2777
|
let cliHelpOutput = "";
|
|
3104
2778
|
try {
|
|
3105
|
-
const { execFileSync:
|
|
3106
|
-
cliHelpOutput =
|
|
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
|
|
3142
|
-
import { join as
|
|
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 =
|
|
3180
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
2889
|
+
var HARNESS_VERSION = true ? "0.28.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 =
|
|
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 :
|
|
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 (!
|
|
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
|
|
3148
|
+
import { execFileSync as execFileSync10 } from "child_process";
|
|
3445
3149
|
|
|
3446
3150
|
// src/modules/infra/container-cleanup.ts
|
|
3447
|
-
import { execFileSync as
|
|
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 =
|
|
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
|
-
|
|
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
|
|
3506
|
-
import { join as
|
|
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 =
|
|
3522
|
-
if (!
|
|
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 =
|
|
3605
|
-
if (!
|
|
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 =
|
|
3314
|
+
content = readFileSync12(dfPath, "utf-8");
|
|
3611
3315
|
} catch {
|
|
3612
3316
|
return fail2("Dockerfile exists but could not be read");
|
|
3613
3317
|
}
|
|
@@ -3657,7 +3361,6 @@ export {
|
|
|
3657
3361
|
detectStacks,
|
|
3658
3362
|
detectStack,
|
|
3659
3363
|
getPackageRoot,
|
|
3660
|
-
renderTemplateFile,
|
|
3661
3364
|
getStatePath,
|
|
3662
3365
|
writeState,
|
|
3663
3366
|
readState,
|
|
@@ -3689,19 +3392,9 @@ export {
|
|
|
3689
3392
|
stopCollectorOnly,
|
|
3690
3393
|
cleanupOrphanedContainers,
|
|
3691
3394
|
cleanupVerifyEnv,
|
|
3692
|
-
removePatch,
|
|
3693
|
-
isBeadsCLIInstalled,
|
|
3694
|
-
createIssue,
|
|
3695
|
-
closeIssue,
|
|
3696
|
-
updateIssue,
|
|
3697
|
-
listIssues,
|
|
3698
|
-
isBeadsInitialized,
|
|
3699
|
-
buildGapId,
|
|
3700
|
-
createOrFindIssue,
|
|
3701
|
-
PATCH_TARGETS,
|
|
3702
3395
|
isBmadInstalled,
|
|
3396
|
+
getStoryFilePath,
|
|
3703
3397
|
parseEpicsFile,
|
|
3704
|
-
importStoriesToBeads,
|
|
3705
3398
|
validateDockerfile,
|
|
3706
3399
|
initProject2 as initProject,
|
|
3707
3400
|
cleanupContainers2 as cleanupContainers
|