ralphctl 0.7.1 → 0.7.3
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 +431 -271
- package/dist/manifest.json +1 -1
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/application/ui/tui/launch.ts
|
|
7
|
-
import
|
|
7
|
+
import React66 from "react";
|
|
8
8
|
|
|
9
9
|
// src/application/bootstrap/storage-paths.ts
|
|
10
10
|
import { promises as fs } from "fs";
|
|
@@ -2667,24 +2667,16 @@ var buildCodexArgs = (session, opts) => {
|
|
|
2667
2667
|
const perms = sandboxFor(session.permissions);
|
|
2668
2668
|
if (!perms.ok) return Result.error(perms.error);
|
|
2669
2669
|
const args = ["exec"];
|
|
2670
|
-
|
|
2670
|
+
const isResume = session.resume !== void 0;
|
|
2671
|
+
if (isResume) {
|
|
2671
2672
|
args.push("resume", String(session.resume));
|
|
2672
2673
|
}
|
|
2673
|
-
args.push(
|
|
2674
|
-
|
|
2675
|
-
"
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
"-m",
|
|
2680
|
-
session.model,
|
|
2681
|
-
"-C",
|
|
2682
|
-
String(session.cwd),
|
|
2683
|
-
"-s",
|
|
2684
|
-
perms.value.sandbox
|
|
2685
|
-
);
|
|
2686
|
-
for (const root of session.additionalRoots ?? []) {
|
|
2687
|
-
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
|
+
}
|
|
2688
2680
|
}
|
|
2689
2681
|
if (opts.reasoningEffort !== void 0) {
|
|
2690
2682
|
args.push("-c", `model_reasoning_effort=${opts.reasoningEffort}`);
|
|
@@ -2862,6 +2854,18 @@ var spawnAttempt2 = async (input) => {
|
|
|
2862
2854
|
const signals = parseHarnessSignals(body, IsoTimestamp.now());
|
|
2863
2855
|
const wrote = await writeJsonAtomic(String(session.signalsFile), signals);
|
|
2864
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
|
+
}
|
|
2865
2869
|
return {
|
|
2866
2870
|
kind: "success",
|
|
2867
2871
|
output: {
|
|
@@ -3958,11 +3962,7 @@ var defaultTemplatesDir = () => TEMPLATES_DIR;
|
|
|
3958
3962
|
var isNodeErrnoCode2 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
3959
3963
|
|
|
3960
3964
|
// src/integration/ai/readiness/claude/probe.ts
|
|
3961
|
-
import {
|
|
3962
|
-
import { basename, join as join7 } from "path";
|
|
3963
|
-
|
|
3964
|
-
// src/domain/value/kebab-case.ts
|
|
3965
|
-
var toKebabCase = (input) => input.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
3965
|
+
import { join as join8 } from "path";
|
|
3966
3966
|
|
|
3967
3967
|
// src/domain/value/error/probe-error.ts
|
|
3968
3968
|
var ProbeError = class extends Error {
|
|
@@ -3983,66 +3983,22 @@ var ProbeError = class extends Error {
|
|
|
3983
3983
|
}
|
|
3984
3984
|
};
|
|
3985
3985
|
|
|
3986
|
-
// src/integration/ai/readiness/_engine/
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
var presentState = (evaluatedAt, artifacts) => ({
|
|
3990
|
-
kind: "present",
|
|
3991
|
-
evaluatedAt,
|
|
3992
|
-
artifacts
|
|
3993
|
-
});
|
|
3986
|
+
// src/integration/ai/readiness/_engine/probe-fs.ts
|
|
3987
|
+
import { promises as fs8 } from "fs";
|
|
3988
|
+
import { basename, join as join7 } from "path";
|
|
3994
3989
|
|
|
3995
|
-
// src/
|
|
3996
|
-
var
|
|
3997
|
-
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;
|
|
3998
|
-
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, "");
|
|
3999
3992
|
|
|
4000
|
-
// src/integration/ai/readiness/
|
|
4001
|
-
var claudeProbe = {
|
|
4002
|
-
tool: "claude-code",
|
|
4003
|
-
async evaluate(repository, now) {
|
|
4004
|
-
const root = repository.path;
|
|
4005
|
-
const claudeMd = await probeFile(join7(root, "CLAUDE.md"));
|
|
4006
|
-
if (!claudeMd.ok) return Result.error(claudeMd.error);
|
|
4007
|
-
const agentsMd = await probeFile(join7(root, "AGENTS.md"));
|
|
4008
|
-
if (!agentsMd.ok) return Result.error(agentsMd.error);
|
|
4009
|
-
const settings = await probeFile(join7(root, ".claude/settings.json"));
|
|
4010
|
-
if (!settings.ok) return Result.error(settings.error);
|
|
4011
|
-
const settingsLocal = await probeFile(join7(root, ".claude/settings.local.json"));
|
|
4012
|
-
if (!settingsLocal.ok) return Result.error(settingsLocal.error);
|
|
4013
|
-
const mcpConfig = await probeFile(join7(root, ".mcp.json"));
|
|
4014
|
-
if (!mcpConfig.ok) return Result.error(mcpConfig.error);
|
|
4015
|
-
const skills = await probeNamedDirCollection(join7(root, ".claude/skills"), "SKILL.md");
|
|
4016
|
-
if (!skills.ok) return Result.error(skills.error);
|
|
4017
|
-
const commands = await probeNamedFileCollection(join7(root, ".claude/commands"));
|
|
4018
|
-
if (!commands.ok) return Result.error(commands.error);
|
|
4019
|
-
const agents = await probeNamedFileCollection(join7(root, ".claude/agents"));
|
|
4020
|
-
if (!agents.ok) return Result.error(agents.error);
|
|
4021
|
-
const hooks = await readHooks([settings.value, settingsLocal.value]);
|
|
4022
|
-
if (!hooks.ok) return Result.error(hooks.error);
|
|
4023
|
-
const artifacts = {
|
|
4024
|
-
tool: "claude-code",
|
|
4025
|
-
...claudeMd.value !== void 0 ? { claudeMd: claudeMd.value } : {},
|
|
4026
|
-
...agentsMd.value !== void 0 ? { agentsMd: agentsMd.value } : {},
|
|
4027
|
-
...settings.value !== void 0 ? { settings: settings.value } : {},
|
|
4028
|
-
...settingsLocal.value !== void 0 ? { settingsLocal: settingsLocal.value } : {},
|
|
4029
|
-
...mcpConfig.value !== void 0 ? { mcpConfig: mcpConfig.value } : {},
|
|
4030
|
-
skills: skills.value,
|
|
4031
|
-
commands: commands.value,
|
|
4032
|
-
agents: agents.value,
|
|
4033
|
-
hooks: hooks.value
|
|
4034
|
-
};
|
|
4035
|
-
return Result.ok(hasAnyClaudeArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
|
|
4036
|
-
}
|
|
4037
|
-
};
|
|
3993
|
+
// src/integration/ai/readiness/_engine/probe-fs.ts
|
|
4038
3994
|
var probeFile = async (path) => {
|
|
4039
3995
|
try {
|
|
4040
3996
|
const stat = await fs8.stat(path);
|
|
4041
3997
|
if (!stat.isFile()) return Result.ok(void 0);
|
|
4042
3998
|
return Result.ok({ path });
|
|
4043
3999
|
} catch (cause) {
|
|
4044
|
-
if (
|
|
4045
|
-
if (
|
|
4000
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
|
|
4001
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4046
4002
|
return Result.error(
|
|
4047
4003
|
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4048
4004
|
);
|
|
@@ -4050,23 +4006,6 @@ var probeFile = async (path) => {
|
|
|
4050
4006
|
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to stat ${path}`, path, cause }));
|
|
4051
4007
|
}
|
|
4052
4008
|
};
|
|
4053
|
-
var probeNamedFileCollection = async (dir) => {
|
|
4054
|
-
const entries = await listDir2(dir);
|
|
4055
|
-
if (!entries.ok) return Result.error(entries.error);
|
|
4056
|
-
const refs = [];
|
|
4057
|
-
for (const entry of entries.value) {
|
|
4058
|
-
if (!entry.endsWith(".md")) continue;
|
|
4059
|
-
const full = join7(dir, entry);
|
|
4060
|
-
const stat = await statSafely(full);
|
|
4061
|
-
if (!stat.ok) return Result.error(stat.error);
|
|
4062
|
-
if (stat.value === void 0 || !stat.value.isFile()) continue;
|
|
4063
|
-
const baseName = entry.slice(0, -".md".length);
|
|
4064
|
-
const slug = Slug.parse(toKebabCase(baseName));
|
|
4065
|
-
if (!slug.ok) continue;
|
|
4066
|
-
refs.push({ name: slug.value, path: full });
|
|
4067
|
-
}
|
|
4068
|
-
return Result.ok(refs);
|
|
4069
|
-
};
|
|
4070
4009
|
var probeNamedDirCollection = async (dir, childMarker) => {
|
|
4071
4010
|
const entries = await listDir2(dir);
|
|
4072
4011
|
if (!entries.ok) return Result.error(entries.error);
|
|
@@ -4086,12 +4025,29 @@ var probeNamedDirCollection = async (dir, childMarker) => {
|
|
|
4086
4025
|
}
|
|
4087
4026
|
return Result.ok(refs);
|
|
4088
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
|
+
};
|
|
4089
4045
|
var listDir2 = async (dir) => {
|
|
4090
4046
|
try {
|
|
4091
4047
|
return Result.ok(await fs8.readdir(dir));
|
|
4092
4048
|
} catch (cause) {
|
|
4093
|
-
if (
|
|
4094
|
-
if (
|
|
4049
|
+
if (isNodeErrnoCode(cause, "ENOENT") || isNodeErrnoCode(cause, "ENOTDIR")) return Result.ok([]);
|
|
4050
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4095
4051
|
return Result.error(
|
|
4096
4052
|
new ProbeError({ subCode: "fs-permission", message: `permission denied listing ${dir}`, path: dir, cause })
|
|
4097
4053
|
);
|
|
@@ -4103,8 +4059,8 @@ var statSafely = async (path) => {
|
|
|
4103
4059
|
try {
|
|
4104
4060
|
return Result.ok(await fs8.stat(path));
|
|
4105
4061
|
} catch (cause) {
|
|
4106
|
-
if (
|
|
4107
|
-
if (
|
|
4062
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(void 0);
|
|
4063
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4108
4064
|
return Result.error(
|
|
4109
4065
|
new ProbeError({ subCode: "fs-permission", message: `permission denied stat ${path}`, path, cause })
|
|
4110
4066
|
);
|
|
@@ -4112,6 +4068,73 @@ var statSafely = async (path) => {
|
|
|
4112
4068
|
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to stat ${path}`, path, cause }));
|
|
4113
4069
|
}
|
|
4114
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
|
+
};
|
|
4115
4138
|
var readHooks = async (settingsRefs) => {
|
|
4116
4139
|
const hooks = [];
|
|
4117
4140
|
for (const ref2 of settingsRefs) {
|
|
@@ -4131,19 +4154,6 @@ var readHooks = async (settingsRefs) => {
|
|
|
4131
4154
|
}
|
|
4132
4155
|
return Result.ok(hooks);
|
|
4133
4156
|
};
|
|
4134
|
-
var readFileSafely = async (path) => {
|
|
4135
|
-
try {
|
|
4136
|
-
return Result.ok(await fs8.readFile(path, "utf8"));
|
|
4137
|
-
} catch (cause) {
|
|
4138
|
-
if (isNodeErrnoCode3(cause, "ENOENT")) return Result.ok(void 0);
|
|
4139
|
-
if (isNodeErrnoCode3(cause, "EACCES")) {
|
|
4140
|
-
return Result.error(
|
|
4141
|
-
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4142
|
-
);
|
|
4143
|
-
}
|
|
4144
|
-
return Result.error(new ProbeError({ subCode: "fs-read", message: `failed to read ${path}`, path, cause }));
|
|
4145
|
-
}
|
|
4146
|
-
};
|
|
4147
4157
|
var extractHooks = (settings, sink) => {
|
|
4148
4158
|
if (typeof settings !== "object" || settings === null) return;
|
|
4149
4159
|
const hooks = settings.hooks;
|
|
@@ -4164,33 +4174,41 @@ var extractHooks = (settings, sink) => {
|
|
|
4164
4174
|
}
|
|
4165
4175
|
}
|
|
4166
4176
|
};
|
|
4167
|
-
var isNodeErrnoCode3 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
4168
4177
|
|
|
4169
4178
|
// src/integration/ai/readiness/codex/probe.ts
|
|
4179
|
+
import { join as join9 } from "path";
|
|
4170
4180
|
var codexProbe = {
|
|
4171
4181
|
tool: "codex",
|
|
4172
|
-
evaluate(repository, now) {
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
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));
|
|
4176
4194
|
}
|
|
4177
4195
|
};
|
|
4178
4196
|
|
|
4179
4197
|
// src/integration/ai/readiness/copilot/probe.ts
|
|
4180
4198
|
import { promises as fs9 } from "fs";
|
|
4181
|
-
import { join as
|
|
4199
|
+
import { join as join10 } from "path";
|
|
4182
4200
|
var copilotProbe = {
|
|
4183
4201
|
tool: "copilot",
|
|
4184
4202
|
async evaluate(repository, now) {
|
|
4185
|
-
const path =
|
|
4203
|
+
const path = join10(repository.path, ".github/copilot-instructions.md");
|
|
4186
4204
|
try {
|
|
4187
4205
|
const stat = await fs9.stat(path);
|
|
4188
4206
|
if (!stat.isFile()) return Result.ok(absentState(now));
|
|
4189
4207
|
const artifacts = { tool: "copilot", copilotInstructions: { path } };
|
|
4190
4208
|
return Result.ok(hasAnyCopilotArtifact(artifacts) ? presentState(now, artifacts) : absentState(now));
|
|
4191
4209
|
} catch (cause) {
|
|
4192
|
-
if (
|
|
4193
|
-
if (
|
|
4210
|
+
if (isNodeErrnoCode(cause, "ENOENT")) return Result.ok(absentState(now));
|
|
4211
|
+
if (isNodeErrnoCode(cause, "EACCES")) {
|
|
4194
4212
|
return Result.error(
|
|
4195
4213
|
new ProbeError({ subCode: "fs-permission", message: `permission denied reading ${path}`, path, cause })
|
|
4196
4214
|
);
|
|
@@ -4199,7 +4217,6 @@ var copilotProbe = {
|
|
|
4199
4217
|
}
|
|
4200
4218
|
}
|
|
4201
4219
|
};
|
|
4202
|
-
var isNodeErrnoCode4 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
4203
4220
|
|
|
4204
4221
|
// src/integration/observability/in-memory-event-bus.ts
|
|
4205
4222
|
var createInMemoryEventBus = () => {
|
|
@@ -4247,7 +4264,7 @@ var loggerForScope = (deps, scope) => {
|
|
|
4247
4264
|
};
|
|
4248
4265
|
|
|
4249
4266
|
// src/integration/version/npm-version-checker.ts
|
|
4250
|
-
import { join as
|
|
4267
|
+
import { join as join11 } from "path";
|
|
4251
4268
|
import { z as z15 } from "zod";
|
|
4252
4269
|
|
|
4253
4270
|
// src/business/version/version-check.ts
|
|
@@ -4289,7 +4306,7 @@ var buildVersionCheck = (current, latest, now) => ({
|
|
|
4289
4306
|
var DEFAULT_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
4290
4307
|
var DEFAULT_FETCH_TIMEOUT_MS = 3e3;
|
|
4291
4308
|
var RegistryPayloadSchema = z15.object({ version: z15.string() });
|
|
4292
|
-
var cachePath = (stateRoot) =>
|
|
4309
|
+
var cachePath = (stateRoot) => join11(String(stateRoot), "version-check.json");
|
|
4293
4310
|
var registryUrl = (packageName) => `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`;
|
|
4294
4311
|
var shouldSkip = (env) => env["NO_NETWORK"] !== void 0 || env["VITEST"] !== void 0;
|
|
4295
4312
|
var readCache = async (path) => {
|
|
@@ -4343,7 +4360,7 @@ var createNpmVersionChecker = (deps) => {
|
|
|
4343
4360
|
// package.json
|
|
4344
4361
|
var package_default = {
|
|
4345
4362
|
name: "ralphctl",
|
|
4346
|
-
version: "0.7.
|
|
4363
|
+
version: "0.7.3",
|
|
4347
4364
|
description: "Agent harness for long-running AI coding tasks \u2014 orchestrates Claude Code & GitHub Copilot across repositories",
|
|
4348
4365
|
homepage: "https://github.com/lukas-grigis/ralphctl",
|
|
4349
4366
|
type: "module",
|
|
@@ -4445,11 +4462,11 @@ var CLI_METADATA = {
|
|
|
4445
4462
|
// src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
|
|
4446
4463
|
import { existsSync } from "fs";
|
|
4447
4464
|
import { mkdir, rm, rmdir, writeFile } from "fs/promises";
|
|
4448
|
-
import { join as
|
|
4465
|
+
import { join as join13 } from "path";
|
|
4449
4466
|
|
|
4450
4467
|
// src/integration/io/git-exclude.ts
|
|
4451
4468
|
import { promises as fs10 } from "fs";
|
|
4452
|
-
import { isAbsolute as isAbsolute2, join as
|
|
4469
|
+
import { isAbsolute as isAbsolute2, join as join12 } from "path";
|
|
4453
4470
|
var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
|
|
4454
4471
|
const resolved = await resolveExcludePath(String(repoRoot));
|
|
4455
4472
|
if (resolved === void 0) return Result.ok(void 0);
|
|
@@ -4457,7 +4474,7 @@ var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
|
|
|
4457
4474
|
try {
|
|
4458
4475
|
existing = await fs10.readFile(resolved, "utf8");
|
|
4459
4476
|
} catch (cause) {
|
|
4460
|
-
if (
|
|
4477
|
+
if (isNodeErrnoCode3(cause, "ENOENT")) {
|
|
4461
4478
|
} else {
|
|
4462
4479
|
return Result.error(
|
|
4463
4480
|
new StorageError({
|
|
@@ -4478,16 +4495,16 @@ var ensureGitExcludeWildcard = async (repoRoot, pattern) => {
|
|
|
4478
4495
|
return writeTextAtomic(resolved, next);
|
|
4479
4496
|
};
|
|
4480
4497
|
var resolveExcludePath = async (repoRoot) => {
|
|
4481
|
-
const gitMarker =
|
|
4498
|
+
const gitMarker = join12(repoRoot, ".git");
|
|
4482
4499
|
let stat;
|
|
4483
4500
|
try {
|
|
4484
4501
|
stat = await fs10.stat(gitMarker);
|
|
4485
4502
|
} catch (cause) {
|
|
4486
|
-
if (
|
|
4503
|
+
if (isNodeErrnoCode3(cause, "ENOENT") || isNodeErrnoCode3(cause, "ENOTDIR")) return void 0;
|
|
4487
4504
|
throw cause;
|
|
4488
4505
|
}
|
|
4489
4506
|
if (stat.isDirectory()) {
|
|
4490
|
-
return
|
|
4507
|
+
return join12(gitMarker, "info", "exclude");
|
|
4491
4508
|
}
|
|
4492
4509
|
if (!stat.isFile()) return void 0;
|
|
4493
4510
|
let pointer;
|
|
@@ -4499,10 +4516,10 @@ var resolveExcludePath = async (repoRoot) => {
|
|
|
4499
4516
|
const match = /^gitdir:\s*(.+)\s*$/m.exec(pointer);
|
|
4500
4517
|
if (match === null) return void 0;
|
|
4501
4518
|
const gitdir = match[1].trim();
|
|
4502
|
-
const absoluteGitdir = isAbsolute2(gitdir) ? gitdir :
|
|
4503
|
-
return
|
|
4519
|
+
const absoluteGitdir = isAbsolute2(gitdir) ? gitdir : join12(repoRoot, gitdir);
|
|
4520
|
+
return join12(absoluteGitdir, "info", "exclude");
|
|
4504
4521
|
};
|
|
4505
|
-
var
|
|
4522
|
+
var isNodeErrnoCode3 = (cause, code) => typeof cause === "object" && cause !== null && cause.code === code;
|
|
4506
4523
|
|
|
4507
4524
|
// src/integration/ai/skills/_engine/filesystem-skills-adapter.ts
|
|
4508
4525
|
var renderSkill = (skill) => {
|
|
@@ -4525,7 +4542,7 @@ var tryRmdirIfEmpty = async (path) => {
|
|
|
4525
4542
|
var createFilesystemSkillsAdapter = (deps) => {
|
|
4526
4543
|
const installed = /* @__PURE__ */ new Map();
|
|
4527
4544
|
const excludeAttempted = /* @__PURE__ */ new Set();
|
|
4528
|
-
const skillsSubdir =
|
|
4545
|
+
const skillsSubdir = join13(deps.parentDir, "skills");
|
|
4529
4546
|
const excludePattern = `${skillsSubdir}/ralphctl-*`;
|
|
4530
4547
|
const pruneStale = () => {
|
|
4531
4548
|
for (const key of [...installed.keys()]) {
|
|
@@ -4535,14 +4552,14 @@ var createFilesystemSkillsAdapter = (deps) => {
|
|
|
4535
4552
|
return {
|
|
4536
4553
|
async install(sessionDir, skills) {
|
|
4537
4554
|
pruneStale();
|
|
4538
|
-
const skillsDir =
|
|
4555
|
+
const skillsDir = join13(String(sessionDir), skillsSubdir);
|
|
4539
4556
|
const tracked = installed.get(String(sessionDir)) ?? /* @__PURE__ */ new Set();
|
|
4540
4557
|
for (const skill of skills) {
|
|
4541
|
-
const dst =
|
|
4558
|
+
const dst = join13(skillsDir, skill.name);
|
|
4542
4559
|
if (existsSync(dst)) continue;
|
|
4543
4560
|
try {
|
|
4544
4561
|
await mkdir(dst, { recursive: true });
|
|
4545
|
-
await writeFile(
|
|
4562
|
+
await writeFile(join13(dst, "SKILL.md"), renderSkill(skill), "utf-8");
|
|
4546
4563
|
tracked.add(skill.name);
|
|
4547
4564
|
} catch (cause) {
|
|
4548
4565
|
if (tracked.size > 0) installed.set(String(sessionDir), tracked);
|
|
@@ -4573,10 +4590,10 @@ var createFilesystemSkillsAdapter = (deps) => {
|
|
|
4573
4590
|
const key = String(sessionDir);
|
|
4574
4591
|
const tracked = installed.get(key);
|
|
4575
4592
|
if (tracked === void 0 || tracked.size === 0) return Result.ok(void 0);
|
|
4576
|
-
const skillsDir =
|
|
4593
|
+
const skillsDir = join13(key, skillsSubdir);
|
|
4577
4594
|
try {
|
|
4578
4595
|
for (const id of tracked) {
|
|
4579
|
-
await rm(
|
|
4596
|
+
await rm(join13(skillsDir, id), { recursive: true, force: true });
|
|
4580
4597
|
}
|
|
4581
4598
|
installed.delete(key);
|
|
4582
4599
|
} catch (cause) {
|
|
@@ -4590,7 +4607,7 @@ var createFilesystemSkillsAdapter = (deps) => {
|
|
|
4590
4607
|
);
|
|
4591
4608
|
}
|
|
4592
4609
|
await tryRmdirIfEmpty(skillsDir);
|
|
4593
|
-
await tryRmdirIfEmpty(
|
|
4610
|
+
await tryRmdirIfEmpty(join13(key, deps.parentDir));
|
|
4594
4611
|
return Result.ok(void 0);
|
|
4595
4612
|
}
|
|
4596
4613
|
};
|
|
@@ -4652,7 +4669,7 @@ var createSkillsAdapter = (deps) => {
|
|
|
4652
4669
|
};
|
|
4653
4670
|
|
|
4654
4671
|
// src/integration/ai/skills/bundled/source.ts
|
|
4655
|
-
import { dirname as dirname7, join as
|
|
4672
|
+
import { dirname as dirname7, join as join14 } from "path";
|
|
4656
4673
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4657
4674
|
import { readFile } from "fs/promises";
|
|
4658
4675
|
|
|
@@ -4681,7 +4698,7 @@ var skillsForFlow = (flowId) => FLOW_SKILLS[flowId];
|
|
|
4681
4698
|
var defaultBundledRoot = (() => {
|
|
4682
4699
|
const here = dirname7(fileURLToPath2(import.meta.url));
|
|
4683
4700
|
const isBundled = import.meta.url.endsWith("/cli.mjs") || import.meta.url.endsWith("\\cli.mjs");
|
|
4684
|
-
return isBundled ?
|
|
4701
|
+
return isBundled ? join14(here, "skills") : here;
|
|
4685
4702
|
})();
|
|
4686
4703
|
var splitFrontmatter = (raw) => {
|
|
4687
4704
|
const trimmed = raw.replace(/^\uFEFF/u, "");
|
|
@@ -4709,7 +4726,7 @@ var parseSimpleYaml = (input) => {
|
|
|
4709
4726
|
return result;
|
|
4710
4727
|
};
|
|
4711
4728
|
var readSkill = async (root, name) => {
|
|
4712
|
-
const path =
|
|
4729
|
+
const path = join14(root, name, "SKILL.md");
|
|
4713
4730
|
let raw;
|
|
4714
4731
|
try {
|
|
4715
4732
|
raw = await readFile(path, "utf-8");
|
|
@@ -5202,7 +5219,7 @@ var getRunInTerminal = () => (fn) => ref.current(fn);
|
|
|
5202
5219
|
|
|
5203
5220
|
// src/application/ui/tui/App.tsx
|
|
5204
5221
|
import "react";
|
|
5205
|
-
import { Box as
|
|
5222
|
+
import { Box as Box50 } from "ink";
|
|
5206
5223
|
|
|
5207
5224
|
// src/application/ui/tui/runtime/deps-context.tsx
|
|
5208
5225
|
import { createContext, useContext } from "react";
|
|
@@ -5699,13 +5716,13 @@ var probeGitConfig = async (id, label, key, runCommand2, hint) => {
|
|
|
5699
5716
|
}
|
|
5700
5717
|
return { id, label, status: "warn", detail: `${key} not set`, hint, group: "vcs" };
|
|
5701
5718
|
};
|
|
5702
|
-
var probeCliAuth = async (id, label, binary, args, hint, runCommand2) => {
|
|
5719
|
+
var probeCliAuth = async (id, label, binary, args, hint, runCommand2, group = "vcs") => {
|
|
5703
5720
|
const r = await runCommand2(binary, args);
|
|
5704
5721
|
if (r.ok) {
|
|
5705
|
-
return { id, label, status: "pass", detail: "authenticated", group
|
|
5722
|
+
return { id, label, status: "pass", detail: "authenticated", group };
|
|
5706
5723
|
}
|
|
5707
5724
|
const detail = r.stderr.split("\n").find((line) => line.trim().length > 0)?.trim() ?? "not authenticated";
|
|
5708
|
-
return { id, label, status: "warn", detail, hint, group
|
|
5725
|
+
return { id, label, status: "warn", detail, hint, group };
|
|
5709
5726
|
};
|
|
5710
5727
|
var createDoctorFlow = (deps) => leaf("doctor", {
|
|
5711
5728
|
useCase: {
|
|
@@ -5817,6 +5834,7 @@ var createDoctorFlow = (deps) => leaf("doctor", {
|
|
|
5817
5834
|
}
|
|
5818
5835
|
const settings = await deps.settingsRepo.load();
|
|
5819
5836
|
const configuredProvider = settings.ok ? settings.value.ai.provider : void 0;
|
|
5837
|
+
let codexInstalled = false;
|
|
5820
5838
|
for (const provider of Object.keys(PROVIDER_BINARY)) {
|
|
5821
5839
|
const binary = PROVIDER_BINARY[provider];
|
|
5822
5840
|
const isConfigured = provider === configuredProvider;
|
|
@@ -5828,12 +5846,26 @@ var createDoctorFlow = (deps) => leaf("doctor", {
|
|
|
5828
5846
|
deps.commandExists,
|
|
5829
5847
|
`install the '${binary}' CLI and ensure it is on your PATH`
|
|
5830
5848
|
);
|
|
5849
|
+
if (provider === "openai-codex") codexInstalled = probe.status === "pass";
|
|
5831
5850
|
if (probe.status === "fail") {
|
|
5832
5851
|
probes.push({ ...probe, status: "warn" });
|
|
5833
5852
|
} else {
|
|
5834
5853
|
probes.push(probe);
|
|
5835
5854
|
}
|
|
5836
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
|
+
}
|
|
5837
5869
|
const projects = await deps.projectRepo.list();
|
|
5838
5870
|
probes.push({
|
|
5839
5871
|
id: "projects-list",
|
|
@@ -6465,10 +6497,17 @@ var spinnerGlyph = (frame) => glyphs.spinner[frame % glyphs.spinner.length] ?? "
|
|
|
6465
6497
|
|
|
6466
6498
|
// src/application/ui/tui/components/spinner.tsx
|
|
6467
6499
|
import { jsxs as jsxs6 } from "react/jsx-runtime";
|
|
6468
|
-
var Spinner = ({ label, color = inkColors.info }) => {
|
|
6469
|
-
const frame = useSpinnerFrame();
|
|
6500
|
+
var Spinner = ({ label, color = inkColors.info, active = true, dim }) => {
|
|
6501
|
+
const frame = useSpinnerFrame(active);
|
|
6502
|
+
const glyph = spinnerGlyph(frame);
|
|
6503
|
+
if (dim === true) {
|
|
6504
|
+
return /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
|
|
6505
|
+
glyph,
|
|
6506
|
+
label !== void 0 && label.length > 0 ? ` ${label}` : ""
|
|
6507
|
+
] });
|
|
6508
|
+
}
|
|
6470
6509
|
return /* @__PURE__ */ jsxs6(Text7, { color, children: [
|
|
6471
|
-
|
|
6510
|
+
glyph,
|
|
6472
6511
|
label !== void 0 && label.length > 0 ? ` ${label}` : ""
|
|
6473
6512
|
] });
|
|
6474
6513
|
};
|
|
@@ -9176,14 +9215,14 @@ var launchAddTickets = (ctx) => {
|
|
|
9176
9215
|
};
|
|
9177
9216
|
|
|
9178
9217
|
// src/application/ui/shared/launch/refine.ts
|
|
9179
|
-
import { join as
|
|
9218
|
+
import { join as join17 } from "path";
|
|
9180
9219
|
|
|
9181
9220
|
// src/application/flows/refine/flow.ts
|
|
9182
|
-
import { join as
|
|
9221
|
+
import { join as join16 } from "path";
|
|
9183
9222
|
|
|
9184
9223
|
// src/application/flows/_shared/build-unit.ts
|
|
9185
9224
|
import { promises as fs11 } from "fs";
|
|
9186
|
-
import { join as
|
|
9225
|
+
import { join as join15 } from "path";
|
|
9187
9226
|
var buildUnitLeaf = (opts) => leaf(opts.name, {
|
|
9188
9227
|
useCase: {
|
|
9189
9228
|
execute: async (input) => {
|
|
@@ -9204,7 +9243,7 @@ var buildUnitLeaf = (opts) => leaf(opts.name, {
|
|
|
9204
9243
|
return Result.ok(parsed.value);
|
|
9205
9244
|
}
|
|
9206
9245
|
},
|
|
9207
|
-
input: (ctx) => ({ path:
|
|
9246
|
+
input: (ctx) => ({ path: join15(String(opts.parent(ctx)), opts.slug(ctx)) }),
|
|
9208
9247
|
output: (ctx, root) => opts.write(ctx, root)
|
|
9209
9248
|
});
|
|
9210
9249
|
|
|
@@ -9666,8 +9705,8 @@ var createRefineFlow = (deps, opts) => {
|
|
|
9666
9705
|
parent: () => opts.refinementRoot,
|
|
9667
9706
|
slug: () => ticketSlug(ticket),
|
|
9668
9707
|
write: (ctx, root) => {
|
|
9669
|
-
const promptPath = AbsolutePath.parse(
|
|
9670
|
-
const outputPath = AbsolutePath.parse(
|
|
9708
|
+
const promptPath = AbsolutePath.parse(join16(String(root), "prompt.md"));
|
|
9709
|
+
const outputPath = AbsolutePath.parse(join16(String(root), "requirements.md"));
|
|
9671
9710
|
if (!promptPath.ok || !outputPath.ok) {
|
|
9672
9711
|
throw promptPath.ok ? outputPath.ok ? new Error("unreachable") : outputPath.error : promptPath.error;
|
|
9673
9712
|
}
|
|
@@ -9768,7 +9807,7 @@ var launchRefine = async (ctx) => {
|
|
|
9768
9807
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
9769
9808
|
const pending = snapshot.sprint.tickets.filter((t) => t.status === "pending");
|
|
9770
9809
|
const refinementRoot = AbsolutePath.parse(
|
|
9771
|
-
|
|
9810
|
+
join17(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "refinement")
|
|
9772
9811
|
);
|
|
9773
9812
|
if (!refinementRoot.ok) return { ok: false, reason: refinementRoot.error.message };
|
|
9774
9813
|
let defaultIssueOrigin = snapshot.project?.defaultIssueOrigin;
|
|
@@ -9841,10 +9880,10 @@ ${body.trim()}`;
|
|
|
9841
9880
|
};
|
|
9842
9881
|
|
|
9843
9882
|
// src/application/ui/shared/launch/plan.ts
|
|
9844
|
-
import { join as
|
|
9883
|
+
import { join as join19 } from "path";
|
|
9845
9884
|
|
|
9846
9885
|
// src/application/flows/plan/flow.ts
|
|
9847
|
-
import { join as
|
|
9886
|
+
import { join as join18 } from "path";
|
|
9848
9887
|
|
|
9849
9888
|
// src/application/flows/_shared/sprint/load-execution.ts
|
|
9850
9889
|
var loadSprintExecutionLeaf = (deps, name = "load-sprint-execution") => leaf(name, {
|
|
@@ -10585,8 +10624,8 @@ var createPlanFlow = (deps, opts) => {
|
|
|
10585
10624
|
parent: () => opts.planRoot,
|
|
10586
10625
|
slug: () => slug,
|
|
10587
10626
|
write: (ctx, root) => {
|
|
10588
|
-
const promptPath = AbsolutePath.parse(
|
|
10589
|
-
const outputPath = AbsolutePath.parse(
|
|
10627
|
+
const promptPath = AbsolutePath.parse(join18(String(root), "prompt.md"));
|
|
10628
|
+
const outputPath = AbsolutePath.parse(join18(String(root), "plan.json"));
|
|
10590
10629
|
if (!promptPath.ok) throw promptPath.error;
|
|
10591
10630
|
if (!outputPath.ok) throw outputPath.error;
|
|
10592
10631
|
return {
|
|
@@ -10676,7 +10715,7 @@ var launchPlan = (ctx) => {
|
|
|
10676
10715
|
if (!snapshot.project) return { ok: false, reason: "No project loaded." };
|
|
10677
10716
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
10678
10717
|
const planRoot = AbsolutePath.parse(
|
|
10679
|
-
|
|
10718
|
+
join19(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "plan")
|
|
10680
10719
|
);
|
|
10681
10720
|
if (!planRoot.ok) return { ok: false, reason: planRoot.error.message };
|
|
10682
10721
|
const reviewBeforeApprove = async (proposedTasks) => {
|
|
@@ -10730,7 +10769,7 @@ ${summary}`;
|
|
|
10730
10769
|
};
|
|
10731
10770
|
|
|
10732
10771
|
// src/application/ui/shared/launch/implement.ts
|
|
10733
|
-
import { join as
|
|
10772
|
+
import { join as join23 } from "path";
|
|
10734
10773
|
|
|
10735
10774
|
// src/application/chain/build/guard.ts
|
|
10736
10775
|
var guard = (name, predicate, body) => ({
|
|
@@ -11643,9 +11682,9 @@ var implementSession = (sandboxCwd, repoPath, prompt, model, signalsFile) => ({
|
|
|
11643
11682
|
});
|
|
11644
11683
|
|
|
11645
11684
|
// src/application/flows/implement/leaves/round-artifacts.ts
|
|
11646
|
-
import { join as
|
|
11685
|
+
import { join as join20 } from "path";
|
|
11647
11686
|
var nextRoundNum = async (workspaceRoot) => {
|
|
11648
|
-
const entries = await listDir(
|
|
11687
|
+
const entries = await listDir(join20(String(workspaceRoot), "rounds"));
|
|
11649
11688
|
if (!entries.ok) return 1;
|
|
11650
11689
|
let max = 0;
|
|
11651
11690
|
for (const name of entries.value) {
|
|
@@ -11654,12 +11693,12 @@ var nextRoundNum = async (workspaceRoot) => {
|
|
|
11654
11693
|
}
|
|
11655
11694
|
return max + 1;
|
|
11656
11695
|
};
|
|
11657
|
-
var roundSignalsPath = (workspaceRoot, round, role) =>
|
|
11658
|
-
var roundEvaluationRelativePath = (round) =>
|
|
11659
|
-
var roundDir = (workspaceRoot, round, role) =>
|
|
11696
|
+
var roundSignalsPath = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role, "signals.json");
|
|
11697
|
+
var roundEvaluationRelativePath = (round) => join20("rounds", String(round), "evaluator", "evaluation.md");
|
|
11698
|
+
var roundDir = (workspaceRoot, round, role) => join20(String(workspaceRoot), "rounds", String(round), role);
|
|
11660
11699
|
var writeEvaluatorRoundArtifacts = async (workspaceRoot, round, signals, logger) => {
|
|
11661
11700
|
const base = roundDir(workspaceRoot, round, "evaluator");
|
|
11662
|
-
const evaluation = await writeTextAtomic(
|
|
11701
|
+
const evaluation = await writeTextAtomic(join20(base, "evaluation.md"), renderEvaluation(findEvaluation2(signals)));
|
|
11663
11702
|
if (!evaluation.ok) {
|
|
11664
11703
|
logger?.warn("failed to write evaluator round artifact", { round, base, error: evaluation.error.message });
|
|
11665
11704
|
}
|
|
@@ -12783,7 +12822,7 @@ var startAttemptLeaf = (deps, taskId) => leaf(
|
|
|
12783
12822
|
|
|
12784
12823
|
// src/application/flows/implement/leaves/build-task-workspace.ts
|
|
12785
12824
|
import { promises as fs16 } from "fs";
|
|
12786
|
-
import { join as
|
|
12825
|
+
import { join as join21 } from "path";
|
|
12787
12826
|
var renderDoneCriteria = (task) => {
|
|
12788
12827
|
const header = `# Done criteria \u2014 ${task.name}
|
|
12789
12828
|
|
|
@@ -12816,7 +12855,7 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
|
|
|
12816
12855
|
useCase: {
|
|
12817
12856
|
execute: async (input) => {
|
|
12818
12857
|
const log = deps.logger.named("implement.workspace");
|
|
12819
|
-
const workspaceRoot =
|
|
12858
|
+
const workspaceRoot = join21(String(opts.sprintDir), "implement", String(input.task.id));
|
|
12820
12859
|
const prompt = await buildImplementPrompt(deps.templateLoader, {
|
|
12821
12860
|
task: input.task,
|
|
12822
12861
|
projectPath: String(opts.cwd),
|
|
@@ -12824,10 +12863,10 @@ var buildTaskWorkspaceLeaf = (deps, opts, taskId) => leaf(`build-task-workspace-
|
|
|
12824
12863
|
...opts.checkScript !== void 0 ? { checkScript: opts.checkScript } : {}
|
|
12825
12864
|
});
|
|
12826
12865
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
12827
|
-
const wrotePrompt = await writeOrError(
|
|
12866
|
+
const wrotePrompt = await writeOrError(join21(workspaceRoot, "prompt.md"), String(prompt.value));
|
|
12828
12867
|
if (!wrotePrompt.ok) return Result.error(wrotePrompt.error);
|
|
12829
12868
|
const wroteCriteria = await writeOrError(
|
|
12830
|
-
|
|
12869
|
+
join21(workspaceRoot, "done-criteria.md"),
|
|
12831
12870
|
renderDoneCriteria(input.task)
|
|
12832
12871
|
);
|
|
12833
12872
|
if (!wroteCriteria.ok) return Result.error(wroteCriteria.error);
|
|
@@ -12898,15 +12937,20 @@ var transitionSprintToReviewLeaf = (deps) => leaf("transition-sprint-to-review",
|
|
|
12898
12937
|
|
|
12899
12938
|
// src/integration/io/lock-paths.ts
|
|
12900
12939
|
import { createHash } from "crypto";
|
|
12901
|
-
import { join as
|
|
12940
|
+
import { join as join22 } from "path";
|
|
12902
12941
|
var repoLockFile = (locksRoot, worktreePath) => {
|
|
12903
12942
|
const hash = createHash("sha1").update(String(worktreePath)).digest("hex").slice(0, 16);
|
|
12904
|
-
return AbsolutePath.parse(
|
|
12943
|
+
return AbsolutePath.parse(join22(String(locksRoot), `repo-${hash}.lock`));
|
|
12905
12944
|
};
|
|
12906
12945
|
|
|
12907
12946
|
// src/application/flows/implement/leaves/with-repo-lock.ts
|
|
12908
12947
|
var withRepoLock = (opts, inner) => ({
|
|
12909
12948
|
name: `with-repo-lock(${inner.name})`,
|
|
12949
|
+
// Expose the wrapped chain through the composite-pattern `children` slot so `flattenLeaves`
|
|
12950
|
+
// walks into it when the TUI builds its planned-leaf list. Without this the wrapper looked
|
|
12951
|
+
// like an opaque single leaf and the Flow-steps panel rendered only "with-repo-lock(…)" —
|
|
12952
|
+
// never the real setup / per-task / teardown sequence inside the lock.
|
|
12953
|
+
children: [inner],
|
|
12910
12954
|
async execute(ctx, signal, onTrace) {
|
|
12911
12955
|
const lockPath = repoLockFile(opts.locksRoot, opts.worktreePath);
|
|
12912
12956
|
if (!lockPath.ok) {
|
|
@@ -13196,11 +13240,11 @@ var launchImplement = (ctx) => {
|
|
|
13196
13240
|
return a.status === "in_progress" ? -1 : 1;
|
|
13197
13241
|
});
|
|
13198
13242
|
if (todoTasks.length === 0) return { ok: false, reason: "No tasks to implement or resume." };
|
|
13199
|
-
const sprintDirPath = AbsolutePath.parse(
|
|
13243
|
+
const sprintDirPath = AbsolutePath.parse(join23(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id)));
|
|
13200
13244
|
if (!sprintDirPath.ok) return { ok: false, reason: sprintDirPath.error.message };
|
|
13201
|
-
const progressPath = AbsolutePath.parse(
|
|
13245
|
+
const progressPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "progress.md"));
|
|
13202
13246
|
if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
|
|
13203
|
-
const chainLogPath = AbsolutePath.parse(
|
|
13247
|
+
const chainLogPath = AbsolutePath.parse(join23(String(sprintDirPath.value), "chain.log"));
|
|
13204
13248
|
if (!chainLogPath.ok) return { ok: false, reason: chainLogPath.error.message };
|
|
13205
13249
|
const chainLog = startFileLogSink({ file: chainLogPath.value, bus: deps.app.eventBus });
|
|
13206
13250
|
const repositories = /* @__PURE__ */ new Map();
|
|
@@ -13265,7 +13309,7 @@ var launchImplement = (ctx) => {
|
|
|
13265
13309
|
};
|
|
13266
13310
|
|
|
13267
13311
|
// src/application/ui/shared/launch/review.ts
|
|
13268
|
-
import { join as
|
|
13312
|
+
import { join as join25 } from "path";
|
|
13269
13313
|
|
|
13270
13314
|
// src/application/flows/review/leaves/ensure-feedback-file.ts
|
|
13271
13315
|
import { promises as fs18 } from "fs";
|
|
@@ -13508,12 +13552,12 @@ var buildApplyFeedbackPrompt = async (deps, input) => buildPrompt(deps, applyFee
|
|
|
13508
13552
|
|
|
13509
13553
|
// src/integration/ai/signals/_engine/temp-signals-file.ts
|
|
13510
13554
|
import { tmpdir as tmpdir2 } from "os";
|
|
13511
|
-
import { join as
|
|
13555
|
+
import { join as join24 } from "path";
|
|
13512
13556
|
var counter = 0;
|
|
13513
13557
|
var allocSignalsTempPath = (label) => {
|
|
13514
13558
|
counter += 1;
|
|
13515
13559
|
const filename = `ralphctl-signals-${label}-${String(process.pid)}-${String(Date.now())}-${String(counter)}.json`;
|
|
13516
|
-
return AbsolutePath.parse(
|
|
13560
|
+
return AbsolutePath.parse(join24(tmpdir2(), filename));
|
|
13517
13561
|
};
|
|
13518
13562
|
var withSignalsTempPath = async (label, fn) => {
|
|
13519
13563
|
const path = allocSignalsTempPath(label);
|
|
@@ -13772,11 +13816,11 @@ var launchReview = (ctx) => {
|
|
|
13772
13816
|
if (!snapshot.sprint) return { ok: false, reason: "No sprint selected." };
|
|
13773
13817
|
if (!cwd) return { ok: false, reason: "No repository path resolvable from the project." };
|
|
13774
13818
|
const feedbackPath = AbsolutePath.parse(
|
|
13775
|
-
|
|
13819
|
+
join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "feedback.md")
|
|
13776
13820
|
);
|
|
13777
13821
|
if (!feedbackPath.ok) return { ok: false, reason: feedbackPath.error.message };
|
|
13778
13822
|
const progressPath = AbsolutePath.parse(
|
|
13779
|
-
|
|
13823
|
+
join25(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "progress.md")
|
|
13780
13824
|
);
|
|
13781
13825
|
if (!progressPath.ok) return { ok: false, reason: progressPath.error.message };
|
|
13782
13826
|
const checkScript = snapshot.project?.repositories.find((r) => r.checkScript !== void 0)?.checkScript;
|
|
@@ -14005,11 +14049,11 @@ var probeReadinessLeaf = (deps) => leaf("probe", {
|
|
|
14005
14049
|
import { promises as fs21 } from "fs";
|
|
14006
14050
|
|
|
14007
14051
|
// src/integration/ai/readiness/_engine/setup.ts
|
|
14008
|
-
import { join as
|
|
14052
|
+
import { join as join27 } from "path";
|
|
14009
14053
|
|
|
14010
14054
|
// src/integration/ai/runs/_engine/run-artifacts.ts
|
|
14011
14055
|
import { promises as fs20 } from "fs";
|
|
14012
|
-
import { join as
|
|
14056
|
+
import { join as join26 } from "path";
|
|
14013
14057
|
var BODY_PREVIEW_LIMIT = 800;
|
|
14014
14058
|
var buildRunDirName = () => {
|
|
14015
14059
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -14019,7 +14063,7 @@ var buildRunDirName = () => {
|
|
|
14019
14063
|
var readRunBodyPreview = async (runDir, options) => {
|
|
14020
14064
|
let raw;
|
|
14021
14065
|
try {
|
|
14022
|
-
raw = await fs20.readFile(
|
|
14066
|
+
raw = await fs20.readFile(join26(String(runDir), "body.txt"), "utf8");
|
|
14023
14067
|
} catch (cause) {
|
|
14024
14068
|
if (isErrnoException(cause) && cause.code === "ENOENT") return void 0;
|
|
14025
14069
|
const code = isErrnoException(cause) ? cause.code : "unknown";
|
|
@@ -14148,11 +14192,11 @@ var setupReadinessUseCase = async (deps, input) => {
|
|
|
14148
14192
|
...input.existingContextFile !== void 0 ? { existingContextFile: input.existingContextFile } : {}
|
|
14149
14193
|
});
|
|
14150
14194
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
14151
|
-
const runDir = AbsolutePath.parse(
|
|
14195
|
+
const runDir = AbsolutePath.parse(join27(String(deps.runsRoot), "readiness", buildRunDirName()));
|
|
14152
14196
|
if (!runDir.ok) return Result.error(runDir.error);
|
|
14153
|
-
const promptFile = AbsolutePath.parse(
|
|
14197
|
+
const promptFile = AbsolutePath.parse(join27(String(runDir.value), "prompt.md"));
|
|
14154
14198
|
if (!promptFile.ok) return Result.error(promptFile.error);
|
|
14155
|
-
const bodyFile = AbsolutePath.parse(
|
|
14199
|
+
const bodyFile = AbsolutePath.parse(join27(String(runDir.value), "body.txt"));
|
|
14156
14200
|
if (!bodyFile.ok) return Result.error(bodyFile.error);
|
|
14157
14201
|
const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
|
|
14158
14202
|
if (!promptWrote.ok) return Result.error(promptWrote.error);
|
|
@@ -14201,7 +14245,7 @@ var setupReadinessUseCase = async (deps, input) => {
|
|
|
14201
14245
|
const verifyScript = signals.value.find(
|
|
14202
14246
|
(s) => s.type === "verify-script"
|
|
14203
14247
|
)?.command;
|
|
14204
|
-
const targetPath = AbsolutePath.parse(
|
|
14248
|
+
const targetPath = AbsolutePath.parse(join27(String(input.repository.path), targetPathFor(input.tool)));
|
|
14205
14249
|
if (!targetPath.ok) return Result.error(targetPath.error);
|
|
14206
14250
|
log.info(`proposal ready for repo ${input.repository.name}`, {
|
|
14207
14251
|
repositoryId: String(input.repository.id),
|
|
@@ -14267,6 +14311,9 @@ var pickExistingContextPath = (tool, state) => {
|
|
|
14267
14311
|
if (tool === "copilot" && a.tool === "copilot") {
|
|
14268
14312
|
return a.copilotInstructions !== void 0 ? String(a.copilotInstructions.path) : void 0;
|
|
14269
14313
|
}
|
|
14314
|
+
if (tool === "codex" && a.tool === "codex") {
|
|
14315
|
+
return a.agentsMd !== void 0 ? String(a.agentsMd.path) : void 0;
|
|
14316
|
+
}
|
|
14270
14317
|
return void 0;
|
|
14271
14318
|
};
|
|
14272
14319
|
var proposeReadinessLeaf = (deps) => leaf("propose", {
|
|
@@ -14447,7 +14494,7 @@ var launchReadiness = (ctx) => {
|
|
|
14447
14494
|
};
|
|
14448
14495
|
|
|
14449
14496
|
// src/application/flows/detect-skills/leaves/propose.ts
|
|
14450
|
-
import { join as
|
|
14497
|
+
import { join as join28 } from "path";
|
|
14451
14498
|
|
|
14452
14499
|
// src/integration/ai/prompts/detect-skills/definition.ts
|
|
14453
14500
|
var detectSkillsPromptDef = {
|
|
@@ -14511,11 +14558,11 @@ var proposeUseCase = async (deps, input) => {
|
|
|
14511
14558
|
skillsConvention: deps.skillsAdapter.describeSkillsConvention()
|
|
14512
14559
|
});
|
|
14513
14560
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
14514
|
-
const runDir = AbsolutePath.parse(
|
|
14561
|
+
const runDir = AbsolutePath.parse(join28(String(deps.runsRoot), "detect-skills", buildRunDirName()));
|
|
14515
14562
|
if (!runDir.ok) return Result.error(runDir.error);
|
|
14516
|
-
const promptFile = AbsolutePath.parse(
|
|
14563
|
+
const promptFile = AbsolutePath.parse(join28(String(runDir.value), "prompt.md"));
|
|
14517
14564
|
if (!promptFile.ok) return Result.error(promptFile.error);
|
|
14518
|
-
const bodyFile = AbsolutePath.parse(
|
|
14565
|
+
const bodyFile = AbsolutePath.parse(join28(String(runDir.value), "body.txt"));
|
|
14519
14566
|
if (!bodyFile.ok) return Result.error(bodyFile.error);
|
|
14520
14567
|
const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
|
|
14521
14568
|
if (!promptWrote.ok) return Result.error(promptWrote.error);
|
|
@@ -15129,7 +15176,7 @@ var launchDetectSkills = (ctx) => {
|
|
|
15129
15176
|
};
|
|
15130
15177
|
|
|
15131
15178
|
// src/application/flows/detect-scripts/leaves/propose.ts
|
|
15132
|
-
import { join as
|
|
15179
|
+
import { join as join29 } from "path";
|
|
15133
15180
|
|
|
15134
15181
|
// src/integration/ai/prompts/detect-scripts/definition.ts
|
|
15135
15182
|
var detectScriptsPromptDef = {
|
|
@@ -15174,11 +15221,11 @@ var proposeUseCase2 = async (deps, input) => {
|
|
|
15174
15221
|
repositoryPath: String(input.repository.path)
|
|
15175
15222
|
});
|
|
15176
15223
|
if (!prompt.ok) return Result.error(prompt.error);
|
|
15177
|
-
const runDir = AbsolutePath.parse(
|
|
15224
|
+
const runDir = AbsolutePath.parse(join29(String(deps.runsRoot), "detect-scripts", buildRunDirName()));
|
|
15178
15225
|
if (!runDir.ok) return Result.error(runDir.error);
|
|
15179
|
-
const promptFile = AbsolutePath.parse(
|
|
15226
|
+
const promptFile = AbsolutePath.parse(join29(String(runDir.value), "prompt.md"));
|
|
15180
15227
|
if (!promptFile.ok) return Result.error(promptFile.error);
|
|
15181
|
-
const bodyFile = AbsolutePath.parse(
|
|
15228
|
+
const bodyFile = AbsolutePath.parse(join29(String(runDir.value), "body.txt"));
|
|
15182
15229
|
if (!bodyFile.ok) return Result.error(bodyFile.error);
|
|
15183
15230
|
const promptWrote = await writeTextAtomic(String(promptFile.value), String(prompt.value));
|
|
15184
15231
|
if (!promptWrote.ok) return Result.error(promptWrote.error);
|
|
@@ -15522,10 +15569,10 @@ var launchDetectScripts = (ctx) => {
|
|
|
15522
15569
|
};
|
|
15523
15570
|
|
|
15524
15571
|
// src/application/ui/shared/launch/ideate.ts
|
|
15525
|
-
import { join as
|
|
15572
|
+
import { join as join31 } from "path";
|
|
15526
15573
|
|
|
15527
15574
|
// src/application/flows/ideate/flow.ts
|
|
15528
|
-
import { join as
|
|
15575
|
+
import { join as join30 } from "path";
|
|
15529
15576
|
|
|
15530
15577
|
// src/integration/ai/prompts/ideate/definition.ts
|
|
15531
15578
|
var nonEmpty2 = (field) => (v) => v.trim().length === 0 ? Result.error(new ValidationError({ field, value: v, message: `${field} must not be empty` })) : Result.ok(v);
|
|
@@ -15762,8 +15809,8 @@ var createIdeateFlow = (deps, opts) => {
|
|
|
15762
15809
|
parent: () => opts.ideateRoot,
|
|
15763
15810
|
slug: () => slug,
|
|
15764
15811
|
write: (ctx, root) => {
|
|
15765
|
-
const promptPath = AbsolutePath.parse(
|
|
15766
|
-
const outputPath = AbsolutePath.parse(
|
|
15812
|
+
const promptPath = AbsolutePath.parse(join30(String(root), "prompt.md"));
|
|
15813
|
+
const outputPath = AbsolutePath.parse(join30(String(root), "ideate.json"));
|
|
15767
15814
|
if (!promptPath.ok) throw promptPath.error;
|
|
15768
15815
|
if (!outputPath.ok) throw outputPath.error;
|
|
15769
15816
|
return {
|
|
@@ -15827,7 +15874,7 @@ var launchIdeate = async (ctx) => {
|
|
|
15827
15874
|
const bodyAns = await deps.interactive.askText("Idea description (paste or type)");
|
|
15828
15875
|
if (!bodyAns.ok) return { ok: false, reason: bodyAns.error.message };
|
|
15829
15876
|
const ideateRoot = AbsolutePath.parse(
|
|
15830
|
-
|
|
15877
|
+
join31(String(deps.storage.dataRoot), "sprints", String(snapshot.sprint.id), "ideate")
|
|
15831
15878
|
);
|
|
15832
15879
|
if (!ideateRoot.ok) return { ok: false, reason: ideateRoot.error.message };
|
|
15833
15880
|
const element = createIdeateFlow(
|
|
@@ -17779,20 +17826,20 @@ import { Box as Box34, Text as Text36, useInput as useInput15 } from "ink";
|
|
|
17779
17826
|
import { useMemo as useMemo10 } from "react";
|
|
17780
17827
|
import { Box as Box30, Text as Text32 } from "ink";
|
|
17781
17828
|
import { jsx as jsx42, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
17782
|
-
var glyphFor = (status
|
|
17829
|
+
var glyphFor = (status) => {
|
|
17783
17830
|
switch (status) {
|
|
17784
17831
|
case "completed":
|
|
17785
|
-
return
|
|
17832
|
+
return { kind: "static", glyph: glyphs.phaseDone, color: inkColors.success };
|
|
17786
17833
|
case "failed":
|
|
17787
|
-
return
|
|
17834
|
+
return { kind: "static", glyph: glyphs.cross, color: inkColors.error };
|
|
17788
17835
|
case "aborted":
|
|
17789
|
-
return
|
|
17836
|
+
return { kind: "static", glyph: glyphs.warningGlyph, color: inkColors.warning };
|
|
17790
17837
|
case "skipped":
|
|
17791
|
-
return
|
|
17838
|
+
return { kind: "static", glyph: glyphs.phaseDisabled, color: inkColors.muted };
|
|
17792
17839
|
case "running":
|
|
17793
|
-
return
|
|
17840
|
+
return { kind: "spinner", color: inkColors.info };
|
|
17794
17841
|
case "pending":
|
|
17795
|
-
return
|
|
17842
|
+
return { kind: "static", glyph: glyphs.phasePending, color: inkColors.muted };
|
|
17796
17843
|
}
|
|
17797
17844
|
};
|
|
17798
17845
|
var trailingLabelFor = (status) => {
|
|
@@ -17842,7 +17889,6 @@ var StepTrace = ({
|
|
|
17842
17889
|
inFlightLabel,
|
|
17843
17890
|
plan
|
|
17844
17891
|
}) => {
|
|
17845
|
-
const frame = useSpinnerFrame(running);
|
|
17846
17892
|
const traceLastEntry = trace[trace.length - 1];
|
|
17847
17893
|
const merged = useMemo10(
|
|
17848
17894
|
() => plan !== void 0 ? mergePlanWithTrace(plan, trace, running) : traceToRows(trace),
|
|
@@ -17856,11 +17902,11 @@ var StepTrace = ({
|
|
|
17856
17902
|
const rows = runningIdx >= 0 ? filtered.slice(Math.max(0, runningIdx - Math.floor(maxRows / 2))).slice(0, maxRows) : filtered.slice(-maxRows);
|
|
17857
17903
|
return /* @__PURE__ */ jsxs31(Box30, { flexDirection: "column", children: [
|
|
17858
17904
|
rows.map((row, i) => {
|
|
17859
|
-
const
|
|
17905
|
+
const instruction = glyphFor(row.status);
|
|
17860
17906
|
const trailing = trailingLabelFor(row.status);
|
|
17861
17907
|
const dimRow = row.status === "pending";
|
|
17862
17908
|
return /* @__PURE__ */ jsxs31(Box30, { paddingX: spacing.indent, children: [
|
|
17863
|
-
/* @__PURE__ */ jsx42(Text32, { color, bold: true, children:
|
|
17909
|
+
instruction.kind === "spinner" ? /* @__PURE__ */ jsx42(Spinner, { active: running, color: instruction.color }) : /* @__PURE__ */ jsx42(Text32, { color: instruction.color, bold: true, children: instruction.glyph }),
|
|
17864
17910
|
/* @__PURE__ */ jsxs31(Text32, { dimColor: dimRow, children: [
|
|
17865
17911
|
" ",
|
|
17866
17912
|
row.name
|
|
@@ -17871,7 +17917,7 @@ var StepTrace = ({
|
|
|
17871
17917
|
" ",
|
|
17872
17918
|
fmtDuration(row.durationMs)
|
|
17873
17919
|
] }),
|
|
17874
|
-
trailing !== void 0 && /* @__PURE__ */ jsxs31(Text32, { color, children: [
|
|
17920
|
+
trailing !== void 0 && /* @__PURE__ */ jsxs31(Text32, { color: instruction.color, children: [
|
|
17875
17921
|
" ",
|
|
17876
17922
|
glyphs.emDash,
|
|
17877
17923
|
" ",
|
|
@@ -17886,7 +17932,7 @@ var StepTrace = ({
|
|
|
17886
17932
|
] }, `${row.name}-${String(i)}`);
|
|
17887
17933
|
}),
|
|
17888
17934
|
plan === void 0 && running && inFlightLabel !== void 0 && /* @__PURE__ */ jsxs31(Box30, { paddingX: spacing.indent, children: [
|
|
17889
|
-
/* @__PURE__ */ jsx42(
|
|
17935
|
+
/* @__PURE__ */ jsx42(Spinner, { active: running, color: inkColors.info }),
|
|
17890
17936
|
/* @__PURE__ */ jsxs31(Text32, { dimColor: true, children: [
|
|
17891
17937
|
" ",
|
|
17892
17938
|
inFlightLabel
|
|
@@ -17982,30 +18028,23 @@ var SignalLine = ({ signal }) => {
|
|
|
17982
18028
|
/* @__PURE__ */ jsx43(Text33, { bold: row.bold ?? false, children: truncate2(row.text, 80) })
|
|
17983
18029
|
] });
|
|
17984
18030
|
};
|
|
18031
|
+
var LEGEND_ENTRIES = [
|
|
18032
|
+
{ label: "change", color: inkColors.info, description: "file/code edit" },
|
|
18033
|
+
{ label: "learning", color: inkColors.highlight, description: "cross-task insight" },
|
|
18034
|
+
{ label: "decision", color: inkColors.highlight, description: "design choice" },
|
|
18035
|
+
{ label: "verified", color: inkColors.success, description: "task self-check passed" },
|
|
18036
|
+
{ label: "blocked", color: inkColors.error, description: "task self-blocked" },
|
|
18037
|
+
{ label: "commit", color: inkColors.info, description: "proposed commit message" }
|
|
18038
|
+
];
|
|
17985
18039
|
var SignalLegend = () => /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingX: spacing.indent, marginBottom: spacing.section, children: [
|
|
17986
|
-
/* @__PURE__ */
|
|
17987
|
-
|
|
17988
|
-
|
|
17989
|
-
/* @__PURE__ */
|
|
17990
|
-
|
|
17991
|
-
|
|
17992
|
-
|
|
17993
|
-
|
|
17994
|
-
" ",
|
|
17995
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.highlight, children: "decision" }),
|
|
17996
|
-
" = design choice"
|
|
17997
|
-
] }),
|
|
17998
|
-
/* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
|
|
17999
|
-
" ",
|
|
18000
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.success, children: "verified" }),
|
|
18001
|
-
" = task self-check passed",
|
|
18002
|
-
" ",
|
|
18003
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.error, children: "blocked" }),
|
|
18004
|
-
" = task self-blocked",
|
|
18005
|
-
" ",
|
|
18006
|
-
/* @__PURE__ */ jsx43(Text33, { color: inkColors.info, children: "commit" }),
|
|
18007
|
-
" = proposed commit message"
|
|
18008
|
-
] })
|
|
18040
|
+
/* @__PURE__ */ jsx43(Text33, { dimColor: true, bold: true, children: "legend" }),
|
|
18041
|
+
/* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: LEGEND_ENTRIES.map((entry) => /* @__PURE__ */ jsxs32(Box31, { children: [
|
|
18042
|
+
/* @__PURE__ */ jsx43(Text33, { color: entry.color, bold: true, children: padLabel2(entry.label) }),
|
|
18043
|
+
/* @__PURE__ */ jsxs32(Text33, { dimColor: true, children: [
|
|
18044
|
+
"= ",
|
|
18045
|
+
entry.description
|
|
18046
|
+
] })
|
|
18047
|
+
] }, entry.label)) })
|
|
18009
18048
|
] });
|
|
18010
18049
|
var EvaluationLine = ({ evaluation }) => {
|
|
18011
18050
|
const color = evaluation.status === "passed" ? inkColors.success : evaluation.status === "failed" ? inkColors.error : inkColors.warning;
|
|
@@ -18029,14 +18068,9 @@ var EvaluationLine = ({ evaluation }) => {
|
|
|
18029
18068
|
evaluation.dimensions.length > 0 && /* @__PURE__ */ jsx43(Box31, { paddingLeft: 6, children: /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: evaluation.dimensions.map((d) => `${d.dimension}: ${String(d.score)}/5 ${d.passed ? glyphs.check : glyphs.cross}`).join(` ${glyphs.bullet} `) }) })
|
|
18030
18069
|
] });
|
|
18031
18070
|
};
|
|
18032
|
-
var SubStepLine = ({
|
|
18033
|
-
sub,
|
|
18034
|
-
running,
|
|
18035
|
-
spinner
|
|
18036
|
-
}) => {
|
|
18071
|
+
var SubStepLine = ({ sub, running }) => {
|
|
18037
18072
|
const presentation = SUB_STEP_PRESENTATION[sub.status];
|
|
18038
18073
|
const glyph = running && sub.status === "completed" ? presentation.glyph : presentation.glyph;
|
|
18039
|
-
void spinner;
|
|
18040
18074
|
return /* @__PURE__ */ jsxs32(Box31, { children: [
|
|
18041
18075
|
/* @__PURE__ */ jsxs32(Text33, { color: presentation.color, bold: true, children: [
|
|
18042
18076
|
glyphs.activityArrow,
|
|
@@ -18065,15 +18099,20 @@ var TaskBlock = ({
|
|
|
18065
18099
|
task,
|
|
18066
18100
|
running,
|
|
18067
18101
|
display,
|
|
18068
|
-
maxSignals
|
|
18102
|
+
maxSignals,
|
|
18103
|
+
maxSubSteps,
|
|
18104
|
+
maxEvaluations
|
|
18069
18105
|
}) => {
|
|
18070
18106
|
const presentation = STATUS_PRESENTATION[task.status];
|
|
18071
|
-
const
|
|
18072
|
-
const glyph = task.status === "running" ? spinnerGlyph(frame) : presentation.glyph;
|
|
18107
|
+
const isSpinning = task.status === "running";
|
|
18073
18108
|
const signalRows = task.signals.slice(-maxSignals);
|
|
18109
|
+
const subStepRows = task.subSteps.slice(-maxSubSteps);
|
|
18110
|
+
const subStepElided = task.subSteps.length - subStepRows.length;
|
|
18111
|
+
const evalRows = task.evaluations.slice(-maxEvaluations);
|
|
18112
|
+
const evalElided = task.evaluations.length - evalRows.length;
|
|
18074
18113
|
return /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", marginBottom: spacing.section, children: [
|
|
18075
18114
|
/* @__PURE__ */ jsxs32(Box31, { children: [
|
|
18076
|
-
/* @__PURE__ */ jsx43(Text33, { color: presentation.color, bold: true, children: glyph }),
|
|
18115
|
+
isSpinning ? /* @__PURE__ */ jsx43(Spinner, { active: running, color: presentation.color }) : /* @__PURE__ */ jsx43(Text33, { color: presentation.color, bold: true, children: presentation.glyph }),
|
|
18077
18116
|
/* @__PURE__ */ jsxs32(Text33, { bold: true, children: [
|
|
18078
18117
|
" ",
|
|
18079
18118
|
display
|
|
@@ -18099,8 +18138,14 @@ var TaskBlock = ({
|
|
|
18099
18138
|
] })
|
|
18100
18139
|
] }),
|
|
18101
18140
|
task.errorMessage !== void 0 && /* @__PURE__ */ jsx43(Box31, { paddingLeft: 2, children: /* @__PURE__ */ jsx43(Text33, { color: inkColors.error, children: task.errorMessage }) }),
|
|
18102
|
-
|
|
18103
|
-
|
|
18141
|
+
subStepRows.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingLeft: 2, children: [
|
|
18142
|
+
subStepElided > 0 && /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: `\u2026 ${String(subStepElided)} earlier sub-steps` }),
|
|
18143
|
+
subStepRows.map((s, i) => /* @__PURE__ */ jsx43(SubStepLine, { sub: s, running }, `${task.id}-sub-${String(i)}`))
|
|
18144
|
+
] }),
|
|
18145
|
+
evalRows.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: [
|
|
18146
|
+
evalElided > 0 && /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: `\u2026 ${String(evalElided)} earlier evaluations` }),
|
|
18147
|
+
evalRows.map((e, i) => /* @__PURE__ */ jsx43(EvaluationLine, { evaluation: e }, `${task.id}-eval-${String(i)}`))
|
|
18148
|
+
] }),
|
|
18104
18149
|
signalRows.length > 0 && /* @__PURE__ */ jsxs32(Box31, { flexDirection: "column", paddingLeft: 2, marginTop: 1, children: [
|
|
18105
18150
|
/* @__PURE__ */ jsx43(Text33, { dimColor: true, children: "signals" }),
|
|
18106
18151
|
/* @__PURE__ */ jsx43(Box31, { flexDirection: "column", paddingLeft: 2, children: signalRows.map((s, i) => /* @__PURE__ */ jsx43(SignalLine, { signal: s }, `${task.id}-sig-${String(i)}`)) })
|
|
@@ -18126,7 +18171,9 @@ var TasksPanel = ({
|
|
|
18126
18171
|
running,
|
|
18127
18172
|
nameById,
|
|
18128
18173
|
maxSignalsPerTask = 8,
|
|
18129
|
-
maxOrphanSignals = 6
|
|
18174
|
+
maxOrphanSignals = 6,
|
|
18175
|
+
maxSubStepsPerTask = 12,
|
|
18176
|
+
maxEvaluationsPerTask = 6
|
|
18130
18177
|
}) => {
|
|
18131
18178
|
if (bucketed.tasks.length === 0 && bucketed.orphanSignals.length === 0) {
|
|
18132
18179
|
return /* @__PURE__ */ jsx43(Box31, { paddingX: spacing.indent, children: /* @__PURE__ */ jsx43(Text33, { dimColor: true, children: "(no tasks yet)" }) });
|
|
@@ -18136,7 +18183,18 @@ var TasksPanel = ({
|
|
|
18136
18183
|
/* @__PURE__ */ jsx43(OrphanSignals, { signals: bucketed.orphanSignals, max: maxOrphanSignals }),
|
|
18137
18184
|
bucketed.tasks.map((task) => {
|
|
18138
18185
|
const display = nameById?.get(task.id) ?? `${task.id.slice(0, 8)}\u2026`;
|
|
18139
|
-
return /* @__PURE__ */ jsx43(
|
|
18186
|
+
return /* @__PURE__ */ jsx43(
|
|
18187
|
+
TaskBlock,
|
|
18188
|
+
{
|
|
18189
|
+
task,
|
|
18190
|
+
running,
|
|
18191
|
+
display,
|
|
18192
|
+
maxSignals: maxSignalsPerTask,
|
|
18193
|
+
maxSubSteps: maxSubStepsPerTask,
|
|
18194
|
+
maxEvaluations: maxEvaluationsPerTask
|
|
18195
|
+
},
|
|
18196
|
+
task.id
|
|
18197
|
+
);
|
|
18140
18198
|
})
|
|
18141
18199
|
] });
|
|
18142
18200
|
};
|
|
@@ -18447,7 +18505,6 @@ var ExecuteView = () => {
|
|
|
18447
18505
|
filter: (e) => "chainId" in e && e.chainId === sessionId2,
|
|
18448
18506
|
limit: 2e3
|
|
18449
18507
|
});
|
|
18450
|
-
const frame = useSpinnerFrame(session?.descriptor.status === "running");
|
|
18451
18508
|
const term = useTerminalSize();
|
|
18452
18509
|
const isRunning = session?.descriptor.status === "running";
|
|
18453
18510
|
useViewHints(
|
|
@@ -18540,10 +18597,7 @@ var ExecuteView = () => {
|
|
|
18540
18597
|
String(tasksTotal)
|
|
18541
18598
|
] })
|
|
18542
18599
|
] }),
|
|
18543
|
-
isRunning && /* @__PURE__ */ jsx46(Box34, { marginLeft: 2, children: /* @__PURE__ */
|
|
18544
|
-
spinnerGlyph(frame),
|
|
18545
|
-
" live"
|
|
18546
|
-
] }) })
|
|
18600
|
+
isRunning && /* @__PURE__ */ jsx46(Box34, { marginLeft: 2, children: /* @__PURE__ */ jsx46(Spinner, { active: isRunning, color: inkColors.info, label: "live" }) })
|
|
18547
18601
|
] }),
|
|
18548
18602
|
currentTask !== void 0 && currentTaskName !== void 0 && /* @__PURE__ */ jsxs35(Box34, { children: [
|
|
18549
18603
|
/* @__PURE__ */ jsxs35(Text36, { dimColor: true, children: [
|
|
@@ -18582,7 +18636,7 @@ var ExecuteView = () => {
|
|
|
18582
18636
|
] })
|
|
18583
18637
|
] })
|
|
18584
18638
|
] }) });
|
|
18585
|
-
const outerFlowFilter = (name) => !isPerTaskLeaf(name);
|
|
18639
|
+
const outerFlowFilter = (name) => !isPerTaskLeaf(name) && !name.startsWith("with-repo-lock(");
|
|
18586
18640
|
const flowStepsPanel = /* @__PURE__ */ jsx46(
|
|
18587
18641
|
StepTrace,
|
|
18588
18642
|
{
|
|
@@ -19314,7 +19368,7 @@ var ProbeRow = ({ probe }) => /* @__PURE__ */ jsxs39(Box38, { flexDirection: "co
|
|
|
19314
19368
|
// src/application/ui/tui/views/export-context-view.tsx
|
|
19315
19369
|
import { useCallback as useCallback7, useEffect as useEffect25, useState as useState31 } from "react";
|
|
19316
19370
|
import { Box as Box39, Text as Text41, useInput as useInput20 } from "ink";
|
|
19317
|
-
import { join as
|
|
19371
|
+
import { join as join32 } from "path";
|
|
19318
19372
|
|
|
19319
19373
|
// src/business/sprint/views/context-md.ts
|
|
19320
19374
|
var renderSprintContextMarkdown = (input) => {
|
|
@@ -19446,7 +19500,7 @@ var ExportContextView = () => {
|
|
|
19446
19500
|
return;
|
|
19447
19501
|
}
|
|
19448
19502
|
const outputPath = AbsolutePath.parse(
|
|
19449
|
-
|
|
19503
|
+
join32(String(storage2.dataRoot), "sprints", String(selection.sprintId), "context.md")
|
|
19450
19504
|
);
|
|
19451
19505
|
if (!outputPath.ok) {
|
|
19452
19506
|
setRun({ kind: "error", message: outputPath.error.message });
|
|
@@ -19504,7 +19558,7 @@ var ExportContextView = () => {
|
|
|
19504
19558
|
// src/application/ui/tui/views/export-requirements-view.tsx
|
|
19505
19559
|
import { useCallback as useCallback8, useEffect as useEffect26, useState as useState32 } from "react";
|
|
19506
19560
|
import { Box as Box40, Text as Text42, useInput as useInput21 } from "ink";
|
|
19507
|
-
import { join as
|
|
19561
|
+
import { join as join33 } from "path";
|
|
19508
19562
|
|
|
19509
19563
|
// src/business/sprint/views/requirements-md.ts
|
|
19510
19564
|
var renderSprintRequirementsMarkdown = (sprint) => {
|
|
@@ -19569,7 +19623,7 @@ var ExportRequirementsView = () => {
|
|
|
19569
19623
|
return;
|
|
19570
19624
|
}
|
|
19571
19625
|
const outputPath = AbsolutePath.parse(
|
|
19572
|
-
|
|
19626
|
+
join33(String(storage2.dataRoot), "sprints", String(selection.sprintId), "requirements.md")
|
|
19573
19627
|
);
|
|
19574
19628
|
if (!outputPath.ok) {
|
|
19575
19629
|
setRun({ kind: "error", message: outputPath.error.message });
|
|
@@ -19904,12 +19958,12 @@ var WelcomeView = () => {
|
|
|
19904
19958
|
import { useEffect as useEffect29, useState as useState36 } from "react";
|
|
19905
19959
|
import { Box as Box44, Text as Text46 } from "ink";
|
|
19906
19960
|
import { homedir as osHomedir2 } from "os";
|
|
19907
|
-
import { basename as basename3, join as
|
|
19961
|
+
import { basename as basename3, join as join35 } from "path";
|
|
19908
19962
|
|
|
19909
19963
|
// src/application/ui/tui/prompts/path-picker-prompt.tsx
|
|
19910
19964
|
import { useEffect as useEffect28, useState as useState35 } from "react";
|
|
19911
19965
|
import { promises as fs24 } from "fs";
|
|
19912
|
-
import { dirname as dirname10, join as
|
|
19966
|
+
import { dirname as dirname10, join as join34 } from "path";
|
|
19913
19967
|
import { homedir } from "os";
|
|
19914
19968
|
import { Box as Box43, Text as Text45, useInput as useInput23 } from "ink";
|
|
19915
19969
|
import { jsx as jsx55, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
@@ -19917,7 +19971,7 @@ var VISIBLE_ROWS2 = 12;
|
|
|
19917
19971
|
var clamp5 = (n, min, max) => Math.max(min, Math.min(max, n));
|
|
19918
19972
|
var expandHome = (input) => {
|
|
19919
19973
|
if (input === "~") return homedir();
|
|
19920
|
-
if (input.startsWith("~/")) return
|
|
19974
|
+
if (input.startsWith("~/")) return join34(homedir(), input.slice(2));
|
|
19921
19975
|
return input;
|
|
19922
19976
|
};
|
|
19923
19977
|
var PathPickerPrompt = ({
|
|
@@ -20005,7 +20059,7 @@ var PathPickerPrompt = ({
|
|
|
20005
20059
|
onSubmit(cwd);
|
|
20006
20060
|
return;
|
|
20007
20061
|
}
|
|
20008
|
-
setCwd(
|
|
20062
|
+
setCwd(join34(cwd, row.entry.name));
|
|
20009
20063
|
setCursor(1);
|
|
20010
20064
|
}
|
|
20011
20065
|
},
|
|
@@ -20087,7 +20141,7 @@ var labelFor = (row) => {
|
|
|
20087
20141
|
import { jsx as jsx56, jsxs as jsxs45 } from "react/jsx-runtime";
|
|
20088
20142
|
var expandHome2 = (input) => {
|
|
20089
20143
|
if (input === "~") return osHomedir2();
|
|
20090
|
-
if (input.startsWith("~/")) return
|
|
20144
|
+
if (input.startsWith("~/")) return join35(osHomedir2(), input.slice(2));
|
|
20091
20145
|
return input;
|
|
20092
20146
|
};
|
|
20093
20147
|
var backStep = (step) => {
|
|
@@ -20303,11 +20357,11 @@ var StepView = ({ step, onChange, onCancel, onSubmit }) => {
|
|
|
20303
20357
|
import { useEffect as useEffect30, useState as useState37 } from "react";
|
|
20304
20358
|
import { Box as Box45, Text as Text47 } from "ink";
|
|
20305
20359
|
import { homedir as osHomedir3 } from "os";
|
|
20306
|
-
import { basename as basename4, join as
|
|
20360
|
+
import { basename as basename4, join as join36 } from "path";
|
|
20307
20361
|
import { jsx as jsx57, jsxs as jsxs46 } from "react/jsx-runtime";
|
|
20308
20362
|
var expandHome3 = (input) => {
|
|
20309
20363
|
if (input === "~") return osHomedir3();
|
|
20310
|
-
if (input.startsWith("~/")) return
|
|
20364
|
+
if (input.startsWith("~/")) return join36(osHomedir3(), input.slice(2));
|
|
20311
20365
|
return input;
|
|
20312
20366
|
};
|
|
20313
20367
|
var backStep2 = (step) => {
|
|
@@ -21087,8 +21141,40 @@ var useGlobalKeys = (opts = {}) => {
|
|
|
21087
21141
|
});
|
|
21088
21142
|
};
|
|
21089
21143
|
|
|
21144
|
+
// src/application/ui/tui/components/memory-pressure-banner.tsx
|
|
21145
|
+
import { useEffect as useEffect34, useState as useState41 } from "react";
|
|
21146
|
+
import { Box as Box49, Text as Text51 } from "ink";
|
|
21147
|
+
import { jsxs as jsxs50 } from "react/jsx-runtime";
|
|
21148
|
+
var formatMb = (bytes) => `${(bytes / 1048576).toFixed(0)} MB`;
|
|
21149
|
+
var formatPercent = (ratio) => `${Math.round(ratio * 100)}%`;
|
|
21150
|
+
var MemoryPressureBanner = () => {
|
|
21151
|
+
const deps = useDeps();
|
|
21152
|
+
const [latest, setLatest] = useState41(void 0);
|
|
21153
|
+
useEffect34(() => {
|
|
21154
|
+
const unsub = deps.eventBus.subscribe((event) => {
|
|
21155
|
+
if (event.type === "memory-pressure") setLatest(event);
|
|
21156
|
+
});
|
|
21157
|
+
return unsub;
|
|
21158
|
+
}, [deps.eventBus]);
|
|
21159
|
+
if (latest === void 0 || latest.severity === "recovered") return null;
|
|
21160
|
+
const tone = latest.severity === "critical" ? inkColors.error : inkColors.warning;
|
|
21161
|
+
const headline = latest.severity === "critical" ? `memory critical \u2014 ${formatPercent(latest.ratio)} of heap; auto-cleared in-memory buffers` : `memory pressure \u2014 ${formatPercent(latest.ratio)} of heap used; consider aborting and restarting`;
|
|
21162
|
+
const detail = `(${formatMb(latest.heapUsed)} / ${formatMb(latest.heapLimit)})`;
|
|
21163
|
+
return /* @__PURE__ */ jsxs50(Box49, { paddingX: spacing.indent, flexDirection: "row", children: [
|
|
21164
|
+
/* @__PURE__ */ jsxs50(Text51, { bold: true, color: tone, children: [
|
|
21165
|
+
glyphs.warningGlyph,
|
|
21166
|
+
" ",
|
|
21167
|
+
headline
|
|
21168
|
+
] }),
|
|
21169
|
+
/* @__PURE__ */ jsxs50(Text51, { dimColor: true, children: [
|
|
21170
|
+
" ",
|
|
21171
|
+
detail
|
|
21172
|
+
] })
|
|
21173
|
+
] });
|
|
21174
|
+
};
|
|
21175
|
+
|
|
21090
21176
|
// src/application/ui/tui/App.tsx
|
|
21091
|
-
import { jsx as jsx62 } from "react/jsx-runtime";
|
|
21177
|
+
import { jsx as jsx62, jsxs as jsxs51 } from "react/jsx-runtime";
|
|
21092
21178
|
var App = ({
|
|
21093
21179
|
deps,
|
|
21094
21180
|
storage: storage2,
|
|
@@ -21111,7 +21197,10 @@ var Layout = ({ children }) => {
|
|
|
21111
21197
|
const ui = useUiState();
|
|
21112
21198
|
const { rows } = useTerminalSize();
|
|
21113
21199
|
useGlobalKeys({ disabled: ui.promptActive });
|
|
21114
|
-
return /* @__PURE__ */
|
|
21200
|
+
return /* @__PURE__ */ jsxs51(Box50, { flexDirection: "column", height: rows, children: [
|
|
21201
|
+
/* @__PURE__ */ jsx62(MemoryPressureBanner, {}),
|
|
21202
|
+
children
|
|
21203
|
+
] });
|
|
21115
21204
|
};
|
|
21116
21205
|
|
|
21117
21206
|
// src/application/ui/tui/launch-routing.ts
|
|
@@ -21138,10 +21227,10 @@ var resolveInitialState = ({
|
|
|
21138
21227
|
|
|
21139
21228
|
// src/integration/persistence/selection/last-selection-store.ts
|
|
21140
21229
|
import { promises as fs25 } from "fs";
|
|
21141
|
-
import { join as
|
|
21230
|
+
import { join as join37 } from "path";
|
|
21142
21231
|
var FILE_NAME = "last-selection.json";
|
|
21143
21232
|
var createLastSelectionStore = (stateRoot) => {
|
|
21144
|
-
const path =
|
|
21233
|
+
const path = join37(String(stateRoot), FILE_NAME);
|
|
21145
21234
|
return {
|
|
21146
21235
|
async read() {
|
|
21147
21236
|
try {
|
|
@@ -21195,6 +21284,69 @@ var createLogLevelGate = (initial) => {
|
|
|
21195
21284
|
};
|
|
21196
21285
|
};
|
|
21197
21286
|
|
|
21287
|
+
// src/integration/observability/heap-watchdog.ts
|
|
21288
|
+
import * as v8 from "v8";
|
|
21289
|
+
var MIN_INTERVAL_MS = 1e3;
|
|
21290
|
+
var DEFAULT_INTERVAL_MS = 1e4;
|
|
21291
|
+
var DEFAULT_WARNING_RATIO = 0.8;
|
|
21292
|
+
var DEFAULT_CRITICAL_RATIO = 0.95;
|
|
21293
|
+
var defaultReadHeap = () => {
|
|
21294
|
+
const stats = v8.getHeapStatistics();
|
|
21295
|
+
return { heapUsed: stats.used_heap_size, heapLimit: stats.heap_size_limit };
|
|
21296
|
+
};
|
|
21297
|
+
var classify = (ratio, warning, critical) => {
|
|
21298
|
+
if (ratio >= critical) return "critical";
|
|
21299
|
+
if (ratio >= warning) return "warning";
|
|
21300
|
+
return "ok";
|
|
21301
|
+
};
|
|
21302
|
+
var startHeapWatchdog = (deps) => {
|
|
21303
|
+
const intervalMs = Math.max(MIN_INTERVAL_MS, deps.intervalMs ?? DEFAULT_INTERVAL_MS);
|
|
21304
|
+
const warning = deps.warningRatio ?? DEFAULT_WARNING_RATIO;
|
|
21305
|
+
const critical = deps.criticalRatio ?? DEFAULT_CRITICAL_RATIO;
|
|
21306
|
+
const clock = deps.clock ?? IsoTimestamp.now;
|
|
21307
|
+
const readHeap = deps.readHeap ?? defaultReadHeap;
|
|
21308
|
+
let stopped = false;
|
|
21309
|
+
let previousBand = "ok";
|
|
21310
|
+
const emit = (severity, reading, ratio) => {
|
|
21311
|
+
deps.eventBus.publish({
|
|
21312
|
+
type: "memory-pressure",
|
|
21313
|
+
severity,
|
|
21314
|
+
ratio,
|
|
21315
|
+
heapUsed: reading.heapUsed,
|
|
21316
|
+
heapLimit: reading.heapLimit,
|
|
21317
|
+
at: clock()
|
|
21318
|
+
});
|
|
21319
|
+
};
|
|
21320
|
+
const sample = () => {
|
|
21321
|
+
if (stopped) return;
|
|
21322
|
+
const reading = readHeap();
|
|
21323
|
+
const ratio = reading.heapLimit > 0 ? reading.heapUsed / reading.heapLimit : 0;
|
|
21324
|
+
const band = classify(ratio, warning, critical);
|
|
21325
|
+
if (band === previousBand) return;
|
|
21326
|
+
if (band === "warning") emit("warning", reading, ratio);
|
|
21327
|
+
else if (band === "critical") {
|
|
21328
|
+
emit("critical", reading, ratio);
|
|
21329
|
+
try {
|
|
21330
|
+
deps.onCritical?.();
|
|
21331
|
+
} catch (err) {
|
|
21332
|
+
console.warn("[heap-watchdog] onCritical threw:", err);
|
|
21333
|
+
}
|
|
21334
|
+
} else {
|
|
21335
|
+
emit("recovered", reading, ratio);
|
|
21336
|
+
}
|
|
21337
|
+
previousBand = band;
|
|
21338
|
+
};
|
|
21339
|
+
const handle = setInterval(sample, intervalMs);
|
|
21340
|
+
handle.unref?.();
|
|
21341
|
+
return {
|
|
21342
|
+
stop() {
|
|
21343
|
+
if (stopped) return;
|
|
21344
|
+
stopped = true;
|
|
21345
|
+
clearInterval(handle);
|
|
21346
|
+
}
|
|
21347
|
+
};
|
|
21348
|
+
};
|
|
21349
|
+
|
|
21198
21350
|
// src/application/ui/tui/launch.ts
|
|
21199
21351
|
var bootstrap = async () => {
|
|
21200
21352
|
const paths = resolveStoragePaths();
|
|
@@ -21218,6 +21370,13 @@ var bootstrap = async () => {
|
|
|
21218
21370
|
const unsubLogForward = deps.eventBus.subscribe((event) => {
|
|
21219
21371
|
if (event.type === "log" && passesLogLevel(event.level, logLevelGate.get())) logBus.emit(event);
|
|
21220
21372
|
});
|
|
21373
|
+
const heapWatchdog = startHeapWatchdog({
|
|
21374
|
+
eventBus: deps.eventBus,
|
|
21375
|
+
onCritical: () => {
|
|
21376
|
+
harnessBus.clear();
|
|
21377
|
+
logBus.clear();
|
|
21378
|
+
}
|
|
21379
|
+
});
|
|
21221
21380
|
const sessions = createSessionManager();
|
|
21222
21381
|
const queue = createPromptQueue();
|
|
21223
21382
|
void createInkInteractivePrompt(queue);
|
|
@@ -21254,6 +21413,7 @@ var bootstrap = async () => {
|
|
|
21254
21413
|
drain: () => {
|
|
21255
21414
|
queue.drain(new Error("TUI shutting down"));
|
|
21256
21415
|
unsubLogForward();
|
|
21416
|
+
heapWatchdog.stop();
|
|
21257
21417
|
}
|
|
21258
21418
|
};
|
|
21259
21419
|
};
|
|
@@ -21268,7 +21428,7 @@ var launchTui = async () => {
|
|
|
21268
21428
|
process.exitCode = 1;
|
|
21269
21429
|
return;
|
|
21270
21430
|
}
|
|
21271
|
-
const appElement =
|
|
21431
|
+
const appElement = React66.createElement(App, booted.app);
|
|
21272
21432
|
const host = createInkHost({ appElement });
|
|
21273
21433
|
setRunInTerminal(host.runInTerminal);
|
|
21274
21434
|
try {
|