ralphctl 0.7.0 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +666 -301
- package/dist/manifest.json +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -2253,30 +2253,6 @@ var runSignalParsers = (text, timestamp, parsers = DEFAULT_SIGNAL_PARSERS) => {
|
|
|
2253
2253
|
var parseHarnessSignals = (text, timestamp) => runSignalParsers(text, timestamp);
|
|
2254
2254
|
|
|
2255
2255
|
// src/integration/ai/providers/claude/parse-stream.ts
|
|
2256
|
-
var parseClaudeJsonEnvelope = (stdout) => {
|
|
2257
|
-
const trimmed = stdout.trim();
|
|
2258
|
-
if (trimmed.length === 0) {
|
|
2259
|
-
return { body: "", sessionId: void 0, model: void 0 };
|
|
2260
|
-
}
|
|
2261
|
-
let parsed;
|
|
2262
|
-
try {
|
|
2263
|
-
parsed = JSON.parse(trimmed);
|
|
2264
|
-
} catch {
|
|
2265
|
-
return { body: stdout, sessionId: void 0, model: void 0 };
|
|
2266
|
-
}
|
|
2267
|
-
if (typeof parsed !== "object" || parsed === null) {
|
|
2268
|
-
return { body: stdout, sessionId: void 0, model: void 0 };
|
|
2269
|
-
}
|
|
2270
|
-
const obj = parsed;
|
|
2271
|
-
const result = stringField(obj, "result");
|
|
2272
|
-
const sessionId2 = stringField(obj, "session_id", "sessionId");
|
|
2273
|
-
const model = stringField(obj, "model");
|
|
2274
|
-
return {
|
|
2275
|
-
body: result ?? stdout,
|
|
2276
|
-
sessionId: sessionId2,
|
|
2277
|
-
model
|
|
2278
|
-
};
|
|
2279
|
-
};
|
|
2280
2256
|
var stringField = (obj, ...names) => {
|
|
2281
2257
|
for (const name of names) {
|
|
2282
2258
|
const v = obj[name];
|
|
@@ -2284,6 +2260,63 @@ var stringField = (obj, ...names) => {
|
|
|
2284
2260
|
}
|
|
2285
2261
|
return void 0;
|
|
2286
2262
|
};
|
|
2263
|
+
var createClaudeStreamParser = () => {
|
|
2264
|
+
let buffer = "";
|
|
2265
|
+
let body = "";
|
|
2266
|
+
let sessionId2;
|
|
2267
|
+
let model;
|
|
2268
|
+
const emit = (raw, onLine) => {
|
|
2269
|
+
if (raw.length === 0) return;
|
|
2270
|
+
if (raw.startsWith("{") && raw.endsWith("}")) {
|
|
2271
|
+
try {
|
|
2272
|
+
const json = JSON.parse(raw);
|
|
2273
|
+
onLine({ raw, json });
|
|
2274
|
+
return;
|
|
2275
|
+
} catch {
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
onLine({ raw });
|
|
2279
|
+
};
|
|
2280
|
+
const ingest = (line) => {
|
|
2281
|
+
const json = line.json;
|
|
2282
|
+
if (json === void 0) return;
|
|
2283
|
+
if (sessionId2 === void 0) {
|
|
2284
|
+
const seen = stringField(json, "session_id", "sessionId");
|
|
2285
|
+
if (seen !== void 0) sessionId2 = seen;
|
|
2286
|
+
}
|
|
2287
|
+
const type = stringField(json, "type");
|
|
2288
|
+
if (type === "system" && model === void 0) {
|
|
2289
|
+
const m = stringField(json, "model");
|
|
2290
|
+
if (m !== void 0) model = m;
|
|
2291
|
+
}
|
|
2292
|
+
if (type === "result") {
|
|
2293
|
+
const r = stringField(json, "result");
|
|
2294
|
+
if (r !== void 0) body = r;
|
|
2295
|
+
}
|
|
2296
|
+
};
|
|
2297
|
+
return {
|
|
2298
|
+
feed(chunk, onLine) {
|
|
2299
|
+
buffer += chunk;
|
|
2300
|
+
let nl = buffer.indexOf("\n");
|
|
2301
|
+
while (nl !== -1) {
|
|
2302
|
+
const line = buffer.slice(0, nl);
|
|
2303
|
+
buffer = buffer.slice(nl + 1);
|
|
2304
|
+
emit(line, onLine);
|
|
2305
|
+
nl = buffer.indexOf("\n");
|
|
2306
|
+
}
|
|
2307
|
+
},
|
|
2308
|
+
flush(onLine) {
|
|
2309
|
+
if (buffer.length > 0) {
|
|
2310
|
+
emit(buffer, onLine);
|
|
2311
|
+
buffer = "";
|
|
2312
|
+
}
|
|
2313
|
+
},
|
|
2314
|
+
ingest,
|
|
2315
|
+
snapshot() {
|
|
2316
|
+
return { body, sessionId: sessionId2, model };
|
|
2317
|
+
}
|
|
2318
|
+
};
|
|
2319
|
+
};
|
|
2287
2320
|
|
|
2288
2321
|
// src/integration/ai/providers/_engine/idle-watchdog.ts
|
|
2289
2322
|
var DEFAULT_IDLE_MS = 5 * 6e4;
|
|
@@ -2428,7 +2461,7 @@ var buildClaudeArgs = (session) => {
|
|
|
2428
2461
|
})
|
|
2429
2462
|
);
|
|
2430
2463
|
}
|
|
2431
|
-
const args = ["-p", "--output-format", "json", "--model", session.model];
|
|
2464
|
+
const args = ["-p", "--verbose", "--output-format", "stream-json", "--model", session.model];
|
|
2432
2465
|
args.push("--permission-mode", "bypassPermissions");
|
|
2433
2466
|
const denied = disallowedToolsFor(session.permissions);
|
|
2434
2467
|
if (denied.length > 0) {
|
|
@@ -2505,13 +2538,14 @@ var spawnAttempt = async (input) => {
|
|
|
2505
2538
|
const child = spawnFn(command, args, {
|
|
2506
2539
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2507
2540
|
});
|
|
2508
|
-
|
|
2541
|
+
const parser = createClaudeStreamParser();
|
|
2509
2542
|
let stderrBuf = "";
|
|
2543
|
+
const onLine = (line) => {
|
|
2544
|
+
parser.ingest(line);
|
|
2545
|
+
};
|
|
2510
2546
|
const { code, signal } = await runHeadlessSpawn({
|
|
2511
2547
|
child,
|
|
2512
|
-
onStdout: (chunk) =>
|
|
2513
|
-
stdoutBuf += chunk;
|
|
2514
|
-
},
|
|
2548
|
+
onStdout: (chunk) => parser.feed(chunk, onLine),
|
|
2515
2549
|
onStderr: (chunk) => {
|
|
2516
2550
|
stderrBuf += chunk;
|
|
2517
2551
|
},
|
|
@@ -2530,6 +2564,7 @@ var spawnAttempt = async (input) => {
|
|
|
2530
2564
|
});
|
|
2531
2565
|
}
|
|
2532
2566
|
});
|
|
2567
|
+
parser.flush(onLine);
|
|
2533
2568
|
if (signal === "SIGTERM") {
|
|
2534
2569
|
return {
|
|
2535
2570
|
kind: "error",
|
|
@@ -2541,9 +2576,8 @@ var spawnAttempt = async (input) => {
|
|
|
2541
2576
|
})
|
|
2542
2577
|
};
|
|
2543
2578
|
}
|
|
2579
|
+
const envelope = parser.snapshot();
|
|
2544
2580
|
if (code === 0) {
|
|
2545
|
-
const envelope = parseClaudeJsonEnvelope(stdoutBuf);
|
|
2546
|
-
stdoutBuf = "";
|
|
2547
2581
|
if (envelope.sessionId !== void 0) {
|
|
2548
2582
|
deps.eventBus.publish({
|
|
2549
2583
|
type: "log",
|
|
@@ -2578,13 +2612,12 @@ var spawnAttempt = async (input) => {
|
|
|
2578
2612
|
};
|
|
2579
2613
|
}
|
|
2580
2614
|
if (RATE_LIMIT_RE.test(stderrBuf)) {
|
|
2581
|
-
const sessionIdOnFailure = parseClaudeJsonEnvelope(stdoutBuf).sessionId;
|
|
2582
2615
|
return {
|
|
2583
2616
|
kind: "rate-limit",
|
|
2584
2617
|
error: new RateLimitError({
|
|
2585
2618
|
subCode: "spawn-stderr",
|
|
2586
2619
|
message: `claude-provider: rate-limit detected in stderr (exit ${String(code)})`,
|
|
2587
|
-
...
|
|
2620
|
+
...envelope.sessionId !== void 0 ? { sessionId: envelope.sessionId } : {}
|
|
2588
2621
|
})
|
|
2589
2622
|
};
|
|
2590
2623
|
}
|
|
@@ -2634,24 +2667,16 @@ var buildCodexArgs = (session, opts) => {
|
|
|
2634
2667
|
const perms = sandboxFor(session.permissions);
|
|
2635
2668
|
if (!perms.ok) return Result.error(perms.error);
|
|
2636
2669
|
const args = ["exec"];
|
|
2637
|
-
|
|
2670
|
+
const isResume = session.resume !== void 0;
|
|
2671
|
+
if (isResume) {
|
|
2638
2672
|
args.push("resume", String(session.resume));
|
|
2639
2673
|
}
|
|
2640
|
-
args.push(
|
|
2641
|
-
|
|
2642
|
-
"
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
"-m",
|
|
2647
|
-
session.model,
|
|
2648
|
-
"-C",
|
|
2649
|
-
String(session.cwd),
|
|
2650
|
-
"-s",
|
|
2651
|
-
perms.value.sandbox
|
|
2652
|
-
);
|
|
2653
|
-
for (const root of session.additionalRoots ?? []) {
|
|
2654
|
-
args.push("--add-dir", String(root));
|
|
2674
|
+
args.push("--ephemeral", "--skip-git-repo-check", "-o", opts.outputFile, "--json", "-m", session.model);
|
|
2675
|
+
if (!isResume) {
|
|
2676
|
+
args.push("-C", String(session.cwd), "-s", perms.value.sandbox);
|
|
2677
|
+
for (const root of session.additionalRoots ?? []) {
|
|
2678
|
+
args.push("--add-dir", String(root));
|
|
2679
|
+
}
|
|
2655
2680
|
}
|
|
2656
2681
|
if (opts.reasoningEffort !== void 0) {
|
|
2657
2682
|
args.push("-c", `model_reasoning_effort=${opts.reasoningEffort}`);
|
|
@@ -2829,6 +2854,18 @@ var spawnAttempt2 = async (input) => {
|
|
|
2829
2854
|
const signals = parseHarnessSignals(body, IsoTimestamp.now());
|
|
2830
2855
|
const wrote = await writeJsonAtomic(String(session.signalsFile), signals);
|
|
2831
2856
|
if (!wrote.ok) return { kind: "error", error: wrote.error };
|
|
2857
|
+
if (session.bodyFile !== void 0) {
|
|
2858
|
+
const bodyWrote = await writeTextAtomic(String(session.bodyFile), body);
|
|
2859
|
+
if (!bodyWrote.ok) {
|
|
2860
|
+
deps.eventBus.publish({
|
|
2861
|
+
type: "log",
|
|
2862
|
+
level: "warn",
|
|
2863
|
+
message: `codex-provider: failed to write body file \u2014 diagnostic capture skipped`,
|
|
2864
|
+
meta: { bodyFile: String(session.bodyFile), error: bodyWrote.error.message },
|
|
2865
|
+
at: IsoTimestamp.now()
|
|
2866
|
+
});
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2832
2869
|
return {
|
|
2833
2870
|
kind: "success",
|
|
2834
2871
|
output: {
|
|
@@ -3211,6 +3248,7 @@ var stringifyError4 = (cause) => cause instanceof Error ? cause.message : String
|
|
|
3211
3248
|
|
|
3212
3249
|
// src/integration/ai/providers/codex/interactive.ts
|
|
3213
3250
|
import { spawn as nodeSpawn7 } from "child_process";
|
|
3251
|
+
import { dirname as dirname4 } from "path";
|
|
3214
3252
|
var defaultSpawn7 = (command, args, options) => nodeSpawn7(command, [...args], { stdio: options.stdio, cwd: options.cwd });
|
|
3215
3253
|
var createInteractiveCodexProvider = (deps) => {
|
|
3216
3254
|
const spawnFn = deps.spawn ?? defaultSpawn7;
|
|
@@ -3227,10 +3265,23 @@ var createInteractiveCodexProvider = (deps) => {
|
|
|
3227
3265
|
})
|
|
3228
3266
|
);
|
|
3229
3267
|
}
|
|
3268
|
+
const allRoots = [
|
|
3269
|
+
String(input.cwd),
|
|
3270
|
+
...input.additionalRoots?.map((r) => String(r)) ?? [],
|
|
3271
|
+
dirname4(String(input.outputFile)),
|
|
3272
|
+
dirname4(String(input.promptFile))
|
|
3273
|
+
];
|
|
3274
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3275
|
+
const dirFlags = allRoots.filter((p) => {
|
|
3276
|
+
if (seen.has(p)) return false;
|
|
3277
|
+
seen.add(p);
|
|
3278
|
+
return true;
|
|
3279
|
+
}).flatMap((p) => ["--add-dir", shellQuote2(p)]);
|
|
3230
3280
|
const inner = [
|
|
3231
3281
|
"codex",
|
|
3232
3282
|
"--cd",
|
|
3233
3283
|
shellQuote2(String(input.cwd)),
|
|
3284
|
+
...dirFlags,
|
|
3234
3285
|
"--model",
|
|
3235
3286
|
shellQuote2(input.model),
|
|
3236
3287
|
"-s",
|
|
@@ -3286,7 +3337,7 @@ var stringifyError5 = (cause) => cause instanceof Error ? cause.message : String
|
|
|
3286
3337
|
// src/integration/ai/providers/copilot/interactive.ts
|
|
3287
3338
|
import { promises as fs6 } from "fs";
|
|
3288
3339
|
import { spawn as nodeSpawn8 } from "child_process";
|
|
3289
|
-
import { dirname as
|
|
3340
|
+
import { dirname as dirname5 } from "path";
|
|
3290
3341
|
var defaultSpawn8 = (command, args, options) => nodeSpawn8(command, [...args], { stdio: options.stdio, cwd: options.cwd });
|
|
3291
3342
|
var defaultReadFile = (path) => fs6.readFile(path, "utf8");
|
|
3292
3343
|
var createInteractiveCopilotProvider = (deps) => {
|
|
@@ -3320,8 +3371,8 @@ var createInteractiveCopilotProvider = (deps) => {
|
|
|
3320
3371
|
const allRoots = [
|
|
3321
3372
|
String(input.cwd),
|
|
3322
3373
|
...input.additionalRoots?.map((r) => String(r)) ?? [],
|
|
3323
|
-
|
|
3324
|
-
|
|
3374
|
+
dirname5(String(input.outputFile)),
|
|
3375
|
+
dirname5(String(input.promptFile))
|
|
3325
3376
|
];
|
|
3326
3377
|
const seen = /* @__PURE__ */ new Set();
|
|
3327
3378
|
const dirFlags = allRoots.filter((p) => {
|
|
@@ -3864,7 +3915,7 @@ var createPullRequestCreator = (deps) => async (input) => {
|
|
|
3864
3915
|
|
|
3865
3916
|
// src/integration/ai/prompts/_engine/fs-template-loader.ts
|
|
3866
3917
|
import { promises as fs7 } from "fs";
|
|
3867
|
-
import { dirname as
|
|
3918
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
3868
3919
|
import { fileURLToPath } from "url";
|
|
3869
3920
|
var createFsTemplateLoader = (templatesDir) => ({
|
|
3870
3921
|
async load(name) {
|
|
@@ -3898,7 +3949,7 @@ var tryRead = async (path) => {
|
|
|
3898
3949
|
}
|
|
3899
3950
|
};
|
|
3900
3951
|
var TEMPLATES_DIR = (() => {
|
|
3901
|
-
const here =
|
|
3952
|
+
const here = dirname6(fileURLToPath(import.meta.url));
|
|
3902
3953
|
const isBundled = import.meta.url.endsWith("/cli.mjs") || import.meta.url.endsWith("\\cli.mjs");
|
|
3903
3954
|
const path = isBundled ? join6(here, "prompts") : join6(here, "..");
|
|
3904
3955
|
const parsed = AbsolutePath.parse(path);
|
|
@@ -3911,11 +3962,7 @@ var defaultTemplatesDir = () => TEMPLATES_DIR;
|
|
|
3911
3962
|
var isNodeErrnoCode2 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
3912
3963
|
|
|
3913
3964
|
// src/integration/ai/readiness/claude/probe.ts
|
|
3914
|
-
import {
|
|
3915
|
-
import { basename, join as join7 } from "path";
|
|
3916
|
-
|
|
3917
|
-
// src/domain/value/kebab-case.ts
|
|
3918
|
-
var toKebabCase = (input) => input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
3965
|
+
import { join as join8 } from "path";
|
|
3919
3966
|
|
|
3920
3967
|
// src/domain/value/error/probe-error.ts
|
|
3921
3968
|
var ProbeError = class extends Error {
|
|
@@ -3936,66 +3983,22 @@ var ProbeError = class extends Error {
|
|
|
3936
3983
|
}
|
|
3937
3984
|
};
|
|
3938
3985
|
|
|
3939
|
-
// src/integration/ai/readiness/_engine/
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
var presentState = (evaluatedAt, artifacts) => ({
|
|
3943
|
-
kind: "present",
|
|
3944
|
-
evaluatedAt,
|
|
3945
|
-
artifacts
|
|
3946
|
-
});
|
|
3986
|
+
// src/integration/ai/readiness/_engine/probe-fs.ts
|
|
3987
|
+
import { promises as fs8 } from "fs";
|
|
3988
|
+
import { basename, join as join7 } from "path";
|
|
3947
3989
|
|
|
3948
|
-
// src/
|
|
3949
|
-
var
|
|
3950
|
-
var hasAnyClaudeArtifact = (a) => a.claudeMd !== void 0 || a.agentsMd !== void 0 || a.settings !== void 0 || a.settingsLocal !== void 0 || a.mcpConfig !== void 0 || a.skills.length > 0 || a.commands.length > 0 || a.agents.length > 0 || a.hooks.length > 0;
|
|
3951
|
-
var hasAnyCopilotArtifact = (a) => a.copilotInstructions !== void 0;
|
|
3990
|
+
// src/domain/value/kebab-case.ts
|
|
3991
|
+
var toKebabCase = (input) => input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
3952
3992
|
|
|
3953
|
-
// src/integration/ai/readiness/
|
|
3954
|
-
var claudeProbe = {
|
|
3955
|
-
tool: "claude-code",
|
|
3956
|
-
async evaluate(repository, now) {
|
|
3957
|
-
const root = repository.path;
|
|
3958
|
-
const claudeMd = await probeFile(join7(root, "CLAUDE.md"));
|
|
3959
|
-
if (!claudeMd.ok) return Result.error(claudeMd.error);
|
|
3960
|
-
const agentsMd = await probeFile(join7(root, "AGENTS.md"));
|
|
3961
|
-
if (!agentsMd.ok) return Result.error(agentsMd.error);
|
|
3962
|
-
const settings = await probeFile(join7(root, ".claude/settings.json"));
|
|
3963
|
-
if (!settings.ok) return Result.error(settings.error);
|
|
3964
|
-
const settingsLocal = await probeFile(join7(root, ".claude/settings.local.json"));
|
|
3965
|
-
if (!settingsLocal.ok) return Result.error(settingsLocal.error);
|
|
3966
|
-
const mcpConfig = await probeFile(join7(root, ".mcp.json"));
|
|
3967
|
-
if (!mcpConfig.ok) return Result.error(mcpConfig.error);
|
|
3968
|
-
const skills = await probeNamedDirCollection(join7(root, ".claude/skills"), "SKILL.md");
|
|
3969
|
-
if (!skills.ok) return Result.error(skills.error);
|
|
3970
|
-
const commands = await probeNamedFileCollection(join7(root, ".claude/commands"));
|
|
3971
|
-
if (!commands.ok) return Result.error(commands.error);
|
|
3972
|
-
const agents = await probeNamedFileCollection(join7(root, ".claude/agents"));
|
|
3973
|
-
if (!agents.ok) return Result.error(agents.error);
|
|
3974
|
-
const hooks = await readHooks([settings.value, settingsLocal.value]);
|
|
3975
|
-
if (!hooks.ok) return Result.error(hooks.error);
|
|
3976
|
-
const artifacts = {
|
|
3977
|
-
tool: "claude-code",
|
|
3978
|
-
...claudeMd.value !== void 0 ? { claudeMd: claudeMd.value } : {},
|
|
3979
|
-
...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
|
|
3980
|
-
...settings.value !== void 0 ? { settings: settings.value } : {},
|
|
3981
|
-
...settingsLocal.value !== void 0 ? { settingsLocal: settingsLocal.value } : {},
|
|
3982
|
-
...mcpConfig.value !== void 0 ? { mcpConfig: mcpConfig.value } : {},
|
|
3983
|
-
skills: skills.value,
|
|
3984
|
-
commands: commands.value,
|
|
3985
|
-
agents: agents.value,
|
|
3986
|
-
hooks: hooks.value
|
|
3987
|
-
};
|
|
3988
|
-
return Result.ok(hasAnyClaudeArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
|
|
3989
|
-
}
|
|
3990
|
-
};
|
|
3993
|
+
// src/integration/ai/readiness/_engine/probe-fs.ts
|
|
3991
3994
|
var probeFile = async (path) => {
|
|
3992
3995
|
try {
|
|
3993
3996
|
const stat = await fs8.stat(path);
|
|
3994
3997
|
if (!stat.isFile()) return Result.ok(void 0);
|
|
3995
3998
|
return Result.ok({ path });
|
|
3996
3999
|
} catch (cause) {
|
|
3997
|
-
if (
|
|
3998
|
-
if (
|
|
4000
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
|
|
4001
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
3999
4002
|
return Result.error(
|
|
4000
4003
|
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4001
4004
|
);
|
|
@@ -4003,23 +4006,6 @@ var probeFile = async (path) => {
|
|
|
4003
4006
|
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to stat ${path}`, path, cause }));
|
|
4004
4007
|
}
|
|
4005
4008
|
};
|
|
4006
|
-
var probeNamedFileCollection = async (dir) => {
|
|
4007
|
-
const entries = await listDir2(dir);
|
|
4008
|
-
if (!entries.ok) return Result.error(entries.error);
|
|
4009
|
-
const refs = [];
|
|
4010
|
-
for (const entry of entries.value) {
|
|
4011
|
-
if (!entry.endsWith(".md")) continue;
|
|
4012
|
-
const full = join7(dir, entry);
|
|
4013
|
-
const stat = await statSafely(full);
|
|
4014
|
-
if (!stat.ok) return Result.error(stat.error);
|
|
4015
|
-
if (stat.value === void 0 || !stat.value.isFile()) continue;
|
|
4016
|
-
const baseName = entry.slice(0, -".md".length);
|
|
4017
|
-
const slug = Slug.parse(toKebabCase(baseName));
|
|
4018
|
-
if (!slug.ok) continue;
|
|
4019
|
-
refs.push({ name: slug.value, path: full });
|
|
4020
|
-
}
|
|
4021
|
-
return Result.ok(refs);
|
|
4022
|
-
};
|
|
4023
4009
|
var probeNamedDirCollection = async (dir, childMarker) => {
|
|
4024
4010
|
const entries = await listDir2(dir);
|
|
4025
4011
|
if (!entries.ok) return Result.error(entries.error);
|
|
@@ -4039,12 +4025,29 @@ var probeNamedDirCollection = async (dir, childMarker) => {
|
|
|
4039
4025
|
}
|
|
4040
4026
|
return Result.ok(refs);
|
|
4041
4027
|
};
|
|
4028
|
+
var probeNamedFileCollection = async (dir) => {
|
|
4029
|
+
const entries = await listDir2(dir);
|
|
4030
|
+
if (!entries.ok) return Result.error(entries.error);
|
|
4031
|
+
const refs = [];
|
|
4032
|
+
for (const entry of entries.value) {
|
|
4033
|
+
if (!entry.endsWith(".md")) continue;
|
|
4034
|
+
const full = join7(dir, entry);
|
|
4035
|
+
const stat = await statSafely(full);
|
|
4036
|
+
if (!stat.ok) return Result.error(stat.error);
|
|
4037
|
+
if (stat.value === void 0 || !stat.value.isFile()) continue;
|
|
4038
|
+
const baseName = entry.slice(0, -".md".length);
|
|
4039
|
+
const slug = Slug.parse(toKebabCase(baseName));
|
|
4040
|
+
if (!slug.ok) continue;
|
|
4041
|
+
refs.push({ name: slug.value, path: full });
|
|
4042
|
+
}
|
|
4043
|
+
return Result.ok(refs);
|
|
4044
|
+
};
|
|
4042
4045
|
var listDir2 = async (dir) => {
|
|
4043
4046
|
try {
|
|
4044
4047
|
return Result.ok(await fs8.readdir(dir));
|
|
4045
4048
|
} catch (cause) {
|
|
4046
|
-
if (
|
|
4047
|
-
if (
|
|
4049
|
+
if (isNodeErrnoCode(cause, "ENOENT") || isNodeErrnoCode(cause, "ENOTDIR")) return Result.ok([]);
|
|
4050
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4048
4051
|
return Result.error(
|
|
4049
4052
|
new ProbeError({ subCode: "fs-permission", message: `permission denied listing ${dir}`, path: dir, cause })
|
|
4050
4053
|
);
|
|
@@ -4056,8 +4059,8 @@ var statSafely = async (path) => {
|
|
|
4056
4059
|
try {
|
|
4057
4060
|
return Result.ok(await fs8.stat(path));
|
|
4058
4061
|
} catch (cause) {
|
|
4059
|
-
if (
|
|
4060
|
-
if (
|
|
4062
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
|
|
4063
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4061
4064
|
return Result.error(
|
|
4062
4065
|
new ProbeError({ subCode: "fs-permission", message: `permission denied stat ${path}`, path, cause })
|
|
4063
4066
|
);
|
|
@@ -4065,6 +4068,73 @@ var statSafely = async (path) => {
|
|
|
4065
4068
|
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to stat ${path}`, path, cause }));
|
|
4066
4069
|
}
|
|
4067
4070
|
};
|
|
4071
|
+
var readFileSafely = async (path) => {
|
|
4072
|
+
try {
|
|
4073
|
+
return Result.ok(await fs8.readFile(path, "utf8"));
|
|
4074
|
+
} catch (cause) {
|
|
4075
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
|
|
4076
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4077
|
+
return Result.error(
|
|
4078
|
+
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4079
|
+
);
|
|
4080
|
+
}
|
|
4081
|
+
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to read ${path}`, path, cause }));
|
|
4082
|
+
}
|
|
4083
|
+
};
|
|
4084
|
+
|
|
4085
|
+
// src/integration/ai/readiness/_engine/state.ts
|
|
4086
|
+
var unknownState = { kind: "unknown" };
|
|
4087
|
+
var absentState = (evaluatedAt) => ({ kind: "absent", evaluatedAt });
|
|
4088
|
+
var presentState = (evaluatedAt, artifacts) => ({
|
|
4089
|
+
kind: "present",
|
|
4090
|
+
evaluatedAt,
|
|
4091
|
+
artifacts
|
|
4092
|
+
});
|
|
4093
|
+
|
|
4094
|
+
// src/integration/ai/readiness/_engine/predicates.ts
|
|
4095
|
+
var isPresent = (state) => state.kind === "present";
|
|
4096
|
+
var hasAnyClaudeArtifact = (a) => a.claudeMd !== void 0 || a.agentsMd !== void 0 || a.settings !== void 0 || a.settingsLocal !== void 0 || a.mcpConfig !== void 0 || a.skills.length > 0 || a.commands.length > 0 || a.agents.length > 0 || a.hooks.length > 0;
|
|
4097
|
+
var hasAnyCopilotArtifact = (a) => a.copilotInstructions !== void 0;
|
|
4098
|
+
var hasAnyCodexArtifact = (a) => a.agentsMd !== void 0 || a.skills.length > 0;
|
|
4099
|
+
|
|
4100
|
+
// src/integration/ai/readiness/claude/probe.ts
|
|
4101
|
+
var claudeProbe = {
|
|
4102
|
+
tool: "claude-code",
|
|
4103
|
+
async evaluate(repository, now) {
|
|
4104
|
+
const root = repository.path;
|
|
4105
|
+
const claudeMd = await probeFile(join8(root, "CLAUDE.md"));
|
|
4106
|
+
if (!claudeMd.ok) return Result.error(claudeMd.error);
|
|
4107
|
+
const agentsMd = await probeFile(join8(root, "AGENTS.md"));
|
|
4108
|
+
if (!agentsMd.ok) return Result.error(agentsMd.error);
|
|
4109
|
+
const settings = await probeFile(join8(root, ".claude/settings.json"));
|
|
4110
|
+
if (!settings.ok) return Result.error(settings.error);
|
|
4111
|
+
const settingsLocal = await probeFile(join8(root, ".claude/settings.local.json"));
|
|
4112
|
+
if (!settingsLocal.ok) return Result.error(settingsLocal.error);
|
|
4113
|
+
const mcpConfig = await probeFile(join8(root, ".mcp.json"));
|
|
4114
|
+
if (!mcpConfig.ok) return Result.error(mcpConfig.error);
|
|
4115
|
+
const skills = await probeNamedDirCollection(join8(root, ".claude/skills"), "SKILL.md");
|
|
4116
|
+
if (!skills.ok) return Result.error(skills.error);
|
|
4117
|
+
const commands = await probeNamedFileCollection(join8(root, ".claude/commands"));
|
|
4118
|
+
if (!commands.ok) return Result.error(commands.error);
|
|
4119
|
+
const agents = await probeNamedFileCollection(join8(root, ".claude/agents"));
|
|
4120
|
+
if (!agents.ok) return Result.error(agents.error);
|
|
4121
|
+
const hooks = await readHooks([settings.value, settingsLocal.value]);
|
|
4122
|
+
if (!hooks.ok) return Result.error(hooks.error);
|
|
4123
|
+
const artifacts = {
|
|
4124
|
+
tool: "claude-code",
|
|
4125
|
+
...claudeMd.value !== void 0 ? { claudeMd: claudeMd.value } : {},
|
|
4126
|
+
...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
|
|
4127
|
+
...settings.value !== void 0 ? { settings: settings.value } : {},
|
|
4128
|
+
...settingsLocal.value !== void 0 ? { settingsLocal: settingsLocal.value } : {},
|
|
4129
|
+
...mcpConfig.value !== void 0 ? { mcpConfig: mcpConfig.value } : {},
|
|
4130
|
+
skills: skills.value,
|
|
4131
|
+
commands: commands.value,
|
|
4132
|
+
agents: agents.value,
|
|
4133
|
+
hooks: hooks.value
|
|
4134
|
+
};
|
|
4135
|
+
return Result.ok(hasAnyClaudeArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
|
|
4136
|
+
}
|
|
4137
|
+
};
|
|
4068
4138
|
var readHooks = async (settingsRefs) => {
|
|
4069
4139
|
const hooks = [];
|
|
4070
4140
|
for (const ref2 of settingsRefs) {
|
|
@@ -4084,19 +4154,6 @@ var readHooks = async (settingsRefs) => {
|
|
|
4084
4154
|
}
|
|
4085
4155
|
return Result.ok(hooks);
|
|
4086
4156
|
};
|
|
4087
|
-
var readFileSafely = async (path) => {
|
|
4088
|
-
try {
|
|
4089
|
-
return Result.ok(await fs8.readFile(path, "utf8"));
|
|
4090
|
-
} catch (cause) {
|
|
4091
|
-
if (isNodeErrnoCode3(cause, "ENOENT")) return Result.ok(void 0);
|
|
4092
|
-
if (isNodeErrnoCode3(cause, "EACCES")) {
|
|
4093
|
-
return Result.error(
|
|
4094
|
-
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4095
|
-
);
|
|
4096
|
-
}
|
|
4097
|
-
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to read ${path}`, path, cause }));
|
|
4098
|
-
}
|
|
4099
|
-
};
|
|
4100
4157
|
var extractHooks = (settings, sink) => {
|
|
4101
4158
|
if (typeof settings !== "object" || settings === null) return;
|
|
4102
4159
|
const hooks = settings.hooks;
|
|
@@ -4117,33 +4174,41 @@ var extractHooks = (settings, sink) => {
|
|
|
4117
4174
|
}
|
|
4118
4175
|
}
|
|
4119
4176
|
};
|
|
4120
|
-
var isNodeErrnoCode3 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
4121
4177
|
|
|
4122
4178
|
// src/integration/ai/readiness/codex/probe.ts
|
|
4179
|
+
import { join as join9 } from "path";
|
|
4123
4180
|
var codexProbe = {
|
|
4124
4181
|
tool: "codex",
|
|
4125
|
-
evaluate(repository, now) {
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
return
|
|
4182
|
+
async evaluate(repository, now) {
|
|
4183
|
+
const root = repository.path;
|
|
4184
|
+
const agentsMd = await probeFile(join9(root, "AGENTS.md"));
|
|
4185
|
+
if (!agentsMd.ok) return Result.error(agentsMd.error);
|
|
4186
|
+
const skills = await probeNamedDirCollection(join9(root, ".agents/skills"), "SKILL.md");
|
|
4187
|
+
if (!skills.ok) return Result.error(skills.error);
|
|
4188
|
+
const artifacts = {
|
|
4189
|
+
tool: "codex",
|
|
4190
|
+
...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
|
|
4191
|
+
skills: skills.value
|
|
4192
|
+
};
|
|
4193
|
+
return Result.ok(hasAnyCodexArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
|
|
4129
4194
|
}
|
|
4130
4195
|
};
|
|
4131
4196
|
|
|
4132
4197
|
// src/integration/ai/readiness/copilot/probe.ts
|
|
4133
4198
|
import { promises as fs9 } from "fs";
|
|
4134
|
-
import { join as
|
|
4199
|
+
import { join as join10 } from "path";
|
|
4135
4200
|
var copilotProbe = {
|
|
4136
4201
|
tool: "copilot",
|
|
4137
4202
|
async evaluate(repository, now) {
|
|
4138
|
-
const path =
|
|
4203
|
+
const path = join10(repository.path, ".github/copilot-instructions.md");
|
|
4139
4204
|
try {
|
|
4140
4205
|
const stat = await fs9.stat(path);
|
|
4141
4206
|
if (!stat.isFile()) return Result.ok(absentState(now));
|
|
4142
4207
|
const artifacts = { tool: "copilot", copilotInstructions: { path } };
|
|
4143
4208
|
return Result.ok(hasAnyCopilotArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
|
|
4144
4209
|
} catch (cause) {
|
|
4145
|
-
if (
|
|
4146
|
-
if (
|
|
4210
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(absentState(now));
|
|
4211
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4147
4212
|
return Result.error(
|
|
4148
4213
|
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4149
4214
|
);
|
|
@@ -4152,7 +4217,6 @@ var copilotProbe = {
|
|
|
4152
4217
|
}
|
|
4153
4218
|
}
|
|
4154
4219
|
};
|
|
4155
|
-
var isNodeErrnoCode4 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
4156
4220
|
|
|
4157
4221
|
// src/integration/observability/in-memory-event-bus.ts
|
|
4158
4222
|
var createInMemoryEventBus = () => {
|
|
@@ -4200,7 +4264,7 @@ var loggerForScope = (deps, scope) => {
|
|
|
4200
4264
|
};
|
|
4201
4265
|
|
|
4202
4266
|
// src/integration/version/npm-version-checker.ts
|
|
4203
|
-
import { join as
|
|
4267
|
+
import { join as join11 } from "path";
|
|
4204
4268
|
import { z as z15 } from "zod";
|
|
4205
4269
|
|
|
4206
4270
|
// src/business/version/version-check.ts
|
|
@@ -4242,7 +4306,7 @@ var buildVersionCheck = (current, latest, now) => ({
|
|
|
4242
4306
|
var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
4243
4307
|
var DEFAULT_FETCH_TIMEOUT_MS = 3e3;
|
|
4244
4308
|
var RegistryPayloadSchema = z15.object({ version: z15.string() });
|
|
4245
|
-
var cachePath = (stateRoot) =>
|
|
4309
|
+
var cachePath = (stateRoot) => join11(String(stateRoot), "version-check.json");
|
|
4246
4310
|
var registryUrl = (packageName) => `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
4247
4311
|
var shouldSkip = (env) => env["NO_NETWORK"] !== void 0 || env["VITEST"] !== void 0;
|
|
4248
4312
|
var readCache = async (path) => {
|
|
@@ -4296,7 +4360,7 @@ var createNpmVersionChecker = (deps) => {
|
|
|
4296
4360
|
// package.json
|
|
4297
4361
|
var package_default = {
|
|
4298
4362
|
name: "ralphctl",
|
|
4299
|
-
version: "0.7.
|
|
4363
|
+
version: "0.7.2",
|
|
4300
4364
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
4301
4365
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
4302
4366
|
type: "module",
|
|
@@ -4398,11 +4462,11 @@ var CLI_METADATA = {
|
|
|
4398
4462
|
// src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
|
|
4399
4463
|
import { existsSync } from "fs";
|
|
4400
4464
|
import { mkdir, rm, rmdir, writeFile } from "fs/promises";
|
|
4401
|
-
import { join as
|
|
4465
|
+
import { join as join13 } from "path";
|
|
4402
4466
|
|
|
4403
4467
|
// src/integration/io/git-exclude.ts
|
|
4404
4468
|
import { promises as fs10 } from "fs";
|
|
4405
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4469
|
+
import { isAbsolute as isAbsolute2, join as join12 } from "path";
|
|
4406
4470
|
var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
|
|
4407
4471
|
const resolved = await resolveExcludePath(String(repoRoot));
|
|
4408
4472
|
if (resolved === void 0) return Result.ok(void 0);
|
|
@@ -4410,7 +4474,7 @@ var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
|
|
|
4410
4474
|
try {
|
|
4411
4475
|
existing = await fs10.readFile(resolved, "utf8");
|
|
4412
4476
|
} catch (cause) {
|
|
4413
|
-
if (
|
|
4477
|
+
if (isNodeErrnoCode3(cause, "ENOENT")) {
|
|
4414
4478
|
} else {
|
|
4415
4479
|
return Result.error(
|
|
4416
4480
|
new StorageError({
|
|
@@ -4431,16 +4495,16 @@ var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
|
|
|
4431
4495
|
return writeTextAtomic(resolved, next);
|
|
4432
4496
|
};
|
|
4433
4497
|
var resolveExcludePath = async (repoRoot) => {
|
|
4434
|
-
const gitMarker =
|
|
4498
|
+
const gitMarker = join12(repoRoot, ".git");
|
|
4435
4499
|
let stat;
|
|
4436
4500
|
try {
|
|
4437
4501
|
stat = await fs10.stat(gitMarker);
|
|
4438
4502
|
} catch (cause) {
|
|
4439
|
-
if (
|
|
4503
|
+
if (isNodeErrnoCode3(cause, "ENOENT") || isNodeErrnoCode3(cause, "ENOTDIR")) return void 0;
|
|
4440
4504
|
throw cause;
|
|
4441
4505
|
}
|
|
4442
4506
|
if (stat.isDirectory()) {
|
|
4443
|
-
return
|
|
4507
|
+
return join12(gitMarker, "info", "exclude");
|
|
4444
4508
|
}
|
|
4445
4509
|
if (!stat.isFile()) return void 0;
|
|
4446
4510
|
let pointer;
|
|
@@ -4452,10 +4516,10 @@ var resolveExcludePath = async (repoRoot) => {
|
|
|
4452
4516
|
const match = /^gitdir:\s*(.+)\s*$/m.exec(pointer);
|
|
4453
4517
|
if (match === null) return void 0;
|
|
4454
4518
|
const gitdir = match[1].trim();
|
|
4455
|
-
const absoluteGitdir = isAbsolute2(gitdir) ? gitdir :
|
|
4456
|
-
return
|
|
4519
|
+
const absoluteGitdir = isAbsolute2(gitdir) ? gitdir : join12(repoRoot, gitdir);
|
|
4520
|
+
return join12(absoluteGitdir, "info", "exclude");
|
|
4457
4521
|
};
|
|
4458
|
-
var
|
|
4522
|
+
var isNodeErrnoCode3 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
4459
4523
|
|
|
4460
4524
|
// src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
|
|
4461
4525
|
var renderSkill = (skill) => {
|
|
@@ -4478,7 +4542,7 @@ var tryRmdirIfEmpty = async (path) => {
|
|
|
4478
4542
|
var createFilesystemSkillsAdapter = (deps) => {
|
|
4479
4543
|
const installed = /* @__PURE__ */ new Map();
|
|
4480
4544
|
const excludeAttempted = /* @__PURE__ */ new Set();
|
|
4481
|
-
const skillsSubdir =
|
|
4545
|
+
const skillsSubdir = join13(deps.parentDir, "skills");
|
|
4482
4546
|
const excludePattern = `${skillsSubdir}/ralphctl-*`;
|
|
4483
4547
|
const pruneStale = () => {
|
|
4484
4548
|
for (const key of [...installed.keys()]) {
|
|
@@ -4488,14 +4552,14 @@ var createFilesystemSkillsAdapter = (deps) => {
|
|
|
4488
4552
|
return {
|
|
4489
4553
|
async install(sessionDir, skills) {
|
|
4490
4554
|
pruneStale();
|
|
4491
|
-
const skillsDir =
|
|
4555
|
+
const skillsDir = join13(String(sessionDir), skillsSubdir);
|
|
4492
4556
|
const tracked = installed.get(String(sessionDir)) ?? /* @__PURE__ */ new Set();
|
|
4493
4557
|
for (const skill of skills) {
|
|
4494
|
-
const dst =
|
|
4558
|
+
const dst = join13(skillsDir, skill.name);
|
|
4495
4559
|
if (existsSync(dst)) continue;
|
|
4496
4560
|
try {
|
|
4497
4561
|
await mkdir(dst, { recursive: true });
|
|
4498
|
-
await writeFile(
|
|
4562
|
+
await writeFile(join13(dst, "SKILL.md"), renderSkill(skill), "utf-8");
|
|
4499
4563
|
tracked.add(skill.name);
|
|
4500
4564
|
} catch (cause) {
|
|
4501
4565
|
if (tracked.size > 0) installed.set(String(sessionDir), tracked);
|
|
@@ -4526,10 +4590,10 @@ var createFilesystemSkillsAdapter = (deps) => {
|
|
|
4526
4590
|
const key = String(sessionDir);
|
|
4527
4591
|
const tracked = installed.get(key);
|
|
4528
4592
|
if (tracked === void 0 || tracked.size === 0) return Result.ok(void 0);
|
|
4529
|
-
const skillsDir =
|
|
4593
|
+
const skillsDir = join13(key, skillsSubdir);
|
|
4530
4594
|
try {
|
|
4531
4595
|
for (const id of tracked) {
|
|
4532
|
-
await rm(
|
|
4596
|
+
await rm(join13(skillsDir, id), { recursive: true, force: true });
|
|
4533
4597
|
}
|
|
4534
4598
|
installed.delete(key);
|
|
4535
4599
|
} catch (cause) {
|
|
@@ -4543,7 +4607,7 @@ var createFilesystemSkillsAdapter = (deps) => {
|
|
|
4543
4607
|
);
|
|
4544
4608
|
}
|
|
4545
4609
|
await tryRmdirIfEmpty(skillsDir);
|
|
4546
|
-
await tryRmdirIfEmpty(
|
|
4610
|
+
await tryRmdirIfEmpty(join13(key, deps.parentDir));
|
|
4547
4611
|
return Result.ok(void 0);
|
|
4548
4612
|
}
|
|
4549
4613
|
};
|
|
@@ -4605,7 +4669,7 @@ var createSkillsAdapter = (deps) => {
|
|
|
4605
4669
|
};
|
|
4606
4670
|
|
|
4607
4671
|
// src/integration/ai/skills/bundled/source.ts
|
|
4608
|
-
import { dirname as
|
|
4672
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
4609
4673
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4610
4674
|
import { readFile } from "fs/promises";
|
|
4611
4675
|
|
|
@@ -4632,9 +4696,9 @@ var skillsForFlow = (flowId) => FLOW_SKILLS[flowId];
|
|
|
4632
4696
|
|
|
4633
4697
|
// src/integration/ai/skills/bundled/source.ts
|
|
4634
4698
|
var defaultBundledRoot = (() => {
|
|
4635
|
-
const here =
|
|
4699
|
+
const here = dirname7(fileURLToPath2(import.meta.url));
|
|
4636
4700
|
const isBundled = import.meta.url.endsWith("/cli.mjs") || import.meta.url.endsWith("\\cli.mjs");
|
|
4637
|
-
return isBundled ?
|
|
4701
|
+
return isBundled ? join14(here, "skills") : here;
|
|
4638
4702
|
})();
|
|
4639
4703
|
var splitFrontmatter = (raw) => {
|
|
4640
4704
|
const trimmed = raw.replace(/^\uFEFF/u, "");
|
|
@@ -4662,7 +4726,7 @@ var parseSimpleYaml = (input) => {
|
|
|
4662
4726
|
return result;
|
|
4663
4727
|
};
|
|
4664
4728
|
var readSkill = async (root, name) => {
|
|
4665
|
-
const path =
|
|
4729
|
+
const path = join14(root, name, "SKILL.md");
|
|
4666
4730
|
let raw;
|
|
4667
4731
|
try {
|
|
4668
4732
|
raw = await readFile(path, "utf-8");
|
|
@@ -5652,13 +5716,13 @@ var probeGitConfig = async (id, label, key, runCommand2, hint) => {
|
|
|
5652
5716
|
}
|
|
5653
5717
|
return { id, label, status: "warn", detail: `${key} not set`, hint, group: "vcs" };
|
|
5654
5718
|
};
|
|
5655
|
-
var probeCliAuth = async (id, label, binary, args, hint, runCommand2) => {
|
|
5719
|
+
var probeCliAuth = async (id, label, binary, args, hint, runCommand2, group = "vcs") => {
|
|
5656
5720
|
const r = await runCommand2(binary, args);
|
|
5657
5721
|
if (r.ok) {
|
|
5658
|
-
return { id, label, status: "pass", detail: "authenticated", group
|
|
5722
|
+
return { id, label, status: "pass", detail: "authenticated", group };
|
|
5659
5723
|
}
|
|
5660
5724
|
const detail = r.stderr.split("\n").find((line) => line.trim().length > 0)?.trim() ?? "not authenticated";
|
|
5661
|
-
return { id, label, status: "warn", detail, hint, group
|
|
5725
|
+
return { id, label, status: "warn", detail, hint, group };
|
|
5662
5726
|
};
|
|
5663
5727
|
var createDoctorFlow = (deps) => leaf("doctor", {
|
|
5664
5728
|
useCase: {
|
|
@@ -5770,6 +5834,7 @@ var createDoctorFlow = (deps) => leaf("doctor", {
|
|
|
5770
5834
|
}
|
|
5771
5835
|
const settings = await deps.settingsRepo.load();
|
|
5772
5836
|
const configuredProvider = settings.ok ? settings.value.ai.provider : void 0;
|
|
5837
|
+
let codexInstalled = false;
|
|
5773
5838
|
for (const provider of Object.keys(PROVIDER_BINARY)) {
|
|
5774
5839
|
const binary = PROVIDER_BINARY[provider];
|
|
5775
5840
|
const isConfigured = provider === configuredProvider;
|
|
@@ -5781,12 +5846,26 @@ var createDoctorFlow = (deps) => leaf("doctor", {
|
|
|
5781
5846
|
deps.commandExists,
|
|
5782
5847
|
`install the '${binary}' CLI and ensure it is on your PATH`
|
|
5783
5848
|
);
|
|
5849
|
+
if (provider === "openai-codex") codexInstalled = probe.status === "pass";
|
|
5784
5850
|
if (probe.status === "fail") {
|
|
5785
5851
|
probes.push({ ...probe, status: "warn" });
|
|
5786
5852
|
} else {
|
|
5787
5853
|
probes.push(probe);
|
|
5788
5854
|
}
|
|
5789
5855
|
}
|
|
5856
|
+
if (configuredProvider === "openai-codex" && codexInstalled) {
|
|
5857
|
+
probes.push(
|
|
5858
|
+
await probeCliAuth(
|
|
5859
|
+
"codex-auth",
|
|
5860
|
+
"OpenAI Codex CLI authenticated",
|
|
5861
|
+
"codex",
|
|
5862
|
+
["login", "status"],
|
|
5863
|
+
"run `codex login` to sign in",
|
|
5864
|
+
deps.runCommand,
|
|
5865
|
+
"ai"
|
|
5866
|
+
)
|
|
5867
|
+
);
|
|
5868
|
+
}
|
|
5790
5869
|
const projects = await deps.projectRepo.list();
|
|
5791
5870
|
probes.push({
|
|
5792
5871
|
id: "projects-list",
|
|
@@ -9129,14 +9208,14 @@ var launchAddTickets = (ctx) => {
|
|
|
9129
9208
|
};
|
|
9130
9209
|
|
|
9131
9210
|
// src/application/ui/shared/launch/refine.ts
|
|
9132
|
-
import { join as
|
|
9211
|
+
import { join as join17 } from "path";
|
|
9133
9212
|
|
|
9134
9213
|
// src/application/flows/refine/flow.ts
|
|
9135
|
-
import { join as
|
|
9214
|
+
import { join as join16 } from "path";
|
|
9136
9215
|
|
|
9137
9216
|
// src/application/flows/_shared/build-unit.ts
|
|
9138
9217
|
import { promises as fs11 } from "fs";
|
|
9139
|
-
import { join as
|
|
9218
|
+
import { join as join15 } from "path";
|
|
9140
9219
|
var buildUnitLeaf = (opts) => leaf(opts.name, {
|
|
9141
9220
|
useCase: {
|
|
9142
9221
|
execute: async (input) => {
|
|
@@ -9157,7 +9236,7 @@ var buildUnitLeaf = (opts) => leaf(opts.name, {
|
|
|
9157
9236
|
return Result.ok(parsed.value);
|
|
9158
9237
|
}
|
|
9159
9238
|
},
|
|
9160
|
-
input: (ctx) => ({ path:
|
|
9239
|
+
input: (ctx) => ({ path: join15(String(opts.parent(ctx)), opts.slug(ctx)) }),
|
|
9161
9240
|
output: (ctx, root) => opts.write(ctx, root)
|
|
9162
9241
|
});
|
|
9163
9242
|
|
|
@@ -9350,7 +9429,7 @@ var refineTicketInteractiveLeaf = (deps, ticket) => leaf(`refine-ticket-${String
|
|
|
9350
9429
|
execute: async (input) => {
|
|
9351
9430
|
const session = await deps.runInTerminal(
|
|
9352
9431
|
async () => deps.interactiveAi.run({
|
|
9353
|
-
cwd:
|
|
9432
|
+
cwd: input.cwd,
|
|
9354
9433
|
promptFile: input.promptFile,
|
|
9355
9434
|
outputFile: input.outputFile,
|
|
9356
9435
|
model: deps.model
|
|
@@ -9407,9 +9486,18 @@ var refineTicketInteractiveLeaf = (deps, ticket) => leaf(`refine-ticket-${String
|
|
|
9407
9486
|
message: `refine-ticket-${String(ticket.id)}: prompt/output paths missing \u2014 render-prompt-to-file must run first`
|
|
9408
9487
|
});
|
|
9409
9488
|
}
|
|
9489
|
+
if (ctx.currentUnitRoot === void 0) {
|
|
9490
|
+
throw new InvalidStateError({
|
|
9491
|
+
entity: "chain",
|
|
9492
|
+
currentState: "pre-refine",
|
|
9493
|
+
attemptedAction: `refine-ticket-${String(ticket.id)}`,
|
|
9494
|
+
message: `refine-ticket-${String(ticket.id)}: unit root missing \u2014 build-refine-unit must run first`
|
|
9495
|
+
});
|
|
9496
|
+
}
|
|
9410
9497
|
return {
|
|
9411
9498
|
sprint: ctx.sprint,
|
|
9412
9499
|
ticket,
|
|
9500
|
+
cwd: ctx.currentUnitRoot,
|
|
9413
9501
|
promptFile: ctx.currentPromptFile,
|
|
9414
9502
|
outputFile: ctx.currentOutputFile
|
|
9415
9503
|
};
|
|
@@ -9610,8 +9698,8 @@ var createRefineFlow = (deps, opts) => {
|
|
|
9610
9698
|
parent: () => opts.refinementRoot,
|
|
9611
9699
|
slug: () => ticketSlug(ticket),
|
|
9612
9700
|
write: (ctx, root) => {
|
|
9613
|
-
const promptPath = AbsolutePath.parse(
|
|
9614
|
-
const outputPath = AbsolutePath.parse(
|
|
9701
|
+
const promptPath = AbsolutePath.parse(join16(String(root), "prompt.md"));
|
|
9702
|
+
const outputPath = AbsolutePath.parse(join16(String(root), "requirements.md"));
|
|
9615
9703
|
if (!promptPath.ok || !outputPath.ok) {
|
|
9616
9704
|
throw promptPath.ok ? outputPath.ok ? new Error("unreachable") : outputPath.error : promptPath.error;
|
|
9617
9705
|
}
|
|
@@ -9648,9 +9736,17 @@ var createRefineFlow = (deps, opts) => {
|
|
|
9648
9736
|
{
|
|
9649
9737
|
name: `install-skills-${String(ticket.id)}`,
|
|
9650
9738
|
flowId: "refine",
|
|
9651
|
-
// Skills land in the AI session's cwd
|
|
9652
|
-
//
|
|
9653
|
-
|
|
9739
|
+
// Skills land in the AI session's cwd — for refine that's the per-ticket unit root
|
|
9740
|
+
// (`<sprintDir>/refinement/<ticket-slug>/`), not the user's repo. Refinement is
|
|
9741
|
+
// implementation-agnostic, so we keep the AI out of the repo's auto-discovered context.
|
|
9742
|
+
cwdPicker: (ctx) => {
|
|
9743
|
+
if (ctx.currentUnitRoot === void 0) {
|
|
9744
|
+
throw new Error(
|
|
9745
|
+
`install-skills-${String(ticket.id)}: currentUnitRoot missing \u2014 build-refine-unit must run first`
|
|
9746
|
+
);
|
|
9747
|
+
}
|
|
9748
|
+
return ctx.currentUnitRoot;
|
|
9749
|
+
}
|
|
9654
9750
|
}
|
|
9655
9751
|
),
|
|
9656
9752
|
refineTicketInteractiveLeaf(
|
|
@@ -9658,7 +9754,6 @@ var createRefineFlow = (deps, opts) => {
|
|
|
9658
9754
|
interactiveAi: deps.interactiveAi,
|
|
9659
9755
|
runInTerminal: deps.runInTerminal,
|
|
9660
9756
|
logger: deps.logger,
|
|
9661
|
-
cwd: opts.cwd,
|
|
9662
9757
|
model: opts.model,
|
|
9663
9758
|
...deps.reviewBeforeApprove !== void 0 ? { reviewBeforeApprove: deps.reviewBeforeApprove } : {},
|
|
9664
9759
|
...deps.issuePusher !== void 0 ? { issuePusher: deps.issuePusher } : {},
|
|
@@ -9668,7 +9763,17 @@ var createRefineFlow = (deps, opts) => {
|
|
|
9668
9763
|
),
|
|
9669
9764
|
uninstallSkillsLeaf(
|
|
9670
9765
|
{ skillsAdapter: deps.skillsAdapter },
|
|
9671
|
-
{
|
|
9766
|
+
{
|
|
9767
|
+
name: `uninstall-skills-${String(ticket.id)}`,
|
|
9768
|
+
cwdPicker: (ctx) => {
|
|
9769
|
+
if (ctx.currentUnitRoot === void 0) {
|
|
9770
|
+
throw new Error(
|
|
9771
|
+
`uninstall-skills-${String(ticket.id)}: currentUnitRoot missing \u2014 build-refine-unit must run first`
|
|
9772
|
+
);
|
|
9773
|
+
}
|
|
9774
|
+
return ctx.currentUnitRoot;
|
|
9775
|
+
}
|
|
9776
|
+
}
|
|
9672
9777
|
),
|
|
9673
9778
|
saveSprintLeaf({ sprintRepo: deps.sprintRepo }, `save-after-${String(ticket.id)}`)
|
|
9674
9779
|
])
|
|
@@ -9691,12 +9796,11 @@ var deriveOriginFromGit = async (cwd, gitRunner) => {
|
|
|
9691
9796
|
return parsed === null ? void 0 : parsed;
|
|
9692
9797
|
};
|
|
9693
9798
|
var launchRefine = async (ctx) => {
|
|
9694
|
-
const { deps, snapshot, extras, settings, interactiveAi, skillsAdapter, skillSource,
|
|
9799
|
+
const { deps, snapshot, extras, settings, interactiveAi, skillsAdapter, skillSource, bridge, sessionId: sessionId2 } = ctx;
|
|
9695
9800
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
9696
|
-
if (!cwd) return { ok: false, reason: "No repository path resolvable from the project." };
|
|
9697
9801
|
const pending = snapshot.sprint.tickets.filter((t) => t.status === "pending");
|
|
9698
9802
|
const refinementRoot = AbsolutePath.parse(
|
|
9699
|
-
|
|
9803
|
+
join17(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "refinement")
|
|
9700
9804
|
);
|
|
9701
9805
|
if (!refinementRoot.ok) return { ok: false, reason: refinementRoot.error.message };
|
|
9702
9806
|
let defaultIssueOrigin = snapshot.project?.defaultIssueOrigin;
|
|
@@ -9756,7 +9860,6 @@ ${body.trim()}`;
|
|
|
9756
9860
|
{
|
|
9757
9861
|
sprintId: snapshot.sprint.id,
|
|
9758
9862
|
pendingTickets: pending,
|
|
9759
|
-
cwd,
|
|
9760
9863
|
model: extras.modelOverride ?? settings.ai.models.refine,
|
|
9761
9864
|
refinementRoot: refinementRoot.value
|
|
9762
9865
|
}
|
|
@@ -9770,10 +9873,10 @@ ${body.trim()}`;
|
|
|
9770
9873
|
};
|
|
9771
9874
|
|
|
9772
9875
|
// src/application/ui/shared/launch/plan.ts
|
|
9773
|
-
import { join as
|
|
9876
|
+
import { join as join19 } from "path";
|
|
9774
9877
|
|
|
9775
9878
|
// src/application/flows/plan/flow.ts
|
|
9776
|
-
import { join as
|
|
9879
|
+
import { join as join18 } from "path";
|
|
9777
9880
|
|
|
9778
9881
|
// src/application/flows/_shared/sprint/load-execution.ts
|
|
9779
9882
|
var loadSprintExecutionLeaf = (deps, name = "load-sprint-execution") => leaf(name, {
|
|
@@ -10214,6 +10317,13 @@ var markTaskBlocked = (task, reason) => {
|
|
|
10214
10317
|
if (!guard2.ok) return Result.error(guard2.error);
|
|
10215
10318
|
return Result.ok({ ...guard2.value, status: "blocked", blockedReason: reason });
|
|
10216
10319
|
};
|
|
10320
|
+
var unblockTask = (task) => {
|
|
10321
|
+
const guard2 = requireStatus("task", task, ["blocked"], "unblock");
|
|
10322
|
+
if (!guard2.ok) return Result.error(guard2.error);
|
|
10323
|
+
const { blockedReason: _ignored, ...rest } = guard2.value;
|
|
10324
|
+
void _ignored;
|
|
10325
|
+
return Result.ok({ ...rest, status: "todo" });
|
|
10326
|
+
};
|
|
10217
10327
|
var latestCritique = (task) => {
|
|
10218
10328
|
for (let i = task.attempts.length - 1; i >= 0; i--) {
|
|
10219
10329
|
const att = task.attempts[i];
|
|
@@ -10394,7 +10504,7 @@ var callPlannerInteractiveLeaf = (deps) => leaf("call-planner-interactive", {
|
|
|
10394
10504
|
const additionalRoots = deps.additionalRoots ?? [];
|
|
10395
10505
|
const session = await deps.runInTerminal(
|
|
10396
10506
|
async () => deps.interactiveAi.run({
|
|
10397
|
-
cwd:
|
|
10507
|
+
cwd: input.cwd,
|
|
10398
10508
|
promptFile: input.promptFile,
|
|
10399
10509
|
outputFile: input.outputFile,
|
|
10400
10510
|
model: deps.model,
|
|
@@ -10471,10 +10581,19 @@ var callPlannerInteractiveLeaf = (deps) => leaf("call-planner-interactive", {
|
|
|
10471
10581
|
message: "call-planner-interactive: prompt/output paths missing \u2014 render-prompt-to-file must run first"
|
|
10472
10582
|
});
|
|
10473
10583
|
}
|
|
10584
|
+
if (ctx.currentUnitRoot === void 0) {
|
|
10585
|
+
throw new InvalidStateError({
|
|
10586
|
+
entity: "chain",
|
|
10587
|
+
currentState: "pre-plan",
|
|
10588
|
+
attemptedAction: "call-planner-interactive",
|
|
10589
|
+
message: "call-planner-interactive: unit root missing \u2014 build-plan-unit must run first"
|
|
10590
|
+
});
|
|
10591
|
+
}
|
|
10474
10592
|
return {
|
|
10475
10593
|
sprint: ctx.sprint,
|
|
10476
10594
|
project: ctx.project,
|
|
10477
10595
|
existingTasks: ctx.tasks ?? [],
|
|
10596
|
+
cwd: ctx.currentUnitRoot,
|
|
10478
10597
|
promptFile: ctx.currentPromptFile,
|
|
10479
10598
|
outputFile: ctx.currentOutputFile
|
|
10480
10599
|
};
|
|
@@ -10498,8 +10617,8 @@ var createPlanFlow = (deps, opts) => {
|
|
|
10498
10617
|
parent: () => opts.planRoot,
|
|
10499
10618
|
slug: () => slug,
|
|
10500
10619
|
write: (ctx, root) => {
|
|
10501
|
-
const promptPath = AbsolutePath.parse(
|
|
10502
|
-
const outputPath = AbsolutePath.parse(
|
|
10620
|
+
const promptPath = AbsolutePath.parse(join18(String(root), "prompt.md"));
|
|
10621
|
+
const outputPath = AbsolutePath.parse(join18(String(root), "plan.json"));
|
|
10503
10622
|
if (!promptPath.ok) throw promptPath.error;
|
|
10504
10623
|
if (!outputPath.ok) throw outputPath.error;
|
|
10505
10624
|
return {
|
|
@@ -10536,9 +10655,21 @@ var createPlanFlow = (deps, opts) => {
|
|
|
10536
10655
|
{ skillsAdapter: deps.skillsAdapter, skillSource: deps.skillSource },
|
|
10537
10656
|
{
|
|
10538
10657
|
flowId: "plan",
|
|
10539
|
-
// Skills land in the AI session's cwd
|
|
10540
|
-
//
|
|
10541
|
-
|
|
10658
|
+
// Skills land in the AI session's cwd — for plan that's the per-sprint plan unit root
|
|
10659
|
+
// (`<sprintDir>/plan/<run-slug>/`), not any project repo. Plan mounts every repo as an
|
|
10660
|
+
// equal `--add-dir` source; rooting the session in any one repo would auto-load that
|
|
10661
|
+
// repo's `CLAUDE.md` / agents / `.mcp.json` and bias the planner toward it.
|
|
10662
|
+
cwdPicker: (ctx) => {
|
|
10663
|
+
if (ctx.currentUnitRoot === void 0) {
|
|
10664
|
+
throw new InvalidStateError({
|
|
10665
|
+
entity: "chain",
|
|
10666
|
+
currentState: "pre-plan",
|
|
10667
|
+
attemptedAction: "install-skills",
|
|
10668
|
+
message: "install-skills: currentUnitRoot missing \u2014 build-plan-unit must run first"
|
|
10669
|
+
});
|
|
10670
|
+
}
|
|
10671
|
+
return ctx.currentUnitRoot;
|
|
10672
|
+
}
|
|
10542
10673
|
}
|
|
10543
10674
|
),
|
|
10544
10675
|
callPlannerInteractiveLeaf({
|
|
@@ -10546,12 +10677,26 @@ var createPlanFlow = (deps, opts) => {
|
|
|
10546
10677
|
runInTerminal: deps.runInTerminal,
|
|
10547
10678
|
logger: deps.logger,
|
|
10548
10679
|
clock: deps.clock,
|
|
10549
|
-
cwd: opts.cwd,
|
|
10550
10680
|
model: opts.model,
|
|
10551
10681
|
...opts.additionalRoots !== void 0 && opts.additionalRoots.length > 0 ? { additionalRoots: opts.additionalRoots } : {},
|
|
10552
10682
|
...deps.reviewBeforeApprove !== void 0 ? { reviewBeforeApprove: deps.reviewBeforeApprove } : {}
|
|
10553
10683
|
}),
|
|
10554
|
-
uninstallSkillsLeaf(
|
|
10684
|
+
uninstallSkillsLeaf(
|
|
10685
|
+
{ skillsAdapter: deps.skillsAdapter },
|
|
10686
|
+
{
|
|
10687
|
+
cwdPicker: (ctx) => {
|
|
10688
|
+
if (ctx.currentUnitRoot === void 0) {
|
|
10689
|
+
throw new InvalidStateError({
|
|
10690
|
+
entity: "chain",
|
|
10691
|
+
currentState: "pre-plan",
|
|
10692
|
+
attemptedAction: "uninstall-skills",
|
|
10693
|
+
message: "uninstall-skills: currentUnitRoot missing \u2014 build-plan-unit must run first"
|
|
10694
|
+
});
|
|
10695
|
+
}
|
|
10696
|
+
return ctx.currentUnitRoot;
|
|
10697
|
+
}
|
|
10698
|
+
}
|
|
10699
|
+
),
|
|
10555
10700
|
saveTasksLeaf({ taskRepo: deps.taskRepo }),
|
|
10556
10701
|
saveSprintLeaf({ sprintRepo: deps.sprintRepo })
|
|
10557
10702
|
]);
|
|
@@ -10559,12 +10704,11 @@ var createPlanFlow = (deps, opts) => {
|
|
|
10559
10704
|
|
|
10560
10705
|
// src/application/ui/shared/launch/plan.ts
|
|
10561
10706
|
var launchPlan = (ctx) => {
|
|
10562
|
-
const { deps, snapshot, extras, settings, interactiveAi, skillsAdapter, skillSource,
|
|
10707
|
+
const { deps, snapshot, extras, settings, interactiveAi, skillsAdapter, skillSource, bridge, sessionId: sessionId2 } = ctx;
|
|
10563
10708
|
if (!snapshot.project) return { ok: false, reason: "No project loaded." };
|
|
10564
10709
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
10565
|
-
if (!cwd) return { ok: false, reason: "No repository path resolvable from the project." };
|
|
10566
10710
|
const planRoot = AbsolutePath.parse(
|
|
10567
|
-
|
|
10711
|
+
join19(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "plan")
|
|
10568
10712
|
);
|
|
10569
10713
|
if (!planRoot.ok) return { ok: false, reason: planRoot.error.message };
|
|
10570
10714
|
const reviewBeforeApprove = async (proposedTasks) => {
|
|
@@ -10601,9 +10745,9 @@ ${summary}`;
|
|
|
10601
10745
|
{
|
|
10602
10746
|
sprintId: snapshot.sprint.id,
|
|
10603
10747
|
projectId: snapshot.project.id,
|
|
10604
|
-
|
|
10605
|
-
//
|
|
10606
|
-
//
|
|
10748
|
+
// Mount every repo on the project as an equal `--add-dir` source so the planner can
|
|
10749
|
+
// navigate across them without per-file approval prompts. No repo enjoys cwd privilege —
|
|
10750
|
+
// the session's cwd is the per-sprint plan unit root.
|
|
10607
10751
|
additionalRoots: snapshot.project.repositories.map((r) => r.path),
|
|
10608
10752
|
model: extras.modelOverride ?? settings.ai.models.plan,
|
|
10609
10753
|
planRoot: planRoot.value
|
|
@@ -10618,7 +10762,7 @@ ${summary}`;
|
|
|
10618
10762
|
};
|
|
10619
10763
|
|
|
10620
10764
|
// src/application/ui/shared/launch/implement.ts
|
|
10621
|
-
import { join as
|
|
10765
|
+
import { join as join23 } from "path";
|
|
10622
10766
|
|
|
10623
10767
|
// src/application/chain/build/guard.ts
|
|
10624
10768
|
var guard = (name, predicate, body) => ({
|
|
@@ -10675,7 +10819,7 @@ var loop = (name, body, opts = {}) => ({
|
|
|
10675
10819
|
|
|
10676
10820
|
// src/integration/observability/sinks/progress-file-sink.ts
|
|
10677
10821
|
import { promises as fs14 } from "fs";
|
|
10678
|
-
import { dirname as
|
|
10822
|
+
import { dirname as dirname8 } from "path";
|
|
10679
10823
|
var MAX_QUEUE_DEPTH = 1e4;
|
|
10680
10824
|
var createProgressFileSink = (deps) => {
|
|
10681
10825
|
const queue = [];
|
|
@@ -10795,7 +10939,7 @@ var TEMPLATE = `${TITLE}
|
|
|
10795
10939
|
${HEADINGS.map((h) => `## ${h}
|
|
10796
10940
|
`).join("\n")}`;
|
|
10797
10941
|
var mergeSection = async (path, rendered) => {
|
|
10798
|
-
await fs14.mkdir(
|
|
10942
|
+
await fs14.mkdir(dirname8(path), { recursive: true });
|
|
10799
10943
|
let current;
|
|
10800
10944
|
try {
|
|
10801
10945
|
current = await fs14.readFile(path, "utf8");
|
|
@@ -11017,6 +11161,45 @@ var gitCommitWithMessage = async (runner, cwd, message) => {
|
|
|
11017
11161
|
if (!head.ok) return Result.error(head.error);
|
|
11018
11162
|
return Result.ok({ committed: true, headSha: head.value });
|
|
11019
11163
|
};
|
|
11164
|
+
var gitStashPush = async (runner, cwd, message) => {
|
|
11165
|
+
const dirty = await gitHasUncommittedChanges(runner, cwd);
|
|
11166
|
+
if (!dirty.ok) return Result.error(dirty.error);
|
|
11167
|
+
if (!dirty.value) return Result.ok({ stashed: false });
|
|
11168
|
+
const stash = await runner.run(cwd, ["stash", "push", "-u", "-m", message]);
|
|
11169
|
+
if (!stash.ok) return Result.error(stash.error);
|
|
11170
|
+
if (stash.value.exitCode !== 0) {
|
|
11171
|
+
return Result.error(
|
|
11172
|
+
new StorageError({
|
|
11173
|
+
subCode: "io",
|
|
11174
|
+
message: `git stash push failed: ${(stash.value.stderr || stash.value.stdout).trim()}`
|
|
11175
|
+
})
|
|
11176
|
+
);
|
|
11177
|
+
}
|
|
11178
|
+
return Result.ok({ stashed: true });
|
|
11179
|
+
};
|
|
11180
|
+
var gitResetHard = async (runner, cwd) => {
|
|
11181
|
+
const reset = await runner.run(cwd, ["reset", "--hard", "HEAD"]);
|
|
11182
|
+
if (!reset.ok) return Result.error(reset.error);
|
|
11183
|
+
if (reset.value.exitCode !== 0) {
|
|
11184
|
+
return Result.error(
|
|
11185
|
+
new StorageError({
|
|
11186
|
+
subCode: "io",
|
|
11187
|
+
message: `git reset --hard failed: ${(reset.value.stderr || reset.value.stdout).trim()}`
|
|
11188
|
+
})
|
|
11189
|
+
);
|
|
11190
|
+
}
|
|
11191
|
+
const clean = await runner.run(cwd, ["clean", "-fd"]);
|
|
11192
|
+
if (!clean.ok) return Result.error(clean.error);
|
|
11193
|
+
if (clean.value.exitCode !== 0) {
|
|
11194
|
+
return Result.error(
|
|
11195
|
+
new StorageError({
|
|
11196
|
+
subCode: "io",
|
|
11197
|
+
message: `git clean -fd failed: ${(clean.value.stderr || clean.value.stdout).trim()}`
|
|
11198
|
+
})
|
|
11199
|
+
);
|
|
11200
|
+
}
|
|
11201
|
+
return Result.ok(void 0);
|
|
11202
|
+
};
|
|
11020
11203
|
var gitGetCurrentBranch = async (runner, cwd) => {
|
|
11021
11204
|
const result = await runner.run(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
11022
11205
|
if (!result.ok) return Result.error(result.error);
|
|
@@ -11492,9 +11675,9 @@ var implementSession = (sandboxCwd, repoPath, prompt, model, signalsFile) => ({
|
|
|
11492
11675
|
});
|
|
11493
11676
|
|
|
11494
11677
|
// src/application/flows/implement/leaves/round-artifacts.ts
|
|
11495
|
-
import { join as
|
|
11678
|
+
import { join as join20 } from "path";
|
|
11496
11679
|
var nextRoundNum = async (workspaceRoot) => {
|
|
11497
|
-
const entries = await listDir(
|
|
11680
|
+
const entries = await listDir(join20(String(workspaceRoot), "rounds"));
|
|
11498
11681
|
if (!entries.ok) return 1;
|
|
11499
11682
|
let max = 0;
|
|
11500
11683
|
for (const name of entries.value) {
|
|
@@ -11503,12 +11686,12 @@ var nextRoundNum = async (workspaceRoot) => {
|
|
|
11503
11686
|
}
|
|
11504
11687
|
return max + 1;
|
|
11505
11688
|
};
|
|
11506
|
-
var roundSignalsPath = (workspaceRoot, round, role) =>
|
|
11507
|
-
var roundEvaluationRelativePath = (round) =>
|
|
11508
|
-
var roundDir = (workspaceRoot, round, role) =>
|
|
11689
|
+
var roundSignalsPath = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role, "signals.json");
|
|
11690
|
+
var roundEvaluationRelativePath = (round) => join20("rounds", String(round), "evaluator", "evaluation.md");
|
|
11691
|
+
var roundDir = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role);
|
|
11509
11692
|
var writeEvaluatorRoundArtifacts = async (workspaceRoot, round, signals, logger) => {
|
|
11510
11693
|
const base = roundDir(workspaceRoot, round, "evaluator");
|
|
11511
|
-
const evaluation = await writeTextAtomic(
|
|
11694
|
+
const evaluation = await writeTextAtomic(join20(base, "evaluation.md"), renderEvaluation(findEvaluation2(signals)));
|
|
11512
11695
|
if (!evaluation.ok) {
|
|
11513
11696
|
logger?.warn("failed to write evaluator round artifact", { round, base, error: evaluation.error.message });
|
|
11514
11697
|
}
|
|
@@ -12015,6 +12198,7 @@ var postTaskCheckLeaf = (deps, opts, taskId) => {
|
|
|
12015
12198
|
};
|
|
12016
12199
|
|
|
12017
12200
|
// src/business/task/preflight-task.ts
|
|
12201
|
+
var ELEMENT_NAME = "preflight-task";
|
|
12018
12202
|
var preflightTaskUseCase = async (props) => {
|
|
12019
12203
|
const log = props.logger.named("task.preflight");
|
|
12020
12204
|
log.debug("checking working tree", { cwd: props.cwd });
|
|
@@ -12035,6 +12219,9 @@ var preflightTaskUseCase = async (props) => {
|
|
|
12035
12219
|
});
|
|
12036
12220
|
return Result.ok(void 0);
|
|
12037
12221
|
}
|
|
12222
|
+
if (policy === "prompt") {
|
|
12223
|
+
return resolveViaPrompt(props, count.value);
|
|
12224
|
+
}
|
|
12038
12225
|
log.warn("refusing to start a task on a dirty tree", { cwd: props.cwd, dirtyEntries: count.value });
|
|
12039
12226
|
return Result.error(
|
|
12040
12227
|
new InvalidStateError({
|
|
@@ -12046,24 +12233,105 @@ var preflightTaskUseCase = async (props) => {
|
|
|
12046
12233
|
})
|
|
12047
12234
|
);
|
|
12048
12235
|
};
|
|
12236
|
+
var resolveViaPrompt = async (props, dirtyEntries) => {
|
|
12237
|
+
const log = props.logger.named("task.preflight");
|
|
12238
|
+
if (props.askDirtyTreeChoice === void 0 || props.gitStash === void 0 || props.gitReset === void 0 || props.clock === void 0) {
|
|
12239
|
+
throw new InvalidStateError({
|
|
12240
|
+
entity: ELEMENT_NAME,
|
|
12241
|
+
currentState: "prompt-without-deps",
|
|
12242
|
+
attemptedAction: "configure-prompt-deps",
|
|
12243
|
+
message: "preflight-task: dirtyTreePolicy='prompt' requires askDirtyTreeChoice, gitStash, gitReset, and clock dependencies"
|
|
12244
|
+
});
|
|
12245
|
+
}
|
|
12246
|
+
const choice = await props.askDirtyTreeChoice({ cwd: props.cwd, dirtyEntries });
|
|
12247
|
+
if (!choice.ok) {
|
|
12248
|
+
return Result.error(choice.error);
|
|
12249
|
+
}
|
|
12250
|
+
switch (choice.value) {
|
|
12251
|
+
case "keep":
|
|
12252
|
+
log.info(`working tree dirty (${String(dirtyEntries)} entries) \u2014 proceeding (user chose 'keep')`, {
|
|
12253
|
+
cwd: props.cwd,
|
|
12254
|
+
dirtyEntries
|
|
12255
|
+
});
|
|
12256
|
+
return Result.ok(void 0);
|
|
12257
|
+
case "stash": {
|
|
12258
|
+
const sprintLabel = props.sprintId !== void 0 && props.sprintId.length > 0 ? props.sprintId : "unknown";
|
|
12259
|
+
const message = `ralphctl preflight stash (sprint ${sprintLabel}, ${String(props.clock())})`;
|
|
12260
|
+
const stashed = await props.gitStash(props.cwd, message);
|
|
12261
|
+
if (!stashed.ok) return Result.error(stashed.error);
|
|
12262
|
+
if (!stashed.value.stashed) {
|
|
12263
|
+
log.warn("stash reported no changes despite dirty status \u2014 proceeding", { cwd: props.cwd });
|
|
12264
|
+
return Result.ok(void 0);
|
|
12265
|
+
}
|
|
12266
|
+
log.info(`stashed working tree \u2014 recoverable as: ${message}`, { cwd: props.cwd, stashMessage: message });
|
|
12267
|
+
return Result.ok(void 0);
|
|
12268
|
+
}
|
|
12269
|
+
case "reset": {
|
|
12270
|
+
const reset = await props.gitReset(props.cwd);
|
|
12271
|
+
if (!reset.ok) return Result.error(reset.error);
|
|
12272
|
+
log.info("reset working tree \u2014 discarded uncommitted + untracked changes", { cwd: props.cwd });
|
|
12273
|
+
return Result.ok(void 0);
|
|
12274
|
+
}
|
|
12275
|
+
case "cancel":
|
|
12276
|
+
return Result.error(
|
|
12277
|
+
new AbortError({ elementName: ELEMENT_NAME, reason: "user cancelled on dirty working tree" })
|
|
12278
|
+
);
|
|
12279
|
+
}
|
|
12280
|
+
};
|
|
12049
12281
|
|
|
12050
12282
|
// src/application/flows/implement/leaves/preflight-task.ts
|
|
12283
|
+
var ELEMENT_NAME2 = "preflight-task";
|
|
12051
12284
|
var preflightTaskLeaf = (deps, cwd, name = "preflight-task") => {
|
|
12052
12285
|
const gitStatusEntryCount = async (path) => {
|
|
12053
12286
|
const status = await gitStatusPorcelain(deps.gitRunner, path);
|
|
12054
12287
|
if (!status.ok) return status;
|
|
12055
12288
|
return { ok: true, value: status.value.length };
|
|
12056
12289
|
};
|
|
12290
|
+
const gitStash = (path, message) => gitStashPush(deps.gitRunner, path, message);
|
|
12291
|
+
const gitReset = (path) => gitResetHard(deps.gitRunner, path);
|
|
12292
|
+
const askDirtyTreeChoice = async ({
|
|
12293
|
+
cwd: dirtyCwd,
|
|
12294
|
+
dirtyEntries
|
|
12295
|
+
}) => {
|
|
12296
|
+
const choice = await deps.interactive.askChoice(
|
|
12297
|
+
`Working tree at ${String(dirtyCwd)} has ${String(dirtyEntries)} uncommitted change(s). How do you want to handle it?`,
|
|
12298
|
+
[
|
|
12299
|
+
{
|
|
12300
|
+
label: "Keep changes \u2014 proceed on the dirty tree",
|
|
12301
|
+
value: "keep",
|
|
12302
|
+
description: "AI may build on / overwrite the pending diff"
|
|
12303
|
+
},
|
|
12304
|
+
{ label: "Stash \u2014 save changes to a recoverable stash, then proceed", value: "stash" },
|
|
12305
|
+
{
|
|
12306
|
+
label: "Reset \u2014 discard all uncommitted + untracked changes, then proceed",
|
|
12307
|
+
value: "reset",
|
|
12308
|
+
description: "git reset --hard && git clean -fd"
|
|
12309
|
+
},
|
|
12310
|
+
{ label: "Cancel \u2014 abort the implement run", value: "cancel" }
|
|
12311
|
+
]
|
|
12312
|
+
);
|
|
12313
|
+
if (!choice.ok) {
|
|
12314
|
+
return Result.error(
|
|
12315
|
+
new AbortError({ elementName: ELEMENT_NAME2, reason: `dirty-tree prompt cancelled \u2014 ${choice.error.message}` })
|
|
12316
|
+
);
|
|
12317
|
+
}
|
|
12318
|
+
return Result.ok(choice.value);
|
|
12319
|
+
};
|
|
12057
12320
|
return leaf(name, {
|
|
12058
12321
|
useCase: {
|
|
12059
|
-
execute: async () => preflightTaskUseCase({
|
|
12322
|
+
execute: async (input) => preflightTaskUseCase({
|
|
12060
12323
|
cwd,
|
|
12061
12324
|
gitStatusEntryCount,
|
|
12325
|
+
gitStash,
|
|
12326
|
+
gitReset,
|
|
12327
|
+
askDirtyTreeChoice,
|
|
12328
|
+
clock: deps.clock,
|
|
12329
|
+
sprintId: input.sprintId,
|
|
12062
12330
|
logger: deps.logger,
|
|
12063
12331
|
...deps.dirtyTreePolicy !== void 0 ? { dirtyTreePolicy: deps.dirtyTreePolicy } : {}
|
|
12064
12332
|
})
|
|
12065
12333
|
},
|
|
12066
|
-
input: () =>
|
|
12334
|
+
input: (ctx) => ({ sprintId: String(ctx.sprintId) }),
|
|
12067
12335
|
output: (ctx) => ctx
|
|
12068
12336
|
});
|
|
12069
12337
|
};
|
|
@@ -12547,7 +12815,7 @@ var startAttemptLeaf = (deps, taskId) => leaf(
|
|
|
12547
12815
|
|
|
12548
12816
|
// src/application/flows/implement/leaves/build-task-workspace.ts
|
|
12549
12817
|
import { promises as fs16 } from "fs";
|
|
12550
|
-
import { join as
|
|
12818
|
+
import { join as join21 } from "path";
|
|
12551
12819
|
var renderDoneCriteria = (task) => {
|
|
12552
12820
|
const header = `# Done criteria \u2014 ${task.name}
|
|
12553
12821
|
|
|
@@ -12580,7 +12848,7 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
|
|
|
12580
12848
|
useCase: {
|
|
12581
12849
|
execute: async (input) => {
|
|
12582
12850
|
const log = deps.logger.named("implement.workspace");
|
|
12583
|
-
const workspaceRoot =
|
|
12851
|
+
const workspaceRoot = join21(String(opts.sprintDir), "implement", String(input.task.id));
|
|
12584
12852
|
const prompt = await buildImplementPrompt(deps.templateLoader, {
|
|
12585
12853
|
task: input.task,
|
|
12586
12854
|
projectPath: String(opts.cwd),
|
|
@@ -12588,10 +12856,10 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
|
|
|
12588
12856
|
...opts.checkScript !== void 0 ? { checkScript: opts.checkScript } : {}
|
|
12589
12857
|
});
|
|
12590
12858
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
12591
|
-
const wrotePrompt = await writeOrError(
|
|
12859
|
+
const wrotePrompt = await writeOrError(join21(workspaceRoot, "prompt.md"), String(prompt.value));
|
|
12592
12860
|
if (!wrotePrompt.ok) return Result.error(wrotePrompt.error);
|
|
12593
12861
|
const wroteCriteria = await writeOrError(
|
|
12594
|
-
|
|
12862
|
+
join21(workspaceRoot, "done-criteria.md"),
|
|
12595
12863
|
renderDoneCriteria(input.task)
|
|
12596
12864
|
);
|
|
12597
12865
|
if (!wroteCriteria.ok) return Result.error(wroteCriteria.error);
|
|
@@ -12662,15 +12930,20 @@ var transitionSprintToReviewLeaf = (deps) => leaf("transition-sprint-to-review",
|
|
|
12662
12930
|
|
|
12663
12931
|
// src/integration/io/lock-paths.ts
|
|
12664
12932
|
import { createHash } from "crypto";
|
|
12665
|
-
import { join as
|
|
12933
|
+
import { join as join22 } from "path";
|
|
12666
12934
|
var repoLockFile = (locksRoot, worktreePath) => {
|
|
12667
12935
|
const hash = createHash("sha1").update(String(worktreePath)).digest("hex").slice(0, 16);
|
|
12668
|
-
return AbsolutePath.parse(
|
|
12936
|
+
return AbsolutePath.parse(join22(String(locksRoot), `repo-${hash}.lock`));
|
|
12669
12937
|
};
|
|
12670
12938
|
|
|
12671
12939
|
// src/application/flows/implement/leaves/with-repo-lock.ts
|
|
12672
12940
|
var withRepoLock = (opts, inner) => ({
|
|
12673
12941
|
name: `with-repo-lock(${inner.name})`,
|
|
12942
|
+
// Expose the wrapped chain through the composite-pattern `children` slot so `flattenLeaves`
|
|
12943
|
+
// walks into it when the TUI builds its planned-leaf list. Without this the wrapper looked
|
|
12944
|
+
// like an opaque single leaf and the Flow-steps panel rendered only "with-repo-lock(…)" —
|
|
12945
|
+
// never the real setup / per-task / teardown sequence inside the lock.
|
|
12946
|
+
children: [inner],
|
|
12674
12947
|
async execute(ctx, signal, onTrace) {
|
|
12675
12948
|
const lockPath = repoLockFile(opts.locksRoot, opts.worktreePath);
|
|
12676
12949
|
if (!lockPath.ok) {
|
|
@@ -12837,12 +13110,15 @@ var createImplementFlow = (deps, opts) => {
|
|
|
12837
13110
|
path: r.path,
|
|
12838
13111
|
...r.setupScript !== void 0 ? { setupScript: r.setupScript } : {}
|
|
12839
13112
|
}));
|
|
13113
|
+
const dirtyTreePolicy = opts.dirtyTreePolicy ?? "prompt";
|
|
12840
13114
|
const preflightLeaves = uniqueRepoCwds.map(
|
|
12841
13115
|
(cwd, i) => preflightTaskLeaf(
|
|
12842
13116
|
{
|
|
12843
13117
|
gitRunner: deps.gitRunner,
|
|
13118
|
+
interactive: deps.interactive,
|
|
13119
|
+
clock: deps.clock,
|
|
12844
13120
|
logger: deps.logger,
|
|
12845
|
-
|
|
13121
|
+
dirtyTreePolicy
|
|
12846
13122
|
},
|
|
12847
13123
|
cwd,
|
|
12848
13124
|
`preflight-task-${String(i + 1)}-${String(cwd)}`
|
|
@@ -12903,7 +13179,7 @@ var createImplementFlow = (deps, opts) => {
|
|
|
12903
13179
|
|
|
12904
13180
|
// src/integration/observability/sinks/file-log-sink.ts
|
|
12905
13181
|
import { promises as fs17 } from "fs";
|
|
12906
|
-
import { dirname as
|
|
13182
|
+
import { dirname as dirname9 } from "path";
|
|
12907
13183
|
var startFileLogSink = (deps) => {
|
|
12908
13184
|
const queue = [];
|
|
12909
13185
|
let draining;
|
|
@@ -12915,7 +13191,7 @@ var startFileLogSink = (deps) => {
|
|
|
12915
13191
|
if (next === void 0) continue;
|
|
12916
13192
|
try {
|
|
12917
13193
|
if (!dirEnsured) {
|
|
12918
|
-
await fs17.mkdir(
|
|
13194
|
+
await fs17.mkdir(dirname9(String(deps.file)), { recursive: true });
|
|
12919
13195
|
dirEnsured = true;
|
|
12920
13196
|
}
|
|
12921
13197
|
await fs17.appendFile(String(deps.file), `${JSON.stringify(next)}
|
|
@@ -12957,11 +13233,11 @@ var launchImplement = (ctx) => {
|
|
|
12957
13233
|
return a.status === "in_progress" ? -1 : 1;
|
|
12958
13234
|
});
|
|
12959
13235
|
if (todoTasks.length === 0) return { ok: false, reason: "No tasks to implement or resume." };
|
|
12960
|
-
const sprintDirPath = AbsolutePath.parse(
|
|
13236
|
+
const sprintDirPath = AbsolutePath.parse(join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id)));
|
|
12961
13237
|
if (!sprintDirPath.ok) return { ok: false, reason: sprintDirPath.error.message };
|
|
12962
|
-
const progressPath = AbsolutePath.parse(
|
|
13238
|
+
const progressPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "progress.md"));
|
|
12963
13239
|
if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
|
|
12964
|
-
const chainLogPath = AbsolutePath.parse(
|
|
13240
|
+
const chainLogPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "chain.log"));
|
|
12965
13241
|
if (!chainLogPath.ok) return { ok: false, reason: chainLogPath.error.message };
|
|
12966
13242
|
const chainLog = startFileLogSink({ file: chainLogPath.value, bus: deps.app.eventBus });
|
|
12967
13243
|
const repositories = /* @__PURE__ */ new Map();
|
|
@@ -13026,7 +13302,7 @@ var launchImplement = (ctx) => {
|
|
|
13026
13302
|
};
|
|
13027
13303
|
|
|
13028
13304
|
// src/application/ui/shared/launch/review.ts
|
|
13029
|
-
import { join as
|
|
13305
|
+
import { join as join25 } from "path";
|
|
13030
13306
|
|
|
13031
13307
|
// src/application/flows/review/leaves/ensure-feedback-file.ts
|
|
13032
13308
|
import { promises as fs18 } from "fs";
|
|
@@ -13269,12 +13545,12 @@ var buildApplyFeedbackPrompt = async (deps, input) => buildPrompt(deps, applyFee
|
|
|
13269
13545
|
|
|
13270
13546
|
// src/integration/ai/signals/_engine/temp-signals-file.ts
|
|
13271
13547
|
import { tmpdir as tmpdir2 } from "os";
|
|
13272
|
-
import { join as
|
|
13548
|
+
import { join as join24 } from "path";
|
|
13273
13549
|
var counter = 0;
|
|
13274
13550
|
var allocSignalsTempPath = (label) => {
|
|
13275
13551
|
counter += 1;
|
|
13276
13552
|
const filename = `ralphctl-signals-${label}-${String(process.pid)}-${String(Date.now())}-${String(counter)}.json`;
|
|
13277
|
-
return AbsolutePath.parse(
|
|
13553
|
+
return AbsolutePath.parse(join24(tmpdir2(), filename));
|
|
13278
13554
|
};
|
|
13279
13555
|
var withSignalsTempPath = async (label, fn) => {
|
|
13280
13556
|
const path = allocSignalsTempPath(label);
|
|
@@ -13533,11 +13809,11 @@ var launchReview = (ctx) => {
|
|
|
13533
13809
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
13534
13810
|
if (!cwd) return { ok: false, reason: "No repository path resolvable from the project." };
|
|
13535
13811
|
const feedbackPath = AbsolutePath.parse(
|
|
13536
|
-
|
|
13812
|
+
join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "feedback.md")
|
|
13537
13813
|
);
|
|
13538
13814
|
if (!feedbackPath.ok) return { ok: false, reason: feedbackPath.error.message };
|
|
13539
13815
|
const progressPath = AbsolutePath.parse(
|
|
13540
|
-
|
|
13816
|
+
join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "progress.md")
|
|
13541
13817
|
);
|
|
13542
13818
|
if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
|
|
13543
13819
|
const checkScript = snapshot.project?.repositories.find((r) => r.checkScript !== void 0)?.checkScript;
|
|
@@ -13766,11 +14042,11 @@ var probeReadinessLeaf = (deps) => leaf("probe", {
|
|
|
13766
14042
|
import { promises as fs21 } from "fs";
|
|
13767
14043
|
|
|
13768
14044
|
// src/integration/ai/readiness/_engine/setup.ts
|
|
13769
|
-
import { join as
|
|
14045
|
+
import { join as join27 } from "path";
|
|
13770
14046
|
|
|
13771
14047
|
// src/integration/ai/runs/_engine/run-artifacts.ts
|
|
13772
14048
|
import { promises as fs20 } from "fs";
|
|
13773
|
-
import { join as
|
|
14049
|
+
import { join as join26 } from "path";
|
|
13774
14050
|
var BODY_PREVIEW_LIMIT = 800;
|
|
13775
14051
|
var buildRunDirName = () => {
|
|
13776
14052
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13780,7 +14056,7 @@ var buildRunDirName = () => {
|
|
|
13780
14056
|
var readRunBodyPreview = async (runDir, options) => {
|
|
13781
14057
|
let raw;
|
|
13782
14058
|
try {
|
|
13783
|
-
raw = await fs20.readFile(
|
|
14059
|
+
raw = await fs20.readFile(join26(String(runDir), "body.txt"), "utf8");
|
|
13784
14060
|
} catch (cause) {
|
|
13785
14061
|
if (isErrnoException(cause) && cause.code === "ENOENT") return void 0;
|
|
13786
14062
|
const code = isErrnoException(cause) ? cause.code : "unknown";
|
|
@@ -13909,11 +14185,11 @@ var setupReadinessUseCase = async (deps, input) => {
|
|
|
13909
14185
|
...input.existingContextFile !== void 0 ? { existingContextFile: input.existingContextFile } : {}
|
|
13910
14186
|
});
|
|
13911
14187
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
13912
|
-
const runDir = AbsolutePath.parse(
|
|
14188
|
+
const runDir = AbsolutePath.parse(join27(String(deps.runsRoot), "readiness", buildRunDirName()));
|
|
13913
14189
|
if (!runDir.ok) return Result.error(runDir.error);
|
|
13914
|
-
const promptFile = AbsolutePath.parse(
|
|
14190
|
+
const promptFile = AbsolutePath.parse(join27(String(runDir.value), "prompt.md"));
|
|
13915
14191
|
if (!promptFile.ok) return Result.error(promptFile.error);
|
|
13916
|
-
const bodyFile = AbsolutePath.parse(
|
|
14192
|
+
const bodyFile = AbsolutePath.parse(join27(String(runDir.value), "body.txt"));
|
|
13917
14193
|
if (!bodyFile.ok) return Result.error(bodyFile.error);
|
|
13918
14194
|
const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
|
|
13919
14195
|
if (!promptWrote.ok) return Result.error(promptWrote.error);
|
|
@@ -13962,7 +14238,7 @@ var setupReadinessUseCase = async (deps, input) => {
|
|
|
13962
14238
|
const verifyScript = signals.value.find(
|
|
13963
14239
|
(s) => s.type === "verify-script"
|
|
13964
14240
|
)?.command;
|
|
13965
|
-
const targetPath = AbsolutePath.parse(
|
|
14241
|
+
const targetPath = AbsolutePath.parse(join27(String(input.repository.path), targetPathFor(input.tool)));
|
|
13966
14242
|
if (!targetPath.ok) return Result.error(targetPath.error);
|
|
13967
14243
|
log.info(`proposal ready for repo ${input.repository.name}`, {
|
|
13968
14244
|
repositoryId: String(input.repository.id),
|
|
@@ -14028,6 +14304,9 @@ var pickExistingContextPath = (tool, state) => {
|
|
|
14028
14304
|
if (tool === "copilot" && a.tool === "copilot") {
|
|
14029
14305
|
return a.copilotInstructions !== void 0 ? String(a.copilotInstructions.path) : void 0;
|
|
14030
14306
|
}
|
|
14307
|
+
if (tool === "codex" && a.tool === "codex") {
|
|
14308
|
+
return a.agentsMd !== void 0 ? String(a.agentsMd.path) : void 0;
|
|
14309
|
+
}
|
|
14031
14310
|
return void 0;
|
|
14032
14311
|
};
|
|
14033
14312
|
var proposeReadinessLeaf = (deps) => leaf("propose", {
|
|
@@ -14208,7 +14487,7 @@ var launchReadiness = (ctx) => {
|
|
|
14208
14487
|
};
|
|
14209
14488
|
|
|
14210
14489
|
// src/application/flows/detect-skills/leaves/propose.ts
|
|
14211
|
-
import { join as
|
|
14490
|
+
import { join as join28 } from "path";
|
|
14212
14491
|
|
|
14213
14492
|
// src/integration/ai/prompts/detect-skills/definition.ts
|
|
14214
14493
|
var detectSkillsPromptDef = {
|
|
@@ -14272,11 +14551,11 @@ var proposeUseCase = async (deps, input) => {
|
|
|
14272
14551
|
skillsConvention: deps.skillsAdapter.describeSkillsConvention()
|
|
14273
14552
|
});
|
|
14274
14553
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
14275
|
-
const runDir = AbsolutePath.parse(
|
|
14554
|
+
const runDir = AbsolutePath.parse(join28(String(deps.runsRoot), "detect-skills", buildRunDirName()));
|
|
14276
14555
|
if (!runDir.ok) return Result.error(runDir.error);
|
|
14277
|
-
const promptFile = AbsolutePath.parse(
|
|
14556
|
+
const promptFile = AbsolutePath.parse(join28(String(runDir.value), "prompt.md"));
|
|
14278
14557
|
if (!promptFile.ok) return Result.error(promptFile.error);
|
|
14279
|
-
const bodyFile = AbsolutePath.parse(
|
|
14558
|
+
const bodyFile = AbsolutePath.parse(join28(String(runDir.value), "body.txt"));
|
|
14280
14559
|
if (!bodyFile.ok) return Result.error(bodyFile.error);
|
|
14281
14560
|
const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
|
|
14282
14561
|
if (!promptWrote.ok) return Result.error(promptWrote.error);
|
|
@@ -14890,7 +15169,7 @@ var launchDetectSkills = (ctx) => {
|
|
|
14890
15169
|
};
|
|
14891
15170
|
|
|
14892
15171
|
// src/application/flows/detect-scripts/leaves/propose.ts
|
|
14893
|
-
import { join as
|
|
15172
|
+
import { join as join29 } from "path";
|
|
14894
15173
|
|
|
14895
15174
|
// src/integration/ai/prompts/detect-scripts/definition.ts
|
|
14896
15175
|
var detectScriptsPromptDef = {
|
|
@@ -14935,11 +15214,11 @@ var proposeUseCase2 = async (deps, input) => {
|
|
|
14935
15214
|
repositoryPath: String(input.repository.path)
|
|
14936
15215
|
});
|
|
14937
15216
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
14938
|
-
const runDir = AbsolutePath.parse(
|
|
15217
|
+
const runDir = AbsolutePath.parse(join29(String(deps.runsRoot), "detect-scripts", buildRunDirName()));
|
|
14939
15218
|
if (!runDir.ok) return Result.error(runDir.error);
|
|
14940
|
-
const promptFile = AbsolutePath.parse(
|
|
15219
|
+
const promptFile = AbsolutePath.parse(join29(String(runDir.value), "prompt.md"));
|
|
14941
15220
|
if (!promptFile.ok) return Result.error(promptFile.error);
|
|
14942
|
-
const bodyFile = AbsolutePath.parse(
|
|
15221
|
+
const bodyFile = AbsolutePath.parse(join29(String(runDir.value), "body.txt"));
|
|
14943
15222
|
if (!bodyFile.ok) return Result.error(bodyFile.error);
|
|
14944
15223
|
const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
|
|
14945
15224
|
if (!promptWrote.ok) return Result.error(promptWrote.error);
|
|
@@ -15283,10 +15562,10 @@ var launchDetectScripts = (ctx) => {
|
|
|
15283
15562
|
};
|
|
15284
15563
|
|
|
15285
15564
|
// src/application/ui/shared/launch/ideate.ts
|
|
15286
|
-
import { join as
|
|
15565
|
+
import { join as join31 } from "path";
|
|
15287
15566
|
|
|
15288
15567
|
// src/application/flows/ideate/flow.ts
|
|
15289
|
-
import { join as
|
|
15568
|
+
import { join as join30 } from "path";
|
|
15290
15569
|
|
|
15291
15570
|
// src/integration/ai/prompts/ideate/definition.ts
|
|
15292
15571
|
var nonEmpty2 = (field) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message: `${field} must not be empty` })) : Result.ok(v);
|
|
@@ -15523,8 +15802,8 @@ var createIdeateFlow = (deps, opts) => {
|
|
|
15523
15802
|
parent: () => opts.ideateRoot,
|
|
15524
15803
|
slug: () => slug,
|
|
15525
15804
|
write: (ctx, root) => {
|
|
15526
|
-
const promptPath = AbsolutePath.parse(
|
|
15527
|
-
const outputPath = AbsolutePath.parse(
|
|
15805
|
+
const promptPath = AbsolutePath.parse(join30(String(root), "prompt.md"));
|
|
15806
|
+
const outputPath = AbsolutePath.parse(join30(String(root), "ideate.json"));
|
|
15528
15807
|
if (!promptPath.ok) throw promptPath.error;
|
|
15529
15808
|
if (!outputPath.ok) throw outputPath.error;
|
|
15530
15809
|
return {
|
|
@@ -15588,7 +15867,7 @@ var launchIdeate = async (ctx) => {
|
|
|
15588
15867
|
const bodyAns = await deps.interactive.askText("Idea description (paste or type)");
|
|
15589
15868
|
if (!bodyAns.ok) return { ok: false, reason: bodyAns.error.message };
|
|
15590
15869
|
const ideateRoot = AbsolutePath.parse(
|
|
15591
|
-
|
|
15870
|
+
join31(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "ideate")
|
|
15592
15871
|
);
|
|
15593
15872
|
if (!ideateRoot.ok) return { ok: false, reason: ideateRoot.error.message };
|
|
15594
15873
|
const element = createIdeateFlow(
|
|
@@ -16704,6 +16983,39 @@ var createTicketRemoveFlow = (deps) => leaf("ticket-remove", {
|
|
|
16704
16983
|
output: (c, o) => ({ ...c, output: o })
|
|
16705
16984
|
});
|
|
16706
16985
|
|
|
16986
|
+
// src/business/task/unblock-task.ts
|
|
16987
|
+
var unblockTaskUseCase = async (props) => {
|
|
16988
|
+
const log = props.logger.named("task.unblock");
|
|
16989
|
+
if (props.task.status === "todo") {
|
|
16990
|
+
log.debug("already todo, skipping", { taskId: props.task.id, sprintId: props.sprintId });
|
|
16991
|
+
return Result.ok(props.task);
|
|
16992
|
+
}
|
|
16993
|
+
log.debug("unblocking task", {
|
|
16994
|
+
taskId: props.task.id,
|
|
16995
|
+
sprintId: props.sprintId,
|
|
16996
|
+
from: props.task.status
|
|
16997
|
+
});
|
|
16998
|
+
const transitioned = unblockTask(props.task);
|
|
16999
|
+
if (!transitioned.ok) {
|
|
17000
|
+
log.warn("invalid state transition", {
|
|
17001
|
+
taskId: props.task.id,
|
|
17002
|
+
from: props.task.status,
|
|
17003
|
+
error: transitioned.error.message
|
|
17004
|
+
});
|
|
17005
|
+
return Result.error(transitioned.error);
|
|
17006
|
+
}
|
|
17007
|
+
const persisted = await props.taskRepo.update(props.sprintId, transitioned.value);
|
|
17008
|
+
if (!persisted.ok) {
|
|
17009
|
+
log.error("persist failed", { taskId: transitioned.value.id, error: persisted.error.message });
|
|
17010
|
+
return Result.error(persisted.error);
|
|
17011
|
+
}
|
|
17012
|
+
log.info(`unblocked task '${transitioned.value.name}'`, {
|
|
17013
|
+
taskId: transitioned.value.id,
|
|
17014
|
+
sprintId: props.sprintId
|
|
17015
|
+
});
|
|
17016
|
+
return Result.ok(transitioned.value);
|
|
17017
|
+
};
|
|
17018
|
+
|
|
16707
17019
|
// src/application/ui/tui/views/sprint-detail-view.tsx
|
|
16708
17020
|
import { jsx as jsx41, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
16709
17021
|
var buildFocusList = (sprint, tasks) => [
|
|
@@ -16772,12 +17084,15 @@ var SprintDetailView = () => {
|
|
|
16772
17084
|
const [feedback, setFeedback] = useState25(void 0);
|
|
16773
17085
|
const ticketsEditable = sprint?.status === "draft";
|
|
16774
17086
|
const inDetail = openIdx !== void 0;
|
|
17087
|
+
const focusedNow = focusList[Math.min(cursorIdx, Math.max(0, focusList.length - 1))];
|
|
17088
|
+
const focusedBlockedTask = focusedNow?.kind === "task" && focusedNow.task.status === "blocked" ? focusedNow.task : void 0;
|
|
16775
17089
|
useViewHints(
|
|
16776
17090
|
inDetail ? [{ keys: "esc", label: "back to list" }] : [
|
|
16777
17091
|
{ keys: "n", label: "flows" },
|
|
16778
17092
|
{ keys: "\u21B5/o", label: "open" },
|
|
16779
17093
|
{ keys: "a", label: "add ticket" },
|
|
16780
|
-
{ keys: "d", label: "remove ticket" }
|
|
17094
|
+
{ keys: "d", label: "remove ticket" },
|
|
17095
|
+
...focusedBlockedTask !== void 0 ? [{ keys: "u", label: "unblock" }] : []
|
|
16781
17096
|
]
|
|
16782
17097
|
);
|
|
16783
17098
|
useInput14((input, key) => {
|
|
@@ -16805,6 +17120,10 @@ var SprintDetailView = () => {
|
|
|
16805
17120
|
if (input === "d" && ticketsEditable) {
|
|
16806
17121
|
const focused = focusList[Math.min(cursorIdx, focusList.length - 1)];
|
|
16807
17122
|
if (focused?.kind === "ticket") setConfirmRemove(focused.ticket);
|
|
17123
|
+
return;
|
|
17124
|
+
}
|
|
17125
|
+
if (input === "u" && focusedBlockedTask !== void 0) {
|
|
17126
|
+
void handleUnblock(focusedBlockedTask);
|
|
16808
17127
|
}
|
|
16809
17128
|
});
|
|
16810
17129
|
useEffect17(() => confirmRemove !== void 0 ? ui.claimPrompt() : void 0, [confirmRemove, ui.claimPrompt]);
|
|
@@ -16820,6 +17139,21 @@ var SprintDetailView = () => {
|
|
|
16820
17139
|
setFeedback(`\u2713 removed "${target.title}"`);
|
|
16821
17140
|
reload();
|
|
16822
17141
|
};
|
|
17142
|
+
const handleUnblock = async (target) => {
|
|
17143
|
+
if (sprint === void 0) return;
|
|
17144
|
+
const r = await unblockTaskUseCase({
|
|
17145
|
+
task: target,
|
|
17146
|
+
sprintId: sprint.id,
|
|
17147
|
+
taskRepo: deps.taskRepo,
|
|
17148
|
+
logger: deps.logger
|
|
17149
|
+
});
|
|
17150
|
+
if (!r.ok) {
|
|
17151
|
+
setFeedback(`\u2717 ${r.error.message}`);
|
|
17152
|
+
return;
|
|
17153
|
+
}
|
|
17154
|
+
setFeedback(`\u2713 unblocked "${target.name}"`);
|
|
17155
|
+
reload();
|
|
17156
|
+
};
|
|
16823
17157
|
return /* @__PURE__ */ jsx41(ViewShell, { title: "Sprint", subtitle: state.kind === "ok" ? state.value.sprint.name : "loading", children: ui.helpOpen ? /* @__PURE__ */ jsx41(HelpOverlay, {}) : state.kind === "loading" || state.kind === "idle" ? /* @__PURE__ */ jsx41(Box29, { paddingX: spacing.indent, children: /* @__PURE__ */ jsx41(Spinner, { label: "Loading\u2026" }) }) : state.kind === "error" ? /* @__PURE__ */ jsx41(Box29, { paddingX: spacing.indent, children: /* @__PURE__ */ jsx41(Text31, { children: "Failed to load sprint." }) }) : confirmRemove !== void 0 ? /* @__PURE__ */ jsxs30(Box29, { flexDirection: "column", paddingX: spacing.indent, children: [
|
|
16824
17158
|
/* @__PURE__ */ jsxs30(Text31, { children: [
|
|
16825
17159
|
"Remove ticket ",
|
|
@@ -17688,30 +18022,23 @@ var SignalLine = ({ signal }) => {
|
|
|
17688
18022
|
/* @__PURE__ */ jsx43(Text33, { bold: row.bold ?? false, children: truncate2(row.text, 80) })
|
|
17689
18023
|
] });
|
|
17690
18024
|
};
|
|
18025
|
+
var LEGEND_ENTRIES = [
|
|
18026
|
+
{ label: "change", color: inkColors.info, description: "file/code edit" },
|
|
18027
|
+
{ label: "learning", color: inkColors.highlight, description: "cross-task insight" },
|
|
18028
|
+
{ label: "decision", color: inkColors.highlight, description: "design choice" },
|
|
18029
|
+
{ label: "verified", color: inkColors.success, description: "task self-check passed" },
|
|
18030
|
+
{ label: "blocked", color: inkColors.error, description: "task self-blocked" },
|
|
18031
|
+
{ label: "commit", color: inkColors.info, description: "proposed commit message" }
|
|
18032
|
+
];
|
|
17691
18033
|
var SignalLegend = () => /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingX: spacing.indent, marginBottom: spacing.section, children: [
|
|
17692
|
-
/* @__PURE__ */
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
/* @__PURE__ */
|
|
17696
|
-
|
|
17697
|
-
|
|
17698
|
-
|
|
17699
|
-
|
|
17700
|
-
" ",
|
|
17701
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.highlight, children: "decision" }),
|
|
17702
|
-
" = design choice"
|
|
17703
|
-
] }),
|
|
17704
|
-
/* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
|
|
17705
|
-
" ",
|
|
17706
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.success, children: "verified" }),
|
|
17707
|
-
" = task self-check passed",
|
|
17708
|
-
" ",
|
|
17709
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.error, children: "blocked" }),
|
|
17710
|
-
" = task self-blocked",
|
|
17711
|
-
" ",
|
|
17712
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.info, children: "commit" }),
|
|
17713
|
-
" = proposed commit message"
|
|
17714
|
-
] })
|
|
18034
|
+
/* @__PURE__ */ jsx43(Text33, { dimColor: true, bold: true, children: "legend" }),
|
|
18035
|
+
/* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: LEGEND_ENTRIES.map((entry) => /* @__PURE__ */ jsxs32(Box31, { children: [
|
|
18036
|
+
/* @__PURE__ */ jsx43(Text33, { color: entry.color, bold: true, children: padLabel2(entry.label) }),
|
|
18037
|
+
/* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
|
|
18038
|
+
"= ",
|
|
18039
|
+
entry.description
|
|
18040
|
+
] })
|
|
18041
|
+
] }, entry.label)) })
|
|
17715
18042
|
] });
|
|
17716
18043
|
var EvaluationLine = ({ evaluation }) => {
|
|
17717
18044
|
const color = evaluation.status === "passed" ? inkColors.success : evaluation.status === "failed" ? inkColors.error : inkColors.warning;
|
|
@@ -18288,7 +18615,7 @@ var ExecuteView = () => {
|
|
|
18288
18615
|
] })
|
|
18289
18616
|
] })
|
|
18290
18617
|
] }) });
|
|
18291
|
-
const outerFlowFilter = (name) => !isPerTaskLeaf(name);
|
|
18618
|
+
const outerFlowFilter = (name) => !isPerTaskLeaf(name) && !name.startsWith("with-repo-lock(");
|
|
18292
18619
|
const flowStepsPanel = /* @__PURE__ */ jsx46(
|
|
18293
18620
|
StepTrace,
|
|
18294
18621
|
{
|
|
@@ -19020,7 +19347,7 @@ var ProbeRow = ({ probe }) => /* @__PURE__ */ jsxs39(Box38, { flexDirection: "co
|
|
|
19020
19347
|
// src/application/ui/tui/views/export-context-view.tsx
|
|
19021
19348
|
import { useCallback as useCallback7, useEffect as useEffect25, useState as useState31 } from "react";
|
|
19022
19349
|
import { Box as Box39, Text as Text41, useInput as useInput20 } from "ink";
|
|
19023
|
-
import { join as
|
|
19350
|
+
import { join as join32 } from "path";
|
|
19024
19351
|
|
|
19025
19352
|
// src/business/sprint/views/context-md.ts
|
|
19026
19353
|
var renderSprintContextMarkdown = (input) => {
|
|
@@ -19152,7 +19479,7 @@ var ExportContextView = () => {
|
|
|
19152
19479
|
return;
|
|
19153
19480
|
}
|
|
19154
19481
|
const outputPath = AbsolutePath.parse(
|
|
19155
|
-
|
|
19482
|
+
join32(String(storage2.dataRoot), "sprints", String(selection.sprintId), "context.md")
|
|
19156
19483
|
);
|
|
19157
19484
|
if (!outputPath.ok) {
|
|
19158
19485
|
setRun({ kind: "error", message: outputPath.error.message });
|
|
@@ -19210,7 +19537,7 @@ var ExportContextView = () => {
|
|
|
19210
19537
|
// src/application/ui/tui/views/export-requirements-view.tsx
|
|
19211
19538
|
import { useCallback as useCallback8, useEffect as useEffect26, useState as useState32 } from "react";
|
|
19212
19539
|
import { Box as Box40, Text as Text42, useInput as useInput21 } from "ink";
|
|
19213
|
-
import { join as
|
|
19540
|
+
import { join as join33 } from "path";
|
|
19214
19541
|
|
|
19215
19542
|
// src/business/sprint/views/requirements-md.ts
|
|
19216
19543
|
var renderSprintRequirementsMarkdown = (sprint) => {
|
|
@@ -19275,7 +19602,7 @@ var ExportRequirementsView = () => {
|
|
|
19275
19602
|
return;
|
|
19276
19603
|
}
|
|
19277
19604
|
const outputPath = AbsolutePath.parse(
|
|
19278
|
-
|
|
19605
|
+
join33(String(storage2.dataRoot), "sprints", String(selection.sprintId), "requirements.md")
|
|
19279
19606
|
);
|
|
19280
19607
|
if (!outputPath.ok) {
|
|
19281
19608
|
setRun({ kind: "error", message: outputPath.error.message });
|
|
@@ -19610,12 +19937,12 @@ var WelcomeView = () => {
|
|
|
19610
19937
|
import { useEffect as useEffect29, useState as useState36 } from "react";
|
|
19611
19938
|
import { Box as Box44, Text as Text46 } from "ink";
|
|
19612
19939
|
import { homedir as osHomedir2 } from "os";
|
|
19613
|
-
import { basename as basename3, join as
|
|
19940
|
+
import { basename as basename3, join as join35 } from "path";
|
|
19614
19941
|
|
|
19615
19942
|
// src/application/ui/tui/prompts/path-picker-prompt.tsx
|
|
19616
19943
|
import { useEffect as useEffect28, useState as useState35 } from "react";
|
|
19617
19944
|
import { promises as fs24 } from "fs";
|
|
19618
|
-
import { dirname as
|
|
19945
|
+
import { dirname as dirname10, join as join34 } from "path";
|
|
19619
19946
|
import { homedir } from "os";
|
|
19620
19947
|
import { Box as Box43, Text as Text45, useInput as useInput23 } from "ink";
|
|
19621
19948
|
import { jsx as jsx55, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
@@ -19623,7 +19950,7 @@ var VISIBLE_ROWS2 = 12;
|
|
|
19623
19950
|
var clamp5 = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
19624
19951
|
var expandHome = (input) => {
|
|
19625
19952
|
if (input === "~") return homedir();
|
|
19626
|
-
if (input.startsWith("~/")) return
|
|
19953
|
+
if (input.startsWith("~/")) return join34(homedir(), input.slice(2));
|
|
19627
19954
|
return input;
|
|
19628
19955
|
};
|
|
19629
19956
|
var PathPickerPrompt = ({
|
|
@@ -19675,7 +20002,7 @@ var PathPickerPrompt = ({
|
|
|
19675
20002
|
return;
|
|
19676
20003
|
}
|
|
19677
20004
|
if (key.backspace || key.delete) {
|
|
19678
|
-
const parent =
|
|
20005
|
+
const parent = dirname10(cwd);
|
|
19679
20006
|
if (parent !== cwd) {
|
|
19680
20007
|
setCwd(parent);
|
|
19681
20008
|
setCursor(1);
|
|
@@ -19700,7 +20027,7 @@ var PathPickerPrompt = ({
|
|
|
19700
20027
|
const row = rows[cursor];
|
|
19701
20028
|
if (row === void 0) return;
|
|
19702
20029
|
if (row.kind === "parent") {
|
|
19703
|
-
const parent =
|
|
20030
|
+
const parent = dirname10(cwd);
|
|
19704
20031
|
if (parent !== cwd) {
|
|
19705
20032
|
setCwd(parent);
|
|
19706
20033
|
setCursor(1);
|
|
@@ -19711,7 +20038,7 @@ var PathPickerPrompt = ({
|
|
|
19711
20038
|
onSubmit(cwd);
|
|
19712
20039
|
return;
|
|
19713
20040
|
}
|
|
19714
|
-
setCwd(
|
|
20041
|
+
setCwd(join34(cwd, row.entry.name));
|
|
19715
20042
|
setCursor(1);
|
|
19716
20043
|
}
|
|
19717
20044
|
},
|
|
@@ -19793,7 +20120,7 @@ var labelFor = (row) => {
|
|
|
19793
20120
|
import { jsx as jsx56, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
19794
20121
|
var expandHome2 = (input) => {
|
|
19795
20122
|
if (input === "~") return osHomedir2();
|
|
19796
|
-
if (input.startsWith("~/")) return
|
|
20123
|
+
if (input.startsWith("~/")) return join35(osHomedir2(), input.slice(2));
|
|
19797
20124
|
return input;
|
|
19798
20125
|
};
|
|
19799
20126
|
var backStep = (step) => {
|
|
@@ -20009,11 +20336,11 @@ var StepView = ({ step, onChange, onCancel, onSubmit }) => {
|
|
|
20009
20336
|
import { useEffect as useEffect30, useState as useState37 } from "react";
|
|
20010
20337
|
import { Box as Box45, Text as Text47 } from "ink";
|
|
20011
20338
|
import { homedir as osHomedir3 } from "os";
|
|
20012
|
-
import { basename as basename4, join as
|
|
20339
|
+
import { basename as basename4, join as join36 } from "path";
|
|
20013
20340
|
import { jsx as jsx57, jsxs as jsxs46 } from "react/jsx-runtime";
|
|
20014
20341
|
var expandHome3 = (input) => {
|
|
20015
20342
|
if (input === "~") return osHomedir3();
|
|
20016
|
-
if (input.startsWith("~/")) return
|
|
20343
|
+
if (input.startsWith("~/")) return join36(osHomedir3(), input.slice(2));
|
|
20017
20344
|
return input;
|
|
20018
20345
|
};
|
|
20019
20346
|
var backStep2 = (step) => {
|
|
@@ -20844,10 +21171,10 @@ var resolveInitialState = ({
|
|
|
20844
21171
|
|
|
20845
21172
|
// src/integration/persistence/selection/last-selection-store.ts
|
|
20846
21173
|
import { promises as fs25 } from "fs";
|
|
20847
|
-
import { join as
|
|
21174
|
+
import { join as join37 } from "path";
|
|
20848
21175
|
var FILE_NAME = "last-selection.json";
|
|
20849
21176
|
var createLastSelectionStore = (stateRoot) => {
|
|
20850
|
-
const path =
|
|
21177
|
+
const path = join37(String(stateRoot), FILE_NAME);
|
|
20851
21178
|
return {
|
|
20852
21179
|
async read() {
|
|
20853
21180
|
try {
|
|
@@ -21734,6 +22061,44 @@ var registerTaskCommand = (program) => {
|
|
|
21734
22061
|
return;
|
|
21735
22062
|
}
|
|
21736
22063
|
process.stdout.write(`${JSON.stringify(result.value, null, 2)}
|
|
22064
|
+
`);
|
|
22065
|
+
});
|
|
22066
|
+
task.command("unblock <taskId>").description("flip a blocked task back to todo so the implement loop picks it up again").requiredOption("-s, --sprint <id>", "sprint id").action(async (rawTaskId, opts) => {
|
|
22067
|
+
const sprintId = SprintId.parse(opts.sprint);
|
|
22068
|
+
if (!sprintId.ok) {
|
|
22069
|
+
process.stderr.write(`error: invalid sprint id: ${sprintId.error.message}
|
|
22070
|
+
`);
|
|
22071
|
+
process.exit(1);
|
|
22072
|
+
return;
|
|
22073
|
+
}
|
|
22074
|
+
const taskId = TaskId.parse(rawTaskId);
|
|
22075
|
+
if (!taskId.ok) {
|
|
22076
|
+
process.stderr.write(`error: invalid task id: ${taskId.error.message}
|
|
22077
|
+
`);
|
|
22078
|
+
process.exit(1);
|
|
22079
|
+
return;
|
|
22080
|
+
}
|
|
22081
|
+
const { deps } = await bootstrapCli();
|
|
22082
|
+
const loaded = await deps.taskRepo.findById(sprintId.value, taskId.value);
|
|
22083
|
+
if (!loaded.ok) {
|
|
22084
|
+
process.stderr.write(`error: ${loaded.error.message}
|
|
22085
|
+
`);
|
|
22086
|
+
process.exit(1);
|
|
22087
|
+
return;
|
|
22088
|
+
}
|
|
22089
|
+
const result = await unblockTaskUseCase({
|
|
22090
|
+
task: loaded.value,
|
|
22091
|
+
sprintId: sprintId.value,
|
|
22092
|
+
taskRepo: deps.taskRepo,
|
|
22093
|
+
logger: deps.logger
|
|
22094
|
+
});
|
|
22095
|
+
if (!result.ok) {
|
|
22096
|
+
process.stderr.write(`error: ${result.error.message}
|
|
22097
|
+
`);
|
|
22098
|
+
process.exit(1);
|
|
22099
|
+
return;
|
|
22100
|
+
}
|
|
22101
|
+
process.stdout.write(`unblocked task '${result.value.name}' (${String(result.value.id)})
|
|
21737
22102
|
`);
|
|
21738
22103
|
});
|
|
21739
22104
|
};
|