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 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: 600,
906
- hungWarningSeconds: 120,
907
- hungKillSeconds: 300,
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 this.context.saveState({
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: 30 },
2941
- strategist: { maxTurns: 30 },
2942
- designer: { maxTurns: 30 },
2943
- architect: { maxTurns: 50 },
2944
- builder: { maxTurns: 100 },
2945
- qa: { maxTurns: 40 },
2946
- guardian: { maxTurns: 50 },
2947
- sentinel: { maxTurns: 30 },
2948
- security: { maxTurns: 30 },
2949
- scribe: { maxTurns: 30 },
2950
- devops: { maxTurns: 40 }
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 desc = chalk10.dim(items[i].description);
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
- for (let i = 0; i < count; i++) {
3403
- process.stdout.write("\x1B[1A\x1B[2K");
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(items.length);
3561
+ clearMenu();
3424
3562
  renderMenu(items, selected);
3425
3563
  }
3426
3564
  if (str === "\x1B[B" && selected < items.length - 1) {
3427
3565
  selected++;
3428
- clearMenu(items.length);
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(items.length);
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(items.length);
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(items.length);
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(items.length);
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(items.length);
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
- await startInteractiveSession();
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);