krenk 0.1.4 → 0.1.6
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/index.js +342 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,8 +18,8 @@ var ContextManager = class {
|
|
|
18
18
|
krenkDir;
|
|
19
19
|
historyDir;
|
|
20
20
|
runId;
|
|
21
|
-
constructor(projectDir) {
|
|
22
|
-
this.runId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
21
|
+
constructor(projectDir, existingRunId) {
|
|
22
|
+
this.runId = existingRunId || (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
23
23
|
this.krenkDir = path.join(projectDir, ".krenk");
|
|
24
24
|
this.historyDir = path.join(this.krenkDir, "history", this.runId);
|
|
25
25
|
fs.mkdirSync(this.krenkDir, { recursive: true });
|
|
@@ -104,6 +104,52 @@ ${content}
|
|
|
104
104
|
getRunId() {
|
|
105
105
|
return this.runId;
|
|
106
106
|
}
|
|
107
|
+
/** Load all <role>.md files from a history dir into memory */
|
|
108
|
+
async loadFromHistory(runId) {
|
|
109
|
+
const histDir = path.join(this.krenkDir, "history", runId);
|
|
110
|
+
try {
|
|
111
|
+
const files = await fs.promises.readdir(histDir);
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
if (file.endsWith(".md")) {
|
|
114
|
+
const role = file.replace(/\.md$/, "");
|
|
115
|
+
const content = await fs.promises.readFile(
|
|
116
|
+
path.join(histDir, file),
|
|
117
|
+
"utf-8"
|
|
118
|
+
);
|
|
119
|
+
this.memory.set(role, content);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Save state.json to both .krenk/ and .krenk/history/<runId>/ */
|
|
126
|
+
async saveStateToHistory(state) {
|
|
127
|
+
const json = JSON.stringify(state, null, 2);
|
|
128
|
+
await Promise.all([
|
|
129
|
+
fs.promises.writeFile(
|
|
130
|
+
path.join(this.krenkDir, "state.json"),
|
|
131
|
+
json,
|
|
132
|
+
"utf-8"
|
|
133
|
+
),
|
|
134
|
+
fs.promises.writeFile(
|
|
135
|
+
path.join(this.historyDir, "state.json"),
|
|
136
|
+
json,
|
|
137
|
+
"utf-8"
|
|
138
|
+
)
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
/** Read state.json from a specific history dir */
|
|
142
|
+
static async loadRunState(projectDir, runId) {
|
|
143
|
+
try {
|
|
144
|
+
const data = await fs.promises.readFile(
|
|
145
|
+
path.join(projectDir, ".krenk", "history", runId, "state.json"),
|
|
146
|
+
"utf-8"
|
|
147
|
+
);
|
|
148
|
+
return JSON.parse(data);
|
|
149
|
+
} catch {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
107
153
|
};
|
|
108
154
|
|
|
109
155
|
// src/agents/spawner.ts
|
|
@@ -902,9 +948,9 @@ import { EventEmitter as EventEmitter2 } from "events";
|
|
|
902
948
|
import { execSync } from "child_process";
|
|
903
949
|
var DEFAULT_LIMITS = {
|
|
904
950
|
maxMemoryMB: 512,
|
|
905
|
-
maxTimeSeconds:
|
|
906
|
-
hungWarningSeconds:
|
|
907
|
-
hungKillSeconds:
|
|
951
|
+
maxTimeSeconds: 900,
|
|
952
|
+
hungWarningSeconds: 180,
|
|
953
|
+
hungKillSeconds: 480,
|
|
908
954
|
pollIntervalMs: 5e3
|
|
909
955
|
};
|
|
910
956
|
var ProcessSupervisor = class extends EventEmitter2 {
|
|
@@ -1800,7 +1846,7 @@ var OrchestrationEngine = class extends EventEmitter5 {
|
|
|
1800
1846
|
constructor(opts) {
|
|
1801
1847
|
super();
|
|
1802
1848
|
this.opts = opts;
|
|
1803
|
-
this.context = new ContextManager(opts.cwd);
|
|
1849
|
+
this.context = new ContextManager(opts.cwd, opts.resumeRunId);
|
|
1804
1850
|
this.scheduler = new Scheduler(opts.maxParallel);
|
|
1805
1851
|
this.registry = new AgentRegistry();
|
|
1806
1852
|
this.supervisor = new ProcessSupervisor(opts.supervisorLimits);
|
|
@@ -1846,8 +1892,32 @@ var OrchestrationEngine = class extends EventEmitter5 {
|
|
|
1846
1892
|
this._startTime = Date.now();
|
|
1847
1893
|
let stageCount = 0;
|
|
1848
1894
|
let plan = null;
|
|
1895
|
+
const completedStages = [];
|
|
1896
|
+
const resumeCompleted = new Set(this.opts.resumeCompletedStages || []);
|
|
1897
|
+
const isResume = resumeCompleted.size > 0;
|
|
1898
|
+
if (isResume && this.opts.resumeRunId) {
|
|
1899
|
+
await this.context.loadFromHistory(this.opts.resumeRunId);
|
|
1900
|
+
const strategistOutput = this.context.get("strategist");
|
|
1901
|
+
if (strategistOutput) {
|
|
1902
|
+
plan = parsePlan(strategistOutput);
|
|
1903
|
+
this.brain.setPlan(plan);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
const saveProgress = async (status) => {
|
|
1907
|
+
const state = {
|
|
1908
|
+
prompt: userPrompt,
|
|
1909
|
+
completedStages,
|
|
1910
|
+
skipStages: this.opts.skipStages,
|
|
1911
|
+
status,
|
|
1912
|
+
runId: this.context.getRunId(),
|
|
1913
|
+
stageCount,
|
|
1914
|
+
duration: Math.round((Date.now() - this._startTime) / 1e3),
|
|
1915
|
+
planAssignments: plan ? Array.from(plan.assignments.keys()) : []
|
|
1916
|
+
};
|
|
1917
|
+
await this.context.saveStateToHistory(state);
|
|
1918
|
+
};
|
|
1849
1919
|
try {
|
|
1850
|
-
if (!this.shouldSkip("analyzing")) {
|
|
1920
|
+
if (!this.shouldSkip("analyzing") && !resumeCompleted.has("analyzing")) {
|
|
1851
1921
|
this.setStage("analyzing");
|
|
1852
1922
|
const result = await this.runDirectedAgent(
|
|
1853
1923
|
"analyst",
|
|
@@ -1857,8 +1927,10 @@ ${userPrompt}`
|
|
|
1857
1927
|
);
|
|
1858
1928
|
await this.context.save("analyst", result.output);
|
|
1859
1929
|
stageCount++;
|
|
1930
|
+
completedStages.push("analyzing");
|
|
1931
|
+
await saveProgress("running");
|
|
1860
1932
|
}
|
|
1861
|
-
if (!this.shouldSkip("planning")) {
|
|
1933
|
+
if (!this.shouldSkip("planning") && !resumeCompleted.has("planning")) {
|
|
1862
1934
|
this.setStage("planning");
|
|
1863
1935
|
const analysisContext = this.context.get("analyst");
|
|
1864
1936
|
const planPrompt = analysisContext ? `${userPrompt}
|
|
@@ -1868,6 +1940,7 @@ ${analysisContext}` : userPrompt;
|
|
|
1868
1940
|
const planResult = await this.runDirectedAgent("strategist", planPrompt);
|
|
1869
1941
|
await this.context.save("strategist", planResult.output);
|
|
1870
1942
|
stageCount++;
|
|
1943
|
+
completedStages.push("planning");
|
|
1871
1944
|
plan = parsePlan(planResult.output);
|
|
1872
1945
|
this.brain.setPlan(plan);
|
|
1873
1946
|
logger.info(
|
|
@@ -1877,53 +1950,65 @@ ${analysisContext}` : userPrompt;
|
|
|
1877
1950
|
assignments: Array.from(plan.assignments.keys()),
|
|
1878
1951
|
modules: plan.modules.length
|
|
1879
1952
|
});
|
|
1953
|
+
await saveProgress("running");
|
|
1880
1954
|
}
|
|
1881
|
-
if (!this.shouldSkip("designing") && this.needsDesign()) {
|
|
1955
|
+
if (!this.shouldSkip("designing") && !resumeCompleted.has("designing") && this.needsDesign()) {
|
|
1882
1956
|
this.setStage("designing");
|
|
1883
1957
|
const prompt = plan ? getAgentTask(plan, "designer", userPrompt) : userPrompt;
|
|
1884
1958
|
const result = await this.runDirectedAgent("designer", prompt);
|
|
1885
1959
|
await this.context.save("designer", result.output);
|
|
1886
1960
|
stageCount++;
|
|
1961
|
+
completedStages.push("designing");
|
|
1962
|
+
await saveProgress("running");
|
|
1887
1963
|
}
|
|
1888
|
-
if (!this.shouldSkip("architecting")) {
|
|
1964
|
+
if (!this.shouldSkip("architecting") && !resumeCompleted.has("architecting")) {
|
|
1889
1965
|
this.setStage("architecting");
|
|
1890
1966
|
const prompt = plan ? getAgentTask(plan, "architect", userPrompt) : userPrompt;
|
|
1891
1967
|
const result = await this.runDirectedAgent("architect", prompt);
|
|
1892
1968
|
await this.context.save("architect", result.output);
|
|
1893
1969
|
stageCount++;
|
|
1970
|
+
completedStages.push("architecting");
|
|
1894
1971
|
if (plan) {
|
|
1895
1972
|
const archModules = this.extractModules(result.output);
|
|
1896
1973
|
if (archModules.length > 0) {
|
|
1897
1974
|
plan.modules = archModules;
|
|
1898
1975
|
}
|
|
1899
1976
|
}
|
|
1977
|
+
await saveProgress("running");
|
|
1900
1978
|
}
|
|
1901
|
-
if (!this.shouldSkip("coding")) {
|
|
1979
|
+
if (!this.shouldSkip("coding") && !resumeCompleted.has("coding")) {
|
|
1902
1980
|
this.setStage("coding");
|
|
1903
1981
|
const prompt = plan ? getAgentTask(plan, "builder", userPrompt) : userPrompt;
|
|
1904
1982
|
await this.runCodingStage(prompt, plan);
|
|
1905
1983
|
stageCount++;
|
|
1984
|
+
completedStages.push("coding");
|
|
1985
|
+
await saveProgress("running");
|
|
1906
1986
|
}
|
|
1907
|
-
if (!this.shouldSkip("qa-planning")) {
|
|
1987
|
+
if (!this.shouldSkip("qa-planning") && !resumeCompleted.has("qa-planning")) {
|
|
1908
1988
|
this.setStage("qa-planning");
|
|
1909
1989
|
const prompt = plan ? getAgentTask(plan, "qa", "Create a comprehensive test strategy and detailed test plans.") : "Create a comprehensive test strategy and detailed test plans for the codebase.";
|
|
1910
1990
|
const result = await this.runDirectedAgent("qa", prompt);
|
|
1911
1991
|
await this.context.save("qa", result.output);
|
|
1912
1992
|
stageCount++;
|
|
1993
|
+
completedStages.push("qa-planning");
|
|
1994
|
+
await saveProgress("running");
|
|
1913
1995
|
}
|
|
1914
|
-
if (!this.shouldSkip("testing")) {
|
|
1996
|
+
if (!this.shouldSkip("testing") && !resumeCompleted.has("testing")) {
|
|
1915
1997
|
this.setStage("testing");
|
|
1916
1998
|
const prompt = plan ? getAgentTask(plan, "guardian", "Write and run comprehensive tests.") : "Analyze the codebase and write comprehensive tests. Run them and report results.";
|
|
1917
1999
|
const result = await this.runDirectedAgent("guardian", prompt);
|
|
1918
2000
|
await this.context.save("guardian", result.output);
|
|
1919
2001
|
stageCount++;
|
|
2002
|
+
completedStages.push("testing");
|
|
2003
|
+
await saveProgress("running");
|
|
1920
2004
|
}
|
|
1921
|
-
if (!this.shouldSkip("reviewing")) {
|
|
2005
|
+
if (!this.shouldSkip("reviewing") && !resumeCompleted.has("reviewing")) {
|
|
1922
2006
|
this.setStage("reviewing");
|
|
1923
2007
|
const prompt = plan ? getAgentTask(plan, "sentinel", "Review all code for bugs, security issues, and quality.") : "Review all code in this project for bugs, security issues, and quality.";
|
|
1924
2008
|
const review = await this.runDirectedAgent("sentinel", prompt);
|
|
1925
2009
|
await this.context.save("sentinel", review.output);
|
|
1926
2010
|
stageCount++;
|
|
2011
|
+
completedStages.push("reviewing");
|
|
1927
2012
|
if (review.output.includes("NEEDS_REVISION") && this._revisionCount < this.MAX_REVISIONS) {
|
|
1928
2013
|
this._revisionCount++;
|
|
1929
2014
|
logger.info(`Revision ${this._revisionCount}/${this.MAX_REVISIONS} - fixing review issues`);
|
|
@@ -1936,13 +2021,15 @@ ${review.output}`
|
|
|
1936
2021
|
await this.context.save("builder", fix.output);
|
|
1937
2022
|
stageCount++;
|
|
1938
2023
|
}
|
|
2024
|
+
await saveProgress("running");
|
|
1939
2025
|
}
|
|
1940
|
-
if (!this.shouldSkip("securing")) {
|
|
2026
|
+
if (!this.shouldSkip("securing") && !resumeCompleted.has("securing")) {
|
|
1941
2027
|
this.setStage("securing");
|
|
1942
2028
|
const prompt = plan ? getAgentTask(plan, "security", "Perform a thorough security audit.") : "Perform a thorough security audit of this project.";
|
|
1943
2029
|
const secAudit = await this.runDirectedAgent("security", prompt);
|
|
1944
2030
|
await this.context.save("security", secAudit.output);
|
|
1945
2031
|
stageCount++;
|
|
2032
|
+
completedStages.push("securing");
|
|
1946
2033
|
if (secAudit.output.includes("NEEDS_REVISION") && this._revisionCount < this.MAX_REVISIONS) {
|
|
1947
2034
|
this._revisionCount++;
|
|
1948
2035
|
logger.info(`Security revision ${this._revisionCount}/${this.MAX_REVISIONS}`);
|
|
@@ -1955,36 +2042,37 @@ ${secAudit.output}`
|
|
|
1955
2042
|
await this.context.save("builder", fix.output);
|
|
1956
2043
|
stageCount++;
|
|
1957
2044
|
}
|
|
2045
|
+
await saveProgress("running");
|
|
1958
2046
|
}
|
|
1959
|
-
if (!this.shouldSkip("documenting")) {
|
|
2047
|
+
if (!this.shouldSkip("documenting") && !resumeCompleted.has("documenting")) {
|
|
1960
2048
|
this.setStage("documenting");
|
|
1961
2049
|
const prompt = plan ? getAgentTask(plan, "scribe", "Write comprehensive documentation.") : "Write comprehensive documentation for this project.";
|
|
1962
2050
|
const result = await this.runDirectedAgent("scribe", prompt);
|
|
1963
2051
|
await this.context.save("scribe", result.output);
|
|
1964
2052
|
stageCount++;
|
|
2053
|
+
completedStages.push("documenting");
|
|
2054
|
+
await saveProgress("running");
|
|
1965
2055
|
}
|
|
1966
|
-
if (!this.shouldSkip("deploying")) {
|
|
2056
|
+
if (!this.shouldSkip("deploying") && !resumeCompleted.has("deploying")) {
|
|
1967
2057
|
this.setStage("deploying");
|
|
1968
2058
|
const prompt = plan ? getAgentTask(plan, "devops", "Set up CI/CD, Docker, and deployment.") : "Set up CI/CD pipeline, Docker configuration, and deployment setup.";
|
|
1969
2059
|
const result = await this.runDirectedAgent("devops", prompt);
|
|
1970
2060
|
await this.context.save("devops", result.output);
|
|
1971
2061
|
stageCount++;
|
|
2062
|
+
completedStages.push("deploying");
|
|
2063
|
+
await saveProgress("running");
|
|
1972
2064
|
}
|
|
1973
2065
|
this.setStage("complete");
|
|
1974
2066
|
this.supervisor.stop();
|
|
1975
2067
|
const duration = Math.round((Date.now() - this._startTime) / 1e3);
|
|
1976
|
-
await
|
|
1977
|
-
stage: "complete",
|
|
1978
|
-
stageCount,
|
|
1979
|
-
duration,
|
|
1980
|
-
runId: this.context.getRunId(),
|
|
1981
|
-
planAssignments: plan ? Array.from(plan.assignments.keys()) : []
|
|
1982
|
-
});
|
|
2068
|
+
await saveProgress("complete");
|
|
1983
2069
|
return { success: true, stages: stageCount, duration };
|
|
1984
2070
|
} catch (error) {
|
|
1985
2071
|
const msg = error instanceof Error ? error.message : String(error);
|
|
1986
2072
|
logger.error(`Engine failed: ${msg}`);
|
|
1987
2073
|
this.emit("error", error);
|
|
2074
|
+
await saveProgress("failed").catch(() => {
|
|
2075
|
+
});
|
|
1988
2076
|
return {
|
|
1989
2077
|
success: false,
|
|
1990
2078
|
stages: stageCount,
|
|
@@ -2785,6 +2873,15 @@ var TerminalRenderer = class {
|
|
|
2785
2873
|
}
|
|
2786
2874
|
this.spinners.delete(role);
|
|
2787
2875
|
}
|
|
2876
|
+
if (result.success && result.output) {
|
|
2877
|
+
const summary = this.extractSummary(result.output);
|
|
2878
|
+
if (summary.length > 0) {
|
|
2879
|
+
console.log(chalk2.dim(" Summary:"));
|
|
2880
|
+
for (const line of summary) {
|
|
2881
|
+
console.log(chalk2.dim(` ${line}`));
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
}
|
|
2788
2885
|
if (!role.includes("-")) {
|
|
2789
2886
|
this.completedStages++;
|
|
2790
2887
|
this.printProgress();
|
|
@@ -2912,6 +3009,32 @@ var TerminalRenderer = class {
|
|
|
2912
3009
|
this.phaseTimers.delete(role);
|
|
2913
3010
|
}
|
|
2914
3011
|
}
|
|
3012
|
+
/**
|
|
3013
|
+
* Extract a short summary from agent output.
|
|
3014
|
+
* Looks for headings, bullet points, or key lines to surface.
|
|
3015
|
+
*/
|
|
3016
|
+
extractSummary(output) {
|
|
3017
|
+
const lines = output.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
3018
|
+
const summary = [];
|
|
3019
|
+
for (const line of lines) {
|
|
3020
|
+
if (summary.length >= 5) break;
|
|
3021
|
+
if (/^#{1,3}\s+/.test(line)) {
|
|
3022
|
+
const clean = line.replace(/^#+\s*/, "").slice(0, 80);
|
|
3023
|
+
if (clean && !summary.includes(clean)) {
|
|
3024
|
+
summary.push(clean);
|
|
3025
|
+
}
|
|
3026
|
+
continue;
|
|
3027
|
+
}
|
|
3028
|
+
if (/^[-*]\s+/.test(line) && summary.length < 5) {
|
|
3029
|
+
const clean = line.replace(/^[-*]\s+/, "").slice(0, 80);
|
|
3030
|
+
if (clean) summary.push(clean);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
if (summary.length === 0 && lines.length > 0) {
|
|
3034
|
+
summary.push(lines[0].slice(0, 100));
|
|
3035
|
+
}
|
|
3036
|
+
return summary;
|
|
3037
|
+
}
|
|
2915
3038
|
printProgress() {
|
|
2916
3039
|
const total = this.totalStages;
|
|
2917
3040
|
const done = this.completedStages;
|
|
@@ -2937,17 +3060,17 @@ var DEFAULT_CONFIG = {
|
|
|
2937
3060
|
workflow: ["plan", "design", "architect", "code", "test", "review", "docs"],
|
|
2938
3061
|
skipStages: [],
|
|
2939
3062
|
agents: {
|
|
2940
|
-
analyst: { maxTurns:
|
|
2941
|
-
strategist: { maxTurns:
|
|
2942
|
-
designer: { maxTurns:
|
|
2943
|
-
architect: { maxTurns:
|
|
2944
|
-
builder: { maxTurns:
|
|
2945
|
-
qa: { maxTurns:
|
|
2946
|
-
guardian: { maxTurns:
|
|
2947
|
-
sentinel: { maxTurns:
|
|
2948
|
-
security: { maxTurns:
|
|
2949
|
-
scribe: { maxTurns:
|
|
2950
|
-
devops: { maxTurns:
|
|
3063
|
+
analyst: { maxTurns: 50 },
|
|
3064
|
+
strategist: { maxTurns: 50 },
|
|
3065
|
+
designer: { maxTurns: 50 },
|
|
3066
|
+
architect: { maxTurns: 75 },
|
|
3067
|
+
builder: { maxTurns: 150 },
|
|
3068
|
+
qa: { maxTurns: 60 },
|
|
3069
|
+
guardian: { maxTurns: 75 },
|
|
3070
|
+
sentinel: { maxTurns: 50 },
|
|
3071
|
+
security: { maxTurns: 50 },
|
|
3072
|
+
scribe: { maxTurns: 50 },
|
|
3073
|
+
devops: { maxTurns: 60 }
|
|
2951
3074
|
}
|
|
2952
3075
|
};
|
|
2953
3076
|
|
|
@@ -3319,6 +3442,11 @@ function formatSize(bytes) {
|
|
|
3319
3442
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
3320
3443
|
}
|
|
3321
3444
|
|
|
3445
|
+
// src/commands/resume.ts
|
|
3446
|
+
import * as fs6 from "fs";
|
|
3447
|
+
import * as path6 from "path";
|
|
3448
|
+
import chalk11 from "chalk";
|
|
3449
|
+
|
|
3322
3450
|
// src/ui/interactive.ts
|
|
3323
3451
|
import * as readline from "readline";
|
|
3324
3452
|
import { spawn as spawnProcess } from "child_process";
|
|
@@ -3384,24 +3512,34 @@ var ALL_AGENTS = [
|
|
|
3384
3512
|
{ key: "scribe", label: "Scribe", desc: "Documentation" },
|
|
3385
3513
|
{ key: "devops", label: "DevOps", desc: "CI/CD & deployment" }
|
|
3386
3514
|
];
|
|
3515
|
+
var lastRenderedLines = 0;
|
|
3387
3516
|
function renderMenu(items, selected, multi, checked) {
|
|
3517
|
+
const cols = process.stdout.columns || 80;
|
|
3518
|
+
lastRenderedLines = 0;
|
|
3388
3519
|
for (let i = 0; i < items.length; i++) {
|
|
3389
3520
|
const isSelected = i === selected;
|
|
3390
3521
|
const pointer = isSelected ? chalk10.hex(THEME.primary)(">") : " ";
|
|
3391
3522
|
const label = isSelected ? chalk10.bold.white(items[i].label.padEnd(14)) : chalk10.white(items[i].label.padEnd(14));
|
|
3392
|
-
const
|
|
3523
|
+
const prefix = multi ? " X [x] " : " X ";
|
|
3524
|
+
const labelRaw = items[i].label.padEnd(14);
|
|
3525
|
+
const availableForDesc = cols - prefix.length - labelRaw.length - 2;
|
|
3526
|
+
const descText = availableForDesc > 10 ? items[i].description.slice(0, availableForDesc) : items[i].description.slice(0, 30);
|
|
3527
|
+
const desc = chalk10.dim(descText);
|
|
3393
3528
|
let check = "";
|
|
3394
3529
|
if (multi && checked) {
|
|
3395
3530
|
check = checked.has(i) ? chalk10.hex(THEME.primary)(" [x] ") : chalk10.dim(" [ ] ");
|
|
3396
3531
|
}
|
|
3397
3532
|
process.stdout.write(` ${pointer}${check} ${label} ${desc}
|
|
3398
3533
|
`);
|
|
3534
|
+
lastRenderedLines++;
|
|
3399
3535
|
}
|
|
3400
3536
|
}
|
|
3401
3537
|
function clearMenu(count) {
|
|
3402
|
-
|
|
3403
|
-
|
|
3538
|
+
const lines = count ?? lastRenderedLines;
|
|
3539
|
+
for (let i = 0; i < lines; i++) {
|
|
3540
|
+
process.stdout.write("\x1B[1A");
|
|
3404
3541
|
}
|
|
3542
|
+
process.stdout.write("\x1B[0J");
|
|
3405
3543
|
}
|
|
3406
3544
|
function arrowSelect(items) {
|
|
3407
3545
|
return new Promise((resolve) => {
|
|
@@ -3420,19 +3558,19 @@ function arrowSelect(items) {
|
|
|
3420
3558
|
}
|
|
3421
3559
|
if (str === "\x1B[A" && selected > 0) {
|
|
3422
3560
|
selected--;
|
|
3423
|
-
clearMenu(
|
|
3561
|
+
clearMenu();
|
|
3424
3562
|
renderMenu(items, selected);
|
|
3425
3563
|
}
|
|
3426
3564
|
if (str === "\x1B[B" && selected < items.length - 1) {
|
|
3427
3565
|
selected++;
|
|
3428
|
-
clearMenu(
|
|
3566
|
+
clearMenu();
|
|
3429
3567
|
renderMenu(items, selected);
|
|
3430
3568
|
}
|
|
3431
3569
|
if (str === "\r" || str === "\n") {
|
|
3432
3570
|
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
3433
3571
|
process.stdin.pause();
|
|
3434
3572
|
process.stdin.removeListener("data", onKey);
|
|
3435
|
-
clearMenu(
|
|
3573
|
+
clearMenu();
|
|
3436
3574
|
const item = items[selected];
|
|
3437
3575
|
console.log(` ${chalk10.hex(THEME.primary)(">")} ${chalk10.bold.white(item.label)}`);
|
|
3438
3576
|
resolve(selected);
|
|
@@ -3459,12 +3597,12 @@ function arrowMultiSelect(items) {
|
|
|
3459
3597
|
}
|
|
3460
3598
|
if (str === "\x1B[A" && selected > 0) {
|
|
3461
3599
|
selected--;
|
|
3462
|
-
clearMenu(
|
|
3600
|
+
clearMenu();
|
|
3463
3601
|
renderMenu(items, selected, true, checked);
|
|
3464
3602
|
}
|
|
3465
3603
|
if (str === "\x1B[B" && selected < items.length - 1) {
|
|
3466
3604
|
selected++;
|
|
3467
|
-
clearMenu(
|
|
3605
|
+
clearMenu();
|
|
3468
3606
|
renderMenu(items, selected, true, checked);
|
|
3469
3607
|
}
|
|
3470
3608
|
if (str === " ") {
|
|
@@ -3473,14 +3611,14 @@ function arrowMultiSelect(items) {
|
|
|
3473
3611
|
} else {
|
|
3474
3612
|
checked.add(selected);
|
|
3475
3613
|
}
|
|
3476
|
-
clearMenu(
|
|
3614
|
+
clearMenu();
|
|
3477
3615
|
renderMenu(items, selected, true, checked);
|
|
3478
3616
|
}
|
|
3479
3617
|
if (str === "\r" || str === "\n") {
|
|
3480
3618
|
if (process.stdin.isTTY) process.stdin.setRawMode(wasRaw ?? false);
|
|
3481
3619
|
process.stdin.pause();
|
|
3482
3620
|
process.stdin.removeListener("data", onKey);
|
|
3483
|
-
clearMenu(
|
|
3621
|
+
clearMenu();
|
|
3484
3622
|
const picks = Array.from(checked).sort();
|
|
3485
3623
|
if (picks.length > 0) {
|
|
3486
3624
|
const names = picks.map((i) => items[i].label).join(", ");
|
|
@@ -3863,11 +4001,168 @@ async function startInteractiveSession() {
|
|
|
3863
4001
|
}
|
|
3864
4002
|
}
|
|
3865
4003
|
|
|
4004
|
+
// src/commands/resume.ts
|
|
4005
|
+
var STAGE_ORDER = STAGES.filter((s) => s.stage !== "complete").map((s) => s.stage);
|
|
4006
|
+
var ROLE_TO_STAGE = {
|
|
4007
|
+
analyst: "analyzing",
|
|
4008
|
+
strategist: "planning",
|
|
4009
|
+
designer: "designing",
|
|
4010
|
+
architect: "architecting",
|
|
4011
|
+
builder: "coding",
|
|
4012
|
+
qa: "qa-planning",
|
|
4013
|
+
guardian: "testing",
|
|
4014
|
+
sentinel: "reviewing",
|
|
4015
|
+
security: "securing",
|
|
4016
|
+
scribe: "documenting",
|
|
4017
|
+
devops: "deploying"
|
|
4018
|
+
};
|
|
4019
|
+
async function discoverRuns(projectDir) {
|
|
4020
|
+
const historyDir = path6.join(projectDir, ".krenk", "history");
|
|
4021
|
+
let entries;
|
|
4022
|
+
try {
|
|
4023
|
+
entries = await fs6.promises.readdir(historyDir);
|
|
4024
|
+
} catch {
|
|
4025
|
+
return [];
|
|
4026
|
+
}
|
|
4027
|
+
const runs = [];
|
|
4028
|
+
for (const entry of entries) {
|
|
4029
|
+
const runDir = path6.join(historyDir, entry);
|
|
4030
|
+
const stat = await fs6.promises.stat(runDir).catch(() => null);
|
|
4031
|
+
if (!stat?.isDirectory()) continue;
|
|
4032
|
+
const state = await ContextManager.loadRunState(projectDir, entry);
|
|
4033
|
+
const inferredStages = [];
|
|
4034
|
+
try {
|
|
4035
|
+
const files = await fs6.promises.readdir(runDir);
|
|
4036
|
+
for (const file of files) {
|
|
4037
|
+
if (file.endsWith(".md")) {
|
|
4038
|
+
const role = file.replace(/\.md$/, "");
|
|
4039
|
+
const stage = ROLE_TO_STAGE[role];
|
|
4040
|
+
if (stage) inferredStages.push(stage);
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
} catch {
|
|
4044
|
+
}
|
|
4045
|
+
let date;
|
|
4046
|
+
try {
|
|
4047
|
+
const isoStr = entry.replace(/^(\d{4}-\d{2}-\d{2}T\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "$1:$2:$3.$4Z");
|
|
4048
|
+
date = new Date(isoStr);
|
|
4049
|
+
if (isNaN(date.getTime())) date = new Date(stat.mtime);
|
|
4050
|
+
} catch {
|
|
4051
|
+
date = new Date(stat.mtime);
|
|
4052
|
+
}
|
|
4053
|
+
runs.push({ runId: entry, state, inferredStages, date });
|
|
4054
|
+
}
|
|
4055
|
+
runs.sort((a, b) => b.date.getTime() - a.date.getTime());
|
|
4056
|
+
return runs;
|
|
4057
|
+
}
|
|
4058
|
+
function getStageLabel(completedStages) {
|
|
4059
|
+
if (completedStages.length === 0) return "not started";
|
|
4060
|
+
let furthest = completedStages[0];
|
|
4061
|
+
for (const stage of completedStages) {
|
|
4062
|
+
if (STAGE_ORDER.indexOf(stage) > STAGE_ORDER.indexOf(furthest)) {
|
|
4063
|
+
furthest = stage;
|
|
4064
|
+
}
|
|
4065
|
+
}
|
|
4066
|
+
const info = STAGES.find((s) => s.stage === furthest);
|
|
4067
|
+
return info ? info.label : furthest;
|
|
4068
|
+
}
|
|
4069
|
+
function getCompletedStages(run) {
|
|
4070
|
+
if (run.state?.completedStages && run.state.completedStages.length > 0) {
|
|
4071
|
+
return run.state.completedStages;
|
|
4072
|
+
}
|
|
4073
|
+
return run.inferredStages;
|
|
4074
|
+
}
|
|
4075
|
+
function formatRunItem(run) {
|
|
4076
|
+
const dateStr = run.date.toLocaleString();
|
|
4077
|
+
const completed = getCompletedStages(run);
|
|
4078
|
+
const stageLabel = getStageLabel(completed);
|
|
4079
|
+
const status = run.state?.status || (completed.length > 0 ? "interrupted" : "unknown");
|
|
4080
|
+
const promptSnippet = run.state?.prompt ? run.state.prompt.slice(0, 50) + (run.state.prompt.length > 50 ? "..." : "") : "(no prompt saved)";
|
|
4081
|
+
const statusColor = status === "complete" ? chalk11.green : status === "failed" ? chalk11.red : chalk11.yellow;
|
|
4082
|
+
return {
|
|
4083
|
+
label: dateStr,
|
|
4084
|
+
description: `${statusColor(status)} | reached: ${stageLabel} | ${chalk11.dim(promptSnippet)}`
|
|
4085
|
+
};
|
|
4086
|
+
}
|
|
4087
|
+
async function resumeCommand() {
|
|
4088
|
+
const cwd = process.cwd();
|
|
4089
|
+
console.log(chalk11.bold.hex(THEME.primary)("\n Resume a previous run\n"));
|
|
4090
|
+
const runs = await discoverRuns(cwd);
|
|
4091
|
+
if (runs.length === 0) {
|
|
4092
|
+
console.log(chalk11.dim(" No previous runs found in .krenk/history/\n"));
|
|
4093
|
+
return;
|
|
4094
|
+
}
|
|
4095
|
+
const resumable = runs.filter((r) => {
|
|
4096
|
+
const status = r.state?.status;
|
|
4097
|
+
return status !== "complete";
|
|
4098
|
+
});
|
|
4099
|
+
if (resumable.length === 0) {
|
|
4100
|
+
console.log(chalk11.dim(" All previous runs completed successfully. Nothing to resume.\n"));
|
|
4101
|
+
console.log(chalk11.dim(" Recent runs:"));
|
|
4102
|
+
for (const run of runs.slice(0, 5)) {
|
|
4103
|
+
const item = formatRunItem(run);
|
|
4104
|
+
console.log(chalk11.dim(` ${item.label} \u2014 ${item.description}`));
|
|
4105
|
+
}
|
|
4106
|
+
console.log();
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
console.log(chalk11.dim(" Select a run to resume:\n"));
|
|
4110
|
+
const menuItems = resumable.map(formatRunItem);
|
|
4111
|
+
const selectedIndex = await arrowSelect(menuItems);
|
|
4112
|
+
const selectedRun = resumable[selectedIndex];
|
|
4113
|
+
const completedStages = getCompletedStages(selectedRun);
|
|
4114
|
+
const prompt = selectedRun.state?.prompt;
|
|
4115
|
+
if (!prompt) {
|
|
4116
|
+
console.log(chalk11.red("\n Cannot resume: no prompt was saved for this run."));
|
|
4117
|
+
console.log(chalk11.dim(" This is a legacy run without state.json. Try running the task again.\n"));
|
|
4118
|
+
return;
|
|
4119
|
+
}
|
|
4120
|
+
const skippedLabels = completedStages.map((s) => {
|
|
4121
|
+
const info = STAGES.find((st) => st.stage === s);
|
|
4122
|
+
return info?.label || s;
|
|
4123
|
+
});
|
|
4124
|
+
const remainingStages = STAGE_ORDER.filter((s) => !completedStages.includes(s));
|
|
4125
|
+
const remainingLabels = remainingStages.map((s) => {
|
|
4126
|
+
const info = STAGES.find((st) => st.stage === s);
|
|
4127
|
+
return info?.label || s;
|
|
4128
|
+
});
|
|
4129
|
+
console.log();
|
|
4130
|
+
if (skippedLabels.length > 0) {
|
|
4131
|
+
console.log(chalk11.dim(` Skipping completed: ${skippedLabels.join(", ")}`));
|
|
4132
|
+
}
|
|
4133
|
+
console.log(chalk11.white(` Resuming from: ${remainingLabels[0] || "unknown"}`));
|
|
4134
|
+
console.log(chalk11.dim(` Remaining: ${remainingLabels.join(", ")}`));
|
|
4135
|
+
console.log();
|
|
4136
|
+
const config = await loadConfig(cwd);
|
|
4137
|
+
const skipStages = selectedRun.state?.skipStages || [];
|
|
4138
|
+
const engine = new OrchestrationEngine({
|
|
4139
|
+
cwd,
|
|
4140
|
+
maxParallel: config.maxParallelAgents,
|
|
4141
|
+
skipStages,
|
|
4142
|
+
noUi: false,
|
|
4143
|
+
supervised: false,
|
|
4144
|
+
agentConfig: config.agents,
|
|
4145
|
+
resumeRunId: selectedRun.runId,
|
|
4146
|
+
resumeCompletedStages: completedStages
|
|
4147
|
+
});
|
|
4148
|
+
const renderer = new TerminalRenderer(engine);
|
|
4149
|
+
setupGracefulShutdown(engine, () => renderer.cleanup());
|
|
4150
|
+
const result = await engine.run(prompt);
|
|
4151
|
+
renderer.printSummary(result);
|
|
4152
|
+
if (!result.success) {
|
|
4153
|
+
process.exit(1);
|
|
4154
|
+
}
|
|
4155
|
+
}
|
|
4156
|
+
|
|
3866
4157
|
// src/index.ts
|
|
3867
4158
|
program.name("krenk").description(
|
|
3868
4159
|
"Multi-agent software engineering orchestrator powered by Claude Code"
|
|
3869
|
-
).version("0.1.0").action(async () => {
|
|
3870
|
-
|
|
4160
|
+
).version("0.1.0").option("--resume", "Resume a previous interrupted or failed run").action(async (opts) => {
|
|
4161
|
+
if (opts.resume) {
|
|
4162
|
+
await resumeCommand();
|
|
4163
|
+
} else {
|
|
4164
|
+
await startInteractiveSession();
|
|
4165
|
+
}
|
|
3871
4166
|
});
|
|
3872
4167
|
program.command("run").description("Run full engineering workflow with all agents").argument("<prompt>", "What to build").option("--skip <stages...>", "Skip specific stages (e.g. --skip design test)").option("--parallel <n>", "Max parallel agents", "3").option("--no-ui", "Disable fancy UI, use plain output").option("--supervised", "Approve each agent before it runs").action(runCommand);
|
|
3873
4168
|
program.command("plan").description("Run only the planning stage").argument("<prompt>", "What to plan").action(planCommand);
|