@mrciphersmith/keryx 0.2.66 → 0.2.67

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.
Files changed (2) hide show
  1. package/dist/cli.js +206 -2
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -53486,6 +53486,7 @@ ${lines.join(`
53486
53486
 
53487
53487
  // src/tui/tui-shell.ts
53488
53488
  init_agent();
53489
+ init_single_turn();
53489
53490
  init_slate_lifecycle();
53490
53491
  init_slate();
53491
53492
 
@@ -53894,7 +53895,7 @@ import { spawnSync as spawnSync2 } from "child_process";
53894
53895
  // package.json
53895
53896
  var package_default = {
53896
53897
  name: "@mrciphersmith/keryx",
53897
- version: "0.2.66",
53898
+ version: "0.2.67",
53898
53899
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
53899
53900
  private: false,
53900
53901
  publishConfig: {
@@ -55360,10 +55361,13 @@ async function readExternalUnboundCandidates(cwd) {
55360
55361
  }
55361
55362
  return groups;
55362
55363
  }, []));
55364
+ const metaPath2 = path150.join(extDir, `${id}.json`);
55365
+ if (await pathExists(unboundDismissedReceiptPath(metaPath2)))
55366
+ continue;
55363
55367
  candidates.push({
55364
55368
  type: "unbound-candidate",
55365
55369
  externalSessionId: id,
55366
- evidencePath: path150.join(extDir, `${id}.json`),
55370
+ evidencePath: metaPath2,
55367
55371
  summary
55368
55372
  });
55369
55373
  }
@@ -55381,6 +55385,8 @@ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
55381
55385
  entries.sort();
55382
55386
  for (let i = entries.length - 1;i >= 0; i--) {
55383
55387
  const evidencePath = path150.join(evidenceDir, entries[i]);
55388
+ if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
55389
+ continue;
55384
55390
  const result = readConfigFile(evidencePath);
55385
55391
  if (!result.ok) {
55386
55392
  continue;
@@ -55589,6 +55595,55 @@ async function readTerminalState(dir) {
55589
55595
  return;
55590
55596
  }
55591
55597
  }
55598
+ function unboundDismissedReceiptPath(candidatePath) {
55599
+ if (/-unbound-candidate\.json$/.test(candidatePath)) {
55600
+ return candidatePath.replace(/-unbound-candidate\.json$/, "-unbound-dismissed.json");
55601
+ }
55602
+ return candidatePath.replace(/\.json$/, ".dismissed.json");
55603
+ }
55604
+ async function resolveUnboundCandidateTarget(cwd, target) {
55605
+ if (target.endsWith("-unbound-candidate.json") || target.endsWith(".json")) {
55606
+ return target;
55607
+ }
55608
+ const session = findSession(cwd, target);
55609
+ if (session === undefined) {
55610
+ throw new Error(`No session or evidence path matches "${target}"`);
55611
+ }
55612
+ const archiveDir = path150.join(sessionDir(session.projectPath, session.id), "slate-archive");
55613
+ let entries;
55614
+ try {
55615
+ entries = (await readdir26(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
55616
+ } catch {
55617
+ throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
55618
+ }
55619
+ if (entries.length === 0)
55620
+ throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
55621
+ entries.sort();
55622
+ return path150.join(archiveDir, entries[entries.length - 1]);
55623
+ }
55624
+ async function dismissUnboundByTarget(cwd, target, reason) {
55625
+ const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
55626
+ return dismissUnboundCandidate(evidencePath, reason);
55627
+ }
55628
+ async function dismissUnboundCandidate(candidatePath, reason) {
55629
+ const { rm: rm10, writeFile: writeFile49 } = await import("fs/promises");
55630
+ const { dirname } = await import("path");
55631
+ await rm10(candidatePath, { force: true });
55632
+ const receiptPath = unboundDismissedReceiptPath(candidatePath);
55633
+ const receipt = {
55634
+ recordType: "unbound-dismissed",
55635
+ dismissedAt: new Date().toISOString(),
55636
+ candidatePath: candidatePath.split("/").pop(),
55637
+ ...reason !== undefined ? { reason } : {}
55638
+ };
55639
+ await writeFile49(receiptPath, `${JSON.stringify(receipt, null, 2)}
55640
+ `, "utf8");
55641
+ const parent = dirname(candidatePath);
55642
+ try {
55643
+ await rm10(parent, { force: true, recursive: false });
55644
+ } catch {}
55645
+ return { removed: candidatePath, receipt: receiptPath };
55646
+ }
55592
55647
  async function readNewestUnboundCandidate(dir) {
55593
55648
  const archiveDir = path150.join(dir, "slate-archive");
55594
55649
  let entries;
@@ -55600,6 +55655,8 @@ async function readNewestUnboundCandidate(dir) {
55600
55655
  entries.sort();
55601
55656
  for (let i = entries.length - 1;i >= 0; i--) {
55602
55657
  const evidencePath = path150.join(archiveDir, entries[i]);
55658
+ if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
55659
+ continue;
55603
55660
  const result = readConfigFile(evidencePath);
55604
55661
  if (!result.ok) {
55605
55662
  continue;
@@ -59371,6 +59428,53 @@ function composerMaxRowsForViewport(viewportRows) {
59371
59428
  }
59372
59429
  return Math.max(COMPOSER_MIN_ROWS, Math.floor(viewportRows / 3));
59373
59430
  }
59431
+ function themeColorToHex(value) {
59432
+ if (typeof value === "string") {
59433
+ return /^#[0-9a-fA-F]{6}$/.test(value) ? value.toLowerCase() : undefined;
59434
+ }
59435
+ if (value !== null && typeof value === "object" && typeof value.toInts === "function") {
59436
+ const [r, g, b, a] = value.toInts();
59437
+ if (a !== 255) {
59438
+ return;
59439
+ }
59440
+ return `#${[r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
59441
+ }
59442
+ return;
59443
+ }
59444
+ function themeColorRemap(from, to) {
59445
+ const remap = new Map;
59446
+ for (const slot of Object.keys(from)) {
59447
+ if (slot === "name") {
59448
+ continue;
59449
+ }
59450
+ const oldColor = from[slot];
59451
+ const newColor = to[slot];
59452
+ if (typeof oldColor === "string" && typeof newColor === "string" && oldColor !== newColor) {
59453
+ remap.set(oldColor, newColor);
59454
+ }
59455
+ }
59456
+ return remap;
59457
+ }
59458
+ function recolorThemeTree(node, remap) {
59459
+ if (node === null || node === undefined) {
59460
+ return;
59461
+ }
59462
+ const target = node;
59463
+ for (const prop of ["borderColor", "backgroundColor", "fg"]) {
59464
+ const hex = themeColorToHex(target[prop]);
59465
+ if (hex !== undefined) {
59466
+ const next = remap.get(hex);
59467
+ if (next !== undefined) {
59468
+ target[prop] = next;
59469
+ }
59470
+ }
59471
+ }
59472
+ if (typeof target.getChildren === "function") {
59473
+ for (const child of target.getChildren()) {
59474
+ recolorThemeTree(child, remap);
59475
+ }
59476
+ }
59477
+ }
59374
59478
  function wrappedLineCount(text, width) {
59375
59479
  const inner = Number.isFinite(width) ? Math.floor(width) : 0;
59376
59480
  const paragraphs = text.length === 0 ? [""] : text.split(`
@@ -59408,6 +59512,7 @@ async function createShellChrome(otui, renderer, opts) {
59408
59512
  const filter = opts.filterCommands ?? ((query) => prefixFilter(opts.commands, query));
59409
59513
  let uid = 0;
59410
59514
  let alive = true;
59515
+ let appliedTheme = getTheme();
59411
59516
  const rootRow = new otui.BoxRenderable(r, { id: "root-row", flexGrow: 1, flexDirection: "row" });
59412
59517
  r.root.add(rootRow);
59413
59518
  const main = new otui.BoxRenderable(r, { id: "main", flexGrow: 1, minWidth: 0, flexDirection: "column" });
@@ -59805,6 +59910,12 @@ async function createShellChrome(otui, renderer, opts) {
59805
59910
  input2.value = "";
59806
59911
  hideMenu();
59807
59912
  syncComposerHeight();
59913
+ if (line.length === 0 && suggestion !== null) {
59914
+ const next = suggestion;
59915
+ clearSuggestion();
59916
+ emitSubmit(next);
59917
+ return;
59918
+ }
59808
59919
  emitSubmit(line);
59809
59920
  };
59810
59921
  const unsubscribeMenuKeys = onKeypress3(r, (key) => {
@@ -59843,7 +59954,51 @@ async function createShellChrome(otui, renderer, opts) {
59843
59954
  key.stopPropagation();
59844
59955
  }
59845
59956
  });
59957
+ const defaultPlaceholder = opts.placeholder;
59958
+ let suggestion = null;
59959
+ const syncPlaceholder = () => {
59960
+ textarea.placeholder = suggestion !== null && input2.value.length === 0 ? suggestion : defaultPlaceholder;
59961
+ };
59962
+ const showSuggestion = (text) => {
59963
+ if (text.length === 0)
59964
+ return;
59965
+ suggestion = text;
59966
+ syncPlaceholder();
59967
+ };
59968
+ const clearSuggestion = () => {
59969
+ if (suggestion === null)
59970
+ return;
59971
+ suggestion = null;
59972
+ syncPlaceholder();
59973
+ };
59974
+ const prevContentChange = textarea.onContentChange;
59975
+ textarea.onContentChange = (event) => {
59976
+ if (suggestion !== null)
59977
+ syncPlaceholder();
59978
+ prevContentChange?.(event);
59979
+ };
59980
+ const unsubscribeSuggestionKeys = onKeypress3(r, (key) => {
59981
+ if (suggestion === null || overlayActive())
59982
+ return;
59983
+ if (menu.visible && menuNav)
59984
+ return;
59985
+ if (key.name === "tab" || key.name === "right") {
59986
+ if (input2.value.length === 0) {
59987
+ input2.value = suggestion;
59988
+ clearSuggestion();
59989
+ key.preventDefault();
59990
+ key.stopPropagation();
59991
+ return;
59992
+ }
59993
+ return;
59994
+ }
59995
+ const ch = key.sequence;
59996
+ if (!key.ctrl && !key.meta && typeof ch === "string" && ch.length === 1 && ch >= " ") {
59997
+ clearSuggestion();
59998
+ }
59999
+ });
59846
60000
  const applyTheme = (theme = getTheme()) => {
60001
+ const remap = themeColorRemap(appliedTheme, theme);
59847
60002
  try {
59848
60003
  r.setBackgroundColor(theme.bg);
59849
60004
  } catch {}
@@ -59861,6 +60016,15 @@ async function createShellChrome(otui, renderer, opts) {
59861
60016
  menu.selectedTextColor = theme.focus;
59862
60017
  menu.descriptionColor = theme.muted;
59863
60018
  menu.selectedDescriptionColor = theme.muted;
60019
+ recolorThemeTree(transcript, remap);
60020
+ recolorThemeTree(dock, remap);
60021
+ recolorThemeTree(queueDock, remap);
60022
+ recolorThemeTree(sidebarTop, remap);
60023
+ recolorThemeTree(menu, remap);
60024
+ recolorThemeTree(composer, remap);
60025
+ recolorThemeTree(header3, remap);
60026
+ recolorThemeTree(footer, remap);
60027
+ appliedTheme = theme;
59864
60028
  };
59865
60029
  const unsubTheme = onThemeChange((theme) => applyTheme(theme));
59866
60030
  return {
@@ -59907,6 +60071,9 @@ async function createShellChrome(otui, renderer, opts) {
59907
60071
  setTitle: (text) => paintDim(headerLeft, text),
59908
60072
  setStatus: (text) => paintDim(footerRight, text),
59909
60073
  setHeaderMeta: (text) => paintDim(headerRight, text),
60074
+ showSuggestion,
60075
+ clearSuggestion,
60076
+ suggestionActive: () => suggestion !== null,
59910
60077
  onSubmit: (handler) => {
59911
60078
  submitHandlers.add(handler);
59912
60079
  return () => {
@@ -59920,6 +60087,7 @@ async function createShellChrome(otui, renderer, opts) {
59920
60087
  clearBusyTimer();
59921
60088
  clearToastTimer();
59922
60089
  unsubscribeMenuKeys();
60090
+ unsubscribeSuggestionKeys();
59923
60091
  try {
59924
60092
  textarea.off(otui.LayoutEvents.RESIZED, onComposerResized);
59925
60093
  } catch {}
@@ -65036,6 +65204,28 @@ ${formatThemeList(getThemeId())}`);
65036
65204
  };
65037
65205
  const controller = new AbortController;
65038
65206
  mainTurnAbortController = controller;
65207
+ const suggestNextStep = async () => {
65208
+ try {
65209
+ const lastUser = [...history].reverse().find((m) => m.role === "user")?.content ?? "";
65210
+ const lastAssistant = [...history].reverse().find((m) => m.role === "assistant")?.content ?? "";
65211
+ const tail = lastAssistant.slice(-3000);
65212
+ const result = await runModelTurn({
65213
+ provider: sel.provider,
65214
+ model: sel.model,
65215
+ system: "You are the next-step advisor of a coding assistant terminal. Based on the user's last request and the assistant's final reply, propose ONE short follow-up the user could do next: imperative, no quotes, no markdown, at most 80 characters. If nothing useful exists, reply with exactly one dot: .",
65216
+ user: `User: ${lastUser.slice(-800)}
65217
+
65218
+ Assistant reply (tail):
65219
+ ${tail}`,
65220
+ maxOutputTokens: 40,
65221
+ requestId: `suggest-next-step-${Date.now()}`
65222
+ });
65223
+ if (!result.credentialAvailable || result.text.trim().length === 0 || result.text.trim() === ".") {
65224
+ return;
65225
+ }
65226
+ chrome.showSuggestion(result.text.trim().split(/\s+/).slice(0, 20).join(" "));
65227
+ } catch {}
65228
+ };
65039
65229
  runAgentTurn(io, deps, history, line, {
65040
65230
  signal: controller.signal,
65041
65231
  ...slateSession !== undefined ? { slateSession } : {}
@@ -65056,6 +65246,9 @@ ${formatThemeList(getThemeId())}`);
65056
65246
  sbContext.content = otui.t`${otui.dim(`~${est.toLocaleString()} tokens (est)`)}`;
65057
65247
  }
65058
65248
  focusComposer();
65249
+ if (priorityMainQuestion === undefined && mainQueue.length === 0 && !turnFailed) {
65250
+ suggestNextStep();
65251
+ }
65059
65252
  if (priorityMainQuestion !== undefined) {
65060
65253
  const next = priorityMainQuestion;
65061
65254
  priorityMainQuestion = undefined;
@@ -73371,6 +73564,17 @@ async function workspaceCommand(args2) {
73371
73564
  console.log(JSON.stringify(normalizeProposalLifecycleResult(result), null, 2));
73372
73565
  return;
73373
73566
  }
73567
+ if (subcommand === "dismiss-candidate") {
73568
+ rejectUnknownOptions(args2.slice(2), new Set(["--reason", "--evidence"]));
73569
+ const target = args2[1];
73570
+ const reason = optionValue(args2, "--reason");
73571
+ const evidence = optionValue(args2, "--evidence");
73572
+ if (!target)
73573
+ throw new Error("Usage: keryx workspace dismiss-candidate <evidence-path|session-id> [--reason <reason>] [--evidence <path>]");
73574
+ const result = await dismissUnboundByTarget(process.cwd(), evidence ?? target, reason);
73575
+ console.log(JSON.stringify(result, null, 2));
73576
+ return;
73577
+ }
73374
73578
  if (subcommand === "collaboration") {
73375
73579
  rejectUnknownOptions(args2.slice(2), new Set);
73376
73580
  const workspaceId = args2[1];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.66",
3
+ "version": "0.2.67",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -72,4 +72,4 @@
72
72
  "protobufjs",
73
73
  "sharp"
74
74
  ]
75
- }
75
+ }