@krtclcdy/tersio 2.25.6 → 2.25.8

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 (149) hide show
  1. package/README.md +43 -13
  2. package/dashboard/dist/index.html +1 -1
  3. package/dist/cli/banner.js +1 -2
  4. package/dist/cli/common.js +26 -4
  5. package/dist/cli/dashboard.js +7 -2
  6. package/dist/cli/doctor-fix.js +12 -7
  7. package/dist/cli/doctor.js +64 -15
  8. package/dist/cli/hosts.js +1 -2
  9. package/dist/cli/install.js +12 -7
  10. package/dist/cli/manifest.js +25 -5
  11. package/dist/cli/profile.js +28 -3
  12. package/dist/cli/rtk-wiring.js +13 -10
  13. package/dist/cli/uninstall.js +1 -1
  14. package/dist/cli/usage.js +5 -0
  15. package/dist/extensions/caveman/index.js +4 -3
  16. package/dist/extensions/combo/index.js +21 -7
  17. package/dist/extensions/commands/index.js +7 -2
  18. package/dist/extensions/pstack/index.js +5 -2
  19. package/dist/extensions/rtk/filter.js +45 -4
  20. package/dist/extensions/rtk/session.js +21 -11
  21. package/dist/extensions/server.js +44 -29
  22. package/dist/extensions/shared/agent-stats.js +21 -13
  23. package/dist/extensions/shared/pricing.js +5 -4
  24. package/dist/extensions/shared/rtk-eligible.js +80 -0
  25. package/dist/extensions/shared/rtk-gain.js +3 -7
  26. package/dist/extensions/shared/sqlite.js +2 -13
  27. package/dist/extensions/shared/status.js +1 -2
  28. package/dist/extensions/shared/usage-ledger.js +196 -29
  29. package/dist/extensions/shared/utils.js +33 -0
  30. package/dist/extensions/updater/index.js +15 -7
  31. package/extensions/caveman/index.ts +4 -3
  32. package/extensions/caveman/rule-lite.md +77 -0
  33. package/extensions/caveman/rule-megacave-lite.md +59 -0
  34. package/extensions/caveman/rule-megacave-ultra.md +59 -0
  35. package/extensions/caveman/rule.md +1 -1
  36. package/extensions/combo/index.ts +22 -7
  37. package/extensions/commands/index.ts +7 -2
  38. package/extensions/pstack/index.ts +5 -2
  39. package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
  40. package/extensions/pstack/playbooks/autonomous-run.md +13 -0
  41. package/extensions/pstack/playbooks/autopilot-full.md +13 -0
  42. package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
  43. package/extensions/pstack/playbooks/babysit.md +23 -0
  44. package/extensions/pstack/playbooks/bug-fix.md +15 -0
  45. package/extensions/pstack/playbooks/eval.md +25 -0
  46. package/extensions/pstack/playbooks/feature.md +21 -0
  47. package/extensions/pstack/playbooks/hillclimb.md +21 -0
  48. package/extensions/pstack/playbooks/investigation.md +14 -0
  49. package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
  50. package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
  51. package/extensions/pstack/playbooks/orchestrate.md +111 -0
  52. package/extensions/pstack/playbooks/pause-safely.md +10 -0
  53. package/extensions/pstack/playbooks/perf-issue.md +25 -0
  54. package/extensions/pstack/playbooks/prototype.md +14 -0
  55. package/extensions/pstack/playbooks/refactoring.md +16 -0
  56. package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
  57. package/extensions/pstack/playbooks/session-pickup.md +11 -0
  58. package/extensions/pstack/playbooks/shipping.md +17 -0
  59. package/extensions/pstack/playbooks/trace-forensics.md +14 -0
  60. package/extensions/pstack/playbooks/visual-parity.md +11 -0
  61. package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
  62. package/extensions/pstack/principles/attack-the-premise.md +22 -0
  63. package/extensions/pstack/principles/boundary-discipline.md +33 -0
  64. package/extensions/pstack/principles/build-the-lever.md +22 -0
  65. package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
  66. package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
  67. package/extensions/pstack/principles/experience-first.md +18 -0
  68. package/extensions/pstack/principles/explain-the-number.md +16 -0
  69. package/extensions/pstack/principles/fix-root-causes.md +23 -0
  70. package/extensions/pstack/principles/foundational-thinking.md +20 -0
  71. package/extensions/pstack/principles/guard-the-context-window.md +11 -0
  72. package/extensions/pstack/principles/laziness-protocol.md +9 -0
  73. package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
  74. package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
  75. package/extensions/pstack/principles/minimize-reader-load.md +17 -0
  76. package/extensions/pstack/principles/model-the-domain.md +23 -0
  77. package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
  78. package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
  79. package/extensions/pstack/principles/prove-it-works.md +21 -0
  80. package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
  81. package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
  82. package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
  83. package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
  84. package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
  85. package/extensions/pstack/principles/type-system-discipline.md +17 -0
  86. package/extensions/pstack/references/bugbot-triage.md +142 -0
  87. package/extensions/pstack/rule.md +88 -12
  88. package/extensions/pstack/skills/architect/SKILL.md +78 -0
  89. package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
  90. package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
  91. package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
  92. package/extensions/pstack/skills/arena/SKILL.md +47 -0
  93. package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
  94. package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
  95. package/extensions/pstack/skills/bro/SKILL.md +6 -0
  96. package/extensions/pstack/skills/correct/SKILL.md +31 -0
  97. package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
  98. package/extensions/pstack/skills/how/SKILL.md +35 -0
  99. package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
  100. package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
  101. package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
  102. package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
  103. package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
  104. package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
  105. package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
  106. package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
  107. package/extensions/pstack/skills/reflect/SKILL.md +66 -0
  108. package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
  109. package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
  110. package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
  111. package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
  112. package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
  113. package/extensions/pstack/skills/swarm/SKILL.md +45 -0
  114. package/extensions/pstack/skills/tdd/SKILL.md +41 -0
  115. package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
  116. package/extensions/pstack/skills/unslop/SKILL.md +61 -0
  117. package/extensions/pstack/skills/why/SKILL.md +97 -0
  118. package/extensions/pstack/skills/why/references/epistemics.md +71 -0
  119. package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
  120. package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
  121. package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
  122. package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
  123. package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
  124. package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
  125. package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
  126. package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
  127. package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
  128. package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
  129. package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
  130. package/extensions/rtk/filter.ts +51 -4
  131. package/extensions/rtk/session.ts +20 -10
  132. package/extensions/server.ts +31 -23
  133. package/extensions/shared/agent-stats.ts +21 -12
  134. package/extensions/shared/pricing.ts +7 -5
  135. package/extensions/shared/rtk-eligible.ts +79 -0
  136. package/extensions/shared/rtk-gain.ts +3 -7
  137. package/extensions/shared/sqlite.ts +2 -13
  138. package/extensions/shared/status.ts +1 -2
  139. package/extensions/shared/usage-ledger.ts +198 -32
  140. package/extensions/shared/utils.ts +29 -0
  141. package/extensions/timing/index.ts +1 -2
  142. package/extensions/updater/index.ts +18 -7
  143. package/package.json +8 -7
  144. package/dist/extensions/caveman/rule-megacave.md +0 -65
  145. package/dist/extensions/caveman/rule-ultra.md +0 -63
  146. package/dist/extensions/caveman/rule.md +0 -89
  147. package/dist/extensions/pstack/rule.md +0 -65
  148. package/dist/probe-catalog.js +0 -48
  149. package/dist/probe-catalog188.js +0 -44
@@ -33,8 +33,11 @@ function piHasUpstreamPonytail() {
33
33
  return false;
34
34
  }
35
35
  }
36
+ // Each level's own rule, quoted from the installed Ponytail so a host that cannot resolve the package still gets the right register: `lite` builds what was asked and offers the smaller option, and only `ultra` questions the request.
36
37
  const PONYTAIL_FALLBACK_INTENSITY = {
37
- lite: 'Prefer the simplest correct solution.',
38
+ lite: 'Build what was asked. Name the smaller option in one line and let the user pick.',
39
+ full: 'The rules above. Default.',
40
+ ultra: 'Also question the request: before building, push back on any part the need does not justify.',
38
41
  };
39
42
  // Fallback text when Ponytail cannot be found; exported for its own test.
40
43
  export function ponytailFallback(mode) {
@@ -58,6 +61,14 @@ async function loadPonytailInstructions(mode) {
58
61
  }
59
62
  return ponytailFallback(mode);
60
63
  }
64
+ // What a fresh session starts from: a stored per-mode default wins, and with none the Combo preset supplies all four. pstackDefault counts as its own default, so a session that stores only it still honors it instead of losing it to the preset's `off`.
65
+ function savedModes() {
66
+ const hasModeDefaults = ['cavemanDefault', 'rtkDefault', 'ponytailDefault', 'pstackDefault'].some((key) => key in readPluginSettings());
67
+ const preset = COMBO_LEVELS[readComboDefault()];
68
+ return hasModeDefaults
69
+ ? { caveman: readCavemanDefault(), rtk: readRtkDefault() ? 'on' : 'off', ponytail: readPonytailDefault(), pstack: readPstackDefault() }
70
+ : { caveman: preset.caveman, rtk: preset.rtk, ponytail: preset.ponytail, pstack: preset.pstack };
71
+ }
61
72
  export default function comboToggleExtension(pi) {
62
73
  setExtensionLabel(pi, 'Combo session toggle (all four add-ons)');
63
74
  let setupPrompted = false;
@@ -74,14 +85,9 @@ export default function comboToggleExtension(pi) {
74
85
  pi.appendEntry?.('combo-level', { level });
75
86
  }
76
87
  function restoreConfiguredDefaults() {
77
- const settings = readPluginSettings();
78
- const hasModeDefaults = ['cavemanDefault', 'rtkDefault', 'ponytailDefault'].some((key) => key in settings);
79
88
  const combo = readComboDefault();
80
89
  const preset = COMBO_LEVELS[combo];
81
- // A stored per-mode default wins; with none, the preset supplies all four.
82
- const modes = hasModeDefaults
83
- ? { caveman: readCavemanDefault(), rtk: readRtkDefault() ? 'on' : 'off', ponytail: readPonytailDefault(), pstack: readPstackDefault() }
84
- : { caveman: preset.caveman, rtk: preset.rtk, ponytail: preset.ponytail, pstack: preset.pstack };
90
+ const modes = savedModes();
85
91
  setSharedComboModes(modes);
86
92
  if (modes.caveman === 'off' && modes.rtk === 'off' && modes.ponytail === 'off' && modes.pstack === 'off')
87
93
  return;
@@ -184,6 +190,13 @@ export default function comboToggleExtension(pi) {
184
190
  const entries = sessionEntries(ctx);
185
191
  if (!hasModeState(entries) && (ctx?.hasUI || OPENCODE))
186
192
  restoreConfiguredDefaults();
193
+ else if (!hasModeState(entries)) {
194
+ // A headless root session has no UI to publish the bridge, so Ponytail never loaded; publish the saved modes only while the bridge is clear, or a subagent branch stops inheriting.
195
+ const shared = getSharedComboState();
196
+ if (shared.caveman === 'off' && shared.rtk === 'off' && shared.ponytail === 'off' && shared.pstack === 'off') {
197
+ setSharedComboModes(savedModes());
198
+ }
199
+ }
187
200
  // Announced last, so any default above is reflected.
188
201
  announceStatus(ctx);
189
202
  });
@@ -199,6 +212,7 @@ export default function comboToggleExtension(pi) {
199
212
  pi.on('before_agent_start', async (event, ctx) => {
200
213
  if (ctx?.hasUI)
201
214
  reconcile(ctx);
215
+ // Shared state, with no marker check: the marker is a sentence copied out of the host binary, so matching on it would drop Ponytail after a host reworded the line.
202
216
  const mode = getSharedComboState().ponytail;
203
217
  // On pi, an installed upstream pi-extension is the sole injector, so Tersio only falls back with none.
204
218
  if (isPiProcess() && piHasUpstreamPonytail())
@@ -32,8 +32,13 @@ async function openDashboard(pi, ctx) {
32
32
  const exported = await pi.exec?.('tersio', ['dashboard', '--export', file], { cwd: ctx?.cwd });
33
33
  if (!exported || exported.code !== 0)
34
34
  throw new Error((exported?.stderr || 'export failed').trim());
35
- const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
36
- await pi.exec?.(openCmd, [file], { cwd: ctx?.cwd });
35
+ // `start` is a cmd builtin, not an executable: spawning it by name fails with ENOENT.
36
+ const [openCmd, openArgs] = process.platform === 'darwin'
37
+ ? ['open', [file]]
38
+ : process.platform === 'win32'
39
+ ? [process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', 'start', '', file]]
40
+ : ['xdg-open', [file]];
41
+ await pi.exec?.(openCmd, openArgs, { cwd: ctx?.cwd });
37
42
  ctx?.ui?.notify?.(`tersio dashboard: opened in your browser.`, 'info');
38
43
  }
39
44
  catch (e) {
@@ -6,7 +6,8 @@ import { getSharedComboState, hasModeState, isOmpSubagentPrompt, lastCustomValue
6
6
  import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
7
7
  import { announceStatus } from '../shared/status.js';
8
8
  import { readPstackDefault } from '../shared/plugin-settings.js';
9
- const PSTACK_DIR = dirname(fileURLToPath(import.meta.url));
9
+ import { ruleDir } from '../shared/utils.js';
10
+ const PSTACK_DIR = ruleDir('pstack', dirname(fileURLToPath(import.meta.url)));
10
11
  const RULE_FILE = 'rule.md';
11
12
  const DEFAULT_MODE = 'off';
12
13
  // The body is immutable for the process, so read it once; reinstalling needs a restart.
@@ -22,7 +23,9 @@ function buildInstruction() {
22
23
  catch {
23
24
  return `pstack mode requested, but ${RULE_FILE} is missing. Run: tersio doctor --fix extensions`;
24
25
  }
25
- cachedInstruction = `pstack mode active for this session.\n${body}`;
26
+ // The rule body names the files by relative path, so one line gives the directory they sit in.
27
+ const home = `Leaves, playbooks, and skills live at: ${PSTACK_DIR} — \`principles/<title>.md\`, \`playbooks/<title>.md\`, \`skills/<name>/SKILL.md\`, \`references/<title>.md\`.`;
28
+ cachedInstruction = `pstack mode active for this session.\n${body}\n${home}`;
26
29
  return cachedInstruction;
27
30
  }
28
31
  function resolveMode(entries, fallback = DEFAULT_MODE) {
@@ -5,6 +5,8 @@ import { join } from 'node:path';
5
5
  import { resolveRtkBinary } from '../shared/utils.js';
6
6
  import { readRtkDefault } from '../shared/plugin-settings.js';
7
7
  import { getSharedComboState, lastCustomValue, sessionEntries } from '../shared/session-state.js';
8
+ import { appendUsage } from '../shared/usage-ledger.js';
9
+ import { isPiHost } from '../shared/host.js';
8
10
  /** Tool name to `rtk pipe --filter` name. Exported so the OpenCode host reuses one map. */
9
11
  export const FILTER_BY_TOOL = new Map([
10
12
  ['grep', 'grep'],
@@ -14,8 +16,13 @@ export const FILTER_BY_TOOL = new Map([
14
16
  /** Below this the rtk spawn costs more than the filter saves. */
15
17
  export const MIN_FILTER_BYTES = 1200;
16
18
  const FILTER_TIMEOUT_MS = 5000;
17
- /** Absolute, so a minimal PATH in the host cannot break the spawn. */
18
- const SHELL = '/bin/sh';
19
+ // A shell that understands `<`, resolved per platform; separate flags, because Node quotes each argv element and cmd.exe rejects `"/d /s /c"`.
20
+ /** The pipe's shell, and whether the whole command needs an extra quote pair: `cmd.exe /s` strips the outer one, so a quoted binary and redirect path must be wrapped. */
21
+ export function filterShell() {
22
+ return process.platform === 'win32'
23
+ ? { shell: process.env.ComSpec || 'cmd.exe', flags: ['/d', '/s', '/c'], wrap: true }
24
+ : { shell: '/bin/sh', flags: ['-c'], wrap: false };
25
+ }
19
26
  function modeOf(value) {
20
27
  if (value === true)
21
28
  return true;
@@ -46,8 +53,10 @@ export async function filterThroughRtk(exec, request) {
46
53
  dir = mkdtempSync(join(tmpdir(), 'tersio-rtk-'));
47
54
  const file = join(dir, 'in.txt');
48
55
  writeFileSync(file, text, 'utf8');
49
- // `rtk pipe` reads stdin only and execFile has no stdin, so one redirect.
50
- const result = await exec(SHELL, ['-c', `"${bin}" pipe -f ${filter} < "${file}"`], { signal, timeout: FILTER_TIMEOUT_MS });
56
+ // `rtk pipe` reads stdin only and execFile has no stdin, so one redirect through the platform shell.
57
+ const { shell, flags, wrap } = filterShell();
58
+ const line = `"${bin}" pipe -f ${filter} < "${file}"`;
59
+ const result = await exec(shell, [...flags, wrap ? `"${line}"` : line], { signal, timeout: FILTER_TIMEOUT_MS });
51
60
  if (result.code !== 0)
52
61
  return null;
53
62
  const out = result.stdout;
@@ -63,11 +72,43 @@ export async function filterThroughRtk(exec, request) {
63
72
  rmSync(dir, { recursive: true, force: true });
64
73
  }
65
74
  }
75
+ /** Ask rtk for the rewritten command; null on any failure, so the original runs. */
76
+ async function rewriteCommand(exec, bin, command, signal) {
77
+ try {
78
+ const result = await exec(bin, ['rewrite', command], { timeout: FILTER_TIMEOUT_MS, signal });
79
+ const out = result.stdout.trim();
80
+ return out || null;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
66
86
  export default function rtkFilterExtension(pi) {
67
87
  const bin = resolveRtkBinary();
68
88
  const exec = pi.exec;
69
89
  if (!bin || !exec)
70
90
  return;
91
+ // omp records the post-hook command in its transcript, so only pi leaves an audit row here:
92
+ // opencode rewrites in its own plugin hook, and an omp row would double-count the transcript.
93
+ const auditHost = isPiHost(pi) ? 'pi' : null;
94
+ // Registered synchronously, unlike the rtk-init hook, whose async version probe can miss the first call of a fast session.
95
+ pi.on('tool_call', async (event, ctx) => {
96
+ const call = (event ?? {});
97
+ const input = call.input;
98
+ if (call.toolName !== 'bash' || !input)
99
+ return;
100
+ const command = input.command;
101
+ if (typeof command !== 'string' || !command.trim() || command.startsWith('rtk '))
102
+ return;
103
+ if (!rtkActive(sessionEntries(ctx)))
104
+ return;
105
+ const rewritten = await rewriteCommand(exec, bin, command, ctx?.signal);
106
+ if (!rewritten || rewritten === command)
107
+ return;
108
+ input.command = rewritten;
109
+ if (auditHost)
110
+ appendUsage('rtk-audit', `${auditHost} ${command}`);
111
+ });
71
112
  pi.on('tool_result', async (event, ctx) => {
72
113
  const filter = FILTER_BY_TOOL.get(String(event?.toolName ?? '').toLowerCase());
73
114
  if (!filter || event?.isError)
@@ -1,7 +1,7 @@
1
1
  import { getSharedComboState, hasModeState, isOmpSubagentPrompt, lastCustomValue, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, systemPromptIncludes } from '../shared/session-state.js';
2
2
  import { injectPromptText, onHostEvent, setExtensionLabel, stringArrayToolParams } from '../shared/host.js';
3
3
  import { readRtkDefault } from '../shared/plugin-settings.js';
4
- import { resolveRtkBinary } from '../shared/utils.js';
4
+ import { resolveRtkBinary, shimCommand } from '../shared/utils.js';
5
5
  import { announceStatus } from '../shared/status.js';
6
6
  const DEFAULT_ENABLED = false;
7
7
  function asBoolean(value) {
@@ -22,8 +22,11 @@ function setRtkProcessEnabled(enabled) {
22
22
  else
23
23
  process.env.RTK_DISABLED = '1';
24
24
  }
25
- // rtk's contract: name the rewritten surface, never the binary — a hand-typed `rtk` fights the rewrite.
26
- const RTK_PROMPT = `RTK guidance active. What you run is condensed automatically: searches (grep, rg), listings (ls, find, cat as read), package and test runs (bun, npm run, pnpm run), version control (git), and type and lint checks (tsc, eslint). Every signal kept, costly noise dropped. Treat a condensed result as complete. Re-run something unfiltered only when its result is empty, garbled, or contradicts its exit code. A command with no condensed form passes through unchanged.`;
25
+ // rtk's contract: name the rewritten surface first and the binary second — a hand-typed `rtk` in front of a
26
+ // rewritten command fights the rewrite, and the direct calls cover only what the rewrite cannot express.
27
+ const RTK_PROMPT = `RTK guidance active. What you run is condensed automatically, in the host's own rewritten form: searches (grep, rg, ast-grep), listings (ls, find, tree), reads (cat), package and test runs (bun, npm, pnpm, npx, cargo, go, pytest, vitest, jest, ctest, tsc), version control and forges (git, gh, glab), containers and clusters (docker, kubectl), cloud and platform CLIs (aws, oc, dotnet), PHP tooling (phpunit, pest, phpstan, pint, ecs), lint and format (eslint, ruff, mypy, prettier, sqlfluff, playwright, prisma, next), and data tools (jq, curl, wget, psql, wc, make, tail, docker logs). The runner's own subcommand, such as \`rtk bun run test\`, keeps the failing test name, the assertion, and the file and line. Every signal kept, costly noise dropped. Treat a condensed result as complete. Re-run something unfiltered only when its result is empty, garbled, or contradicts its exit code.
28
+
29
+ Call rtk directly for what the rewrite cannot express: \`rtk read <file> --level aggressive\` for an overview, \`--max-lines N\` for a preview, \`rtk smart <file>\` for a two-line summary, \`rtk summary <cmd>\` for a heuristic one, \`rtk err <cmd>\` for errors only, \`rtk log <file>\` to dedupe logs, \`rtk json <file>\` with \`--keys-only\` for shape, plus \`rtk deps\`, \`rtk diff\`, \`rtk env\`, \`rtk ast-grep\`, and \`rtk test <cmd>\` for a runner without its own form. A command with no condensed form passes through unchanged, so wrap it: \`rtk err node build.js\`, and \`rtk find -name '*.ts'\` in place of fd. Never type \`rtk\` in front of a command listed above.`;
27
30
  export default function rtkSessionExtension(pi) {
28
31
  let enabled = DEFAULT_ENABLED;
29
32
  function fail(text) {
@@ -65,30 +68,37 @@ export default function rtkSessionExtension(pi) {
65
68
  pi.registerTool?.({
66
69
  name: 'rtk_run',
67
70
  label: 'RTK Run',
68
- description: 'Run the installed `rtk` binary for compact command output when RTK mode is enabled.',
71
+ description: 'Run a shell command through the installed `rtk` binary when RTK mode is on, and run it plainly when RTK mode is off.',
69
72
  parameters: stringArrayToolParams(pi, 'args', {
70
73
  minItems: 1,
71
74
  description: "Arguments passed to rtk, e.g. ['git','status'] or ['read','src/index.ts']",
72
75
  }),
73
76
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
74
77
  // A subagent's own flag sits at its start-up default, so honour shared state too.
75
- if (!(enabled || getSharedComboState().rtk === 'on'))
76
- return fail('RTK mode is off. Run /rtk on for this session, or use bash explicitly.');
78
+ const rtkOn = enabled || getSharedComboState().rtk === 'on';
77
79
  if (typeof pi.exec !== 'function')
78
80
  return fail('This host cannot run rtk.');
79
- onUpdate?.({ content: [{ type: 'text', text: `rtk ${params.args.join(' ')}` }], details: { phase: 'start' } });
81
+ // RTK off: run the command as given rather than answering "mode is off". The tool is the only one left under --no-tools, and a refusal there made the model spend turns re-calling it. rtk is the condenser, never the command.
82
+ const cwd = ctx?.cwd || pi.cwd;
83
+ const bin = rtkOn ? resolveRtkBinary() : null;
84
+ if (rtkOn && bin === null)
85
+ return fail('RTK mode is on but the rtk binary is not installed. Run: tersio update');
86
+ const argv = params.args;
87
+ onUpdate?.({ content: [{ type: 'text', text: `${bin === null ? '' : 'rtk '}${argv.join(' ')}` }], details: { phase: 'start' } });
80
88
  try {
81
89
  // A GUI-launched host may lack ~/.bun/bin on PATH, so prefer the resolved path.
82
- const result = await pi.exec(resolveRtkBinary() ?? 'rtk', params.args, { signal, cwd: ctx?.cwd || pi.cwd });
90
+ // A `.cmd` shim cannot be spawned directly on Windows, so it runs through the shell.
91
+ const shim = shimCommand(bin ?? argv[0], argv);
92
+ const result = await pi.exec(shim.cmd, shim.args, { signal, cwd });
83
93
  const text = [result.stdout, result.stderr].filter(Boolean).join('\n');
84
94
  return {
85
95
  isError: result.code !== 0,
86
- content: [{ type: 'text', text: text || `rtk exited ${result.code}` }],
87
- details: { code: result.code, killed: result.killed, enabled },
96
+ content: [{ type: 'text', text: text || `exited ${result.code}` }],
97
+ details: { code: result.code, killed: result.killed, enabled, condensed: bin !== null },
88
98
  };
89
99
  }
90
100
  catch (e) {
91
- return fail(`rtk could not run: ${e instanceof Error ? e.message : String(e)}`);
101
+ return fail(`${bin === null ? argv[0] : 'rtk'} could not run: ${e instanceof Error ? e.message : String(e)}`);
92
102
  }
93
103
  },
94
104
  });
@@ -11,7 +11,8 @@ import comboToggleExtension from './combo/index.js';
11
11
  import tersioCommandsExtension from './commands/index.js';
12
12
  import { getSharedComboState } from './shared/session-state.js';
13
13
  import { formatStatus } from './shared/status.js';
14
- import { resolveRtkBinary } from './shared/utils.js';
14
+ import { resolveRtkBinary, shimCommand } from './shared/utils.js';
15
+ import { appendUsage } from './shared/usage-ledger.js';
15
16
  // The npm name, so the plugin list reads the same as the OMP and pi installs.
16
17
  const plugin = {
17
18
  id: '@krtclcdy/tersio',
@@ -217,8 +218,12 @@ const plugin = {
217
218
  if (!rtkActive(sid === undefined ? undefined : sessionEntriesOf(sid)))
218
219
  return;
219
220
  const rewritten = await rewriteCommand(input.command);
220
- if (rewritten)
221
- input.command = rewritten;
221
+ if (!rewritten || rewritten === input.command)
222
+ return;
223
+ const original = input.command;
224
+ input.command = rewritten;
225
+ // The transcript stores the pre-hook command, so the rewrite leaves its own row.
226
+ appendUsage('rtk-audit', `opencode ${original}`);
222
227
  });
223
228
  // Built-in grep and glob output never reaches the bash hook; mutate result.content in place.
224
229
  try {
@@ -253,16 +258,20 @@ const plugin = {
253
258
  };
254
259
  /** execFile with the promise shape `filterThroughRtk` expects. */
255
260
  function execRtk(cmd, args, opts) {
256
- return new Promise((resolve) => {
257
- execFile(cmd, args, { signal: opts?.signal, timeout: opts?.timeout }, (error, stdout) => {
258
- const out = stdout ?? '';
259
- if (!error)
260
- return resolve({ stdout: out, code: 0 });
261
- // A non-numeric code is a spawn failure (ENOENT, EACCES, killed) and must not read as success.
262
- const raw = error.code;
263
- resolve({ stdout: out, code: typeof raw === 'number' ? raw : 1 });
264
- });
261
+ const { promise, resolve } = Promise.withResolvers();
262
+ // cmd.exe parses its own quoting, so the command string must reach it verbatim: Node's default escaping turns the inner quotes into `\"` and the redirect then fails.
263
+ const shim = shimCommand(cmd, args);
264
+ execFile(shim.cmd, shim.args, { signal: opts?.signal, timeout: opts?.timeout, windowsVerbatimArguments: process.platform === 'win32' }, (error, stdout) => {
265
+ const out = stdout ?? '';
266
+ if (!error) {
267
+ resolve({ stdout: out, code: 0 });
268
+ return;
269
+ }
270
+ // A non-numeric code is a spawn failure (ENOENT, EACCES, killed) and must not read as success.
271
+ const raw = error.code;
272
+ resolve({ stdout: out, code: typeof raw === 'number' ? raw : 1 });
265
273
  });
274
+ return promise;
266
275
  }
267
276
  /** `rtk rewrite` exit codes: 0 rewrite found, 1 no equivalent, 3 advisory; the code is the contract. */
268
277
  const RTK_REWRITE_FOUND = 0;
@@ -326,23 +335,29 @@ function rewriteCommand(command) {
326
335
  const bin = resolveRtkBinary();
327
336
  if (!bin)
328
337
  return Promise.resolve(null);
329
- return new Promise((resolve) => {
330
- execFile(bin, ['rewrite', command], { timeout: 5000 }, (error, stdout) => {
331
- // A kill or timeout means untrustworthy output; pass the command through.
332
- const err = error;
333
- if (err && (err.killed || err.signal))
334
- return resolve(null);
335
- const code = typeof err?.code === 'number' ? err.code : RTK_REWRITE_FOUND;
336
- // Exit 1 is the only "no equivalent" answer, so anything else with output rewrites.
337
- if (code === 1)
338
- return resolve(null);
339
- const out = String(stdout).trim();
340
- if (!out || out === command)
341
- return resolve(null);
342
- if (code !== RTK_REWRITE_FOUND && code !== RTK_REWRITE_ADVISORY)
343
- return resolve(null);
344
- resolve(out);
345
- });
338
+ const { promise, resolve } = Promise.withResolvers();
339
+ // A `.cmd` shim cannot be spawned directly on Windows, so it runs through the shell.
340
+ const shim = shimCommand(bin, ['rewrite', command]);
341
+ execFile(shim.cmd, shim.args, { timeout: 5000 }, (error, stdout) => {
342
+ // A kill or timeout means untrustworthy output; pass the command through.
343
+ const err = error;
344
+ if (err && (err.killed || err.signal)) {
345
+ resolve(null);
346
+ return;
347
+ }
348
+ const code = typeof err?.code === 'number' ? err.code : RTK_REWRITE_FOUND;
349
+ // Exit 1 is the only "no equivalent" answer, so anything else with output rewrites.
350
+ if (code === 1) {
351
+ resolve(null);
352
+ return;
353
+ }
354
+ const out = String(stdout).trim();
355
+ if (!out || out === command || (code !== RTK_REWRITE_FOUND && code !== RTK_REWRITE_ADVISORY)) {
356
+ resolve(null);
357
+ return;
358
+ }
359
+ resolve(out);
346
360
  });
361
+ return promise;
347
362
  }
348
363
  export default plugin;
@@ -1,8 +1,8 @@
1
1
  // Read-only aggregate over the usage store; missing SQLite, table, or metric reads as zeros.
2
2
  import fs from 'node:fs';
3
- import os from 'node:os';
4
3
  import path from 'node:path';
5
4
  import { sqliteRead } from './sqlite.js';
5
+ import { homeDir } from './utils.js';
6
6
  import { usageDbPath } from './usage-store.js';
7
7
  export const RANGES = ['1h', '24h', '7d', '30d', '90d', 'all'];
8
8
  export function isRangeKey(v) {
@@ -65,7 +65,7 @@ export function statsDbPath() {
65
65
  }
66
66
  /** Quota snapshots. Only omp records these, so this stays omp's own database. */
67
67
  export function agentDbPath() {
68
- return process.env.TERSIO_AGENT_SESSIONS_DB ?? path.join(os.homedir(), '.omp', 'agent', 'agent.db');
68
+ return process.env.TERSIO_AGENT_SESSIONS_DB ?? path.join(homeDir(), '.omp', 'agent', 'agent.db');
69
69
  }
70
70
  /** The row shape every query groups by: a message joined to its file, since path and facts live apart. */
71
71
  const SOURCE = 'messages m JOIN files f ON f.id = m.file_id';
@@ -94,14 +94,19 @@ function providerExpr(model) {
94
94
  .join(' ');
95
95
  return `CASE WHEN instr(${model}, '/') = 0 THEN '' ELSE CASE ${named} ${cases} ELSE ${named} END END`;
96
96
  }
97
- /** SQL: the project a transcript belongs to, named by its session folder. A flat session tree uses its data dir. */
97
+ /** SQL: the project from a transcript's session folder; the separator follows the stored path, since a Windows run stores `\`. */
98
98
  function projectExpr(file) {
99
- const marker = `instr(${file}, '/sessions/')`;
100
- const seg = `substr(${file}, ${marker} + 10, instr(substr(${file}, ${marker} + 10) || '/', '/') - 1)`;
101
- const base = `replace(${file}, rtrim(${file}, replace(${file}, '/', '')), '')`;
99
+ // Position of the session marker under either separator; 0 when the path has none.
100
+ const posAt = `instr(${file}, '/sessions/')`;
101
+ const posWin = `instr(${file}, '\\sessions\\')`;
102
+ const at = `CASE WHEN ${posAt} > 0 THEN ${posAt} ELSE ${posWin} END`;
103
+ // Which separator to slice on: the one the matched marker used, else the one the path uses.
104
+ const sep = `CASE WHEN ${posAt} > 0 THEN '/' WHEN ${posWin} > 0 THEN '\\' WHEN instr(${file}, '\\') > 0 THEN '\\' ELSE '/' END`;
105
+ const seg = `substr(${file}, ${at} + 10, instr(substr(${file}, ${at} + 10) || ${sep}, ${sep}) - 1)`;
106
+ const base = `replace(${file}, rtrim(${file}, replace(${file}, ${sep}, '')), '')`;
102
107
  const dir = `substr(${file}, 1, length(${file}) - length(${base}) - 1)`;
103
- const dirBase = `replace(${dir}, rtrim(${dir}, replace(${dir}, '/', '')), '')`;
104
- return `CASE WHEN ${marker} > 0 THEN ${seg} ELSE ${dirBase} END`;
108
+ const dirBase = `replace(${dir}, rtrim(${dir}, replace(${dir}, ${sep}, '')), '')`;
109
+ return `CASE WHEN ${at} > 0 THEN ${seg} ELSE ${dirBase} END`;
105
110
  }
106
111
  /** Every column a query reads, mapped to the store's schema in one place. */
107
112
  const COL = {
@@ -436,18 +441,20 @@ function foldTraces(rows) {
436
441
  .map((row) => ({ ...row, models: row.models.toSorted() }))
437
442
  .toSorted((a, b) => b.lastTs - a.lastTs);
438
443
  }
439
- /** The root session file for a transcript, which is itself when it is already a root. */
444
+ /** The root session file for a transcript, which is itself when it is already a root.
445
+ * Splits on whichever separator the stored path uses: a Windows run writes `\`. */
440
446
  export function rootSessionFile(sessionFile) {
441
- const marker = '/sessions/';
447
+ const marker = sessionFile.includes('/sessions/') ? '/sessions/' : '\\sessions\\';
442
448
  const at = sessionFile.indexOf(marker);
443
449
  if (at < 0)
444
450
  return sessionFile;
445
451
  const base = sessionFile.slice(0, at + marker.length);
446
- const parts = sessionFile.slice(at + marker.length).split('/');
452
+ const parts = sessionFile.slice(at + marker.length).split(marker[0]);
447
453
  // `<project>/<root>.jsonl` is a root; `<project>/<root>/<child>.jsonl` belongs to that root.
448
454
  if (parts.length < 3)
449
455
  return sessionFile;
450
- return `${base}${parts[0]}/${parts[1]}.jsonl`;
456
+ // Rejoined with the same separator, so a child's root matches its root file exactly.
457
+ return `${base}${parts[0]}${marker[0]}${parts[1]}.jsonl`;
451
458
  }
452
459
  /** Same normalization as the reference: ids and counters collapse, an HTTP status does not. */
453
460
  const UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
@@ -900,7 +907,8 @@ function stopState(stopReason) {
900
907
  const AGENT_TOOLS = new Set(['task', 'subagent']);
901
908
  /** One transcript as spans, tracks and markers: a model span runs `ts - duration` to `ts`. */
902
909
  export function readSessionTrace(sessionFile, limit = TRACE_SPAN_LIMIT) {
903
- const project = sessionFile.split('/sessions/')[1]?.split('/')[0] ?? '';
910
+ // Either separator: a Windows host writes `<drive>\...\.omp\agent\sessions\<project>\<file>`.
911
+ const project = sessionFile.split(/[/\\]sessions[/\\]/)[1]?.split(/[/\\]/)[0] ?? '';
904
912
  const empty = {
905
913
  sessionFile, project, title: '', cwd: project, startedAt: 0, endedAt: 0,
906
914
  tracks: [], markers: [], summary: emptyTraceSummary(), toolStats: [], truncated: false,
@@ -80,15 +80,16 @@ function asPrice(v) {
80
80
  }
81
81
  let liveCache = null;
82
82
  export function loadLivePrices() {
83
- // One stat per call beats one full parse: the recent-rows map prices thousands of rows.
83
+ // One stat per call beats a full parse; the key carries size and mtime, because Windows rounds a rapid rewrite's mtime to one value.
84
84
  try {
85
85
  const file = pricesCachePath();
86
- const mtime = fs.statSync(file).mtimeMs;
87
- if (liveCache && liveCache.path === file && liveCache.mtime === mtime)
86
+ const stat = fs.statSync(file);
87
+ if (liveCache && liveCache.path === file && liveCache.mtime === stat.mtimeMs && liveCache.size === stat.size) {
88
88
  return liveCache.prices;
89
+ }
89
90
  const prices = readLivePrices();
90
91
  if (prices)
91
- liveCache = { path: file, mtime, prices };
92
+ liveCache = { path: file, mtime: stat.mtimeMs, size: stat.size, prices };
92
93
  return prices;
93
94
  }
94
95
  catch {
@@ -0,0 +1,80 @@
1
+ // Eligible heads come from rtk's own contract: `rtk rewrite <probe>` exits 0 or 3 when rtk handles the command, 1 when it has no equivalent.
2
+ import { execFileSync } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { normalizeRtkVersion, shimCommand, tersioDataPath } from './utils.js';
6
+ /** Verified against rtk 1.0.1; the drift test fails when rtk's set moves. */
7
+ export const FALLBACK_ELIGIBLE_HEADS = [
8
+ 'rtk', 'git', 'gh', 'glab', 'docker', 'kubectl', 'psql', 'aws', 'bunx', 'npx',
9
+ 'pytest', 'mypy', 'cat', 'grep', 'rg', 'find', 'tree', 'diff', 'ls', 'ast-grep',
10
+ 'wget', 'prettier', 'playwright', 'prisma', 'jest', 'vitest', 'ctest', 'lint',
11
+ 'wc', 'tsc', 'curl', 'cargo', 'npm', 'pnpm', 'bun', 'go', 'ruff', 'next',
12
+ 'dotnet', 'oc', 'jq', 'make',
13
+ ];
14
+ const PROBES = {
15
+ git: 'git log', gh: 'gh pr list', glab: 'glab mr list', docker: 'docker ps',
16
+ kubectl: 'kubectl get pods', psql: 'psql --version', aws: 'aws --version',
17
+ bunx: 'bunx --version', npx: 'npx --version', pytest: 'pytest --version',
18
+ mypy: 'mypy --version', cat: 'cat README.md', grep: 'grep rtk README.md',
19
+ rg: 'rg rtk README.md', find: 'find . -name x', tree: 'tree .', diff: 'diff a b',
20
+ ls: 'ls -la', 'ast-grep': 'ast-grep --version', wget: 'wget --version',
21
+ prettier: 'prettier --version', playwright: 'playwright --version', prisma: 'prisma --version',
22
+ jest: 'jest --version', vitest: 'vitest --version', ctest: 'ctest --version',
23
+ lint: 'lint .', wc: 'wc -l x', tsc: 'tsc --version', curl: 'curl --version',
24
+ cargo: 'cargo test', npm: 'npm run build', pnpm: 'pnpm run build', bun: 'bun run test',
25
+ go: 'go test ./...', ruff: 'ruff check .', next: 'next build', dotnet: 'dotnet build',
26
+ oc: 'oc get pods', jq: 'jq . x', make: 'make',
27
+ };
28
+ function probe(bin, command) {
29
+ try {
30
+ const shim = shimCommand(bin, ['rewrite', command]);
31
+ const out = execFileSync(shim.cmd, shim.args, { encoding: 'utf8', timeout: 5000, windowsHide: true });
32
+ return out.trim().length > 0;
33
+ }
34
+ catch (e) {
35
+ // Non-zero exit throws; exit 3 still prints a rewrite, exits 1 and 2 do not.
36
+ const err = e;
37
+ if (err.status !== 0 && err.status !== 3)
38
+ return false;
39
+ return String(err.stdout ?? '').trim().length > 0;
40
+ }
41
+ }
42
+ /** One set of heads for this rtk build, read straight from its rewrite contract. */
43
+ export function deriveEligibleHeads(bin) {
44
+ const heads = new Set(['rtk']);
45
+ for (const [head, command] of Object.entries(PROBES)) {
46
+ if (probe(bin, command))
47
+ heads.add(head);
48
+ }
49
+ return heads;
50
+ }
51
+ function rtkVersion(bin) {
52
+ try {
53
+ const shim = shimCommand(bin, ['--version']);
54
+ return normalizeRtkVersion(execFileSync(shim.cmd, shim.args, { encoding: 'utf8', timeout: 5000, windowsHide: true }));
55
+ }
56
+ catch {
57
+ return '';
58
+ }
59
+ }
60
+ /** Heads rtk rewrites, cached per binary version; without a binary, or on any read failure, the verified fallback stands. */
61
+ export function eligibleHeads(bin) {
62
+ if (!bin)
63
+ return new Set(FALLBACK_ELIGIBLE_HEADS);
64
+ const version = rtkVersion(bin);
65
+ const file = tersioDataPath('rtk-eligible.json', 'rtk-eligible.json');
66
+ try {
67
+ const cached = JSON.parse(fs.readFileSync(file, 'utf8'));
68
+ if (cached.version === version && Array.isArray(cached.heads) && cached.heads.every((h) => typeof h === 'string')) {
69
+ return new Set(cached.heads);
70
+ }
71
+ }
72
+ catch { /* derive below */ }
73
+ const heads = deriveEligibleHeads(bin);
74
+ try {
75
+ fs.mkdirSync(path.dirname(file), { recursive: true });
76
+ fs.writeFileSync(file, `${JSON.stringify({ version, heads: [...heads] })}\n`, 'utf8');
77
+ }
78
+ catch { /* the cache is best-effort */ }
79
+ return heads;
80
+ }
@@ -1,7 +1,7 @@
1
- // Savings read from rtk history, never estimated; modes bench in docs/BENCHMARK.md.
1
+ // Savings read from rtk history, never estimated; modes bench in docs/BENCHMARK_GUIDE.md.
2
2
  import fs from 'node:fs';
3
- import os from 'node:os';
4
3
  import path from 'node:path';
4
+ import { rtkDirs } from './utils.js';
5
5
  import { sqliteRead } from './sqlite.js';
6
6
  const EMPTY = { commands: 0, saved: 0, input: 0, avgPct: 0, totalMs: 0, byCommand: [] };
7
7
  // The table shows one clipped cell, so bound the command before it reaches /data.json.
@@ -12,11 +12,7 @@ export function rtkDbPath() {
12
12
  const override = process.env.TERSIO_RTK_DB;
13
13
  if (override)
14
14
  return override;
15
- if (process.platform === 'darwin') {
16
- return path.join(os.homedir(), 'Library', 'Application Support', 'rtk', 'history.db');
17
- }
18
- const xdg = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
19
- return path.join(xdg, 'rtk', 'history.db');
15
+ return path.join(rtkDirs().data, 'history.db');
20
16
  }
21
17
  function query(db, sql) {
22
18
  return sqliteRead(db, sql)
@@ -1,14 +1,4 @@
1
- // One place that runs SQL against a database file.
2
- //
3
- // Bun and Node 22.5+ carry SQLite in process, so the `sqlite3` binary is a fallback rather than a
4
- // requirement: hosts without it kept silently reading nothing. Reads come back exactly as the sqlite3
5
- // CLI printed them -- rows on their own lines, cells joined by tabs, NULL an empty cell, one trailing
6
- // newline -- because every caller already parses that shape. The engine rungs take one statement per
7
- // read, so a caller with several queries calls once per query.
8
- //
9
- // The write path leaves the journal mode alone. The store's own schema sets WAL so a reader (the
10
- // dashboard) never blocks a writer, and changing the mode needs exclusive access: with a reader holding
11
- // the file the pragma fails, which is a failure reported after the caller's rows have already committed.
1
+ // One place that runs SQL: Bun or Node 22.5+ in process, the `sqlite3` binary as the fallback, and rows in its CLI shape for every caller. The write path leaves the journal mode alone (see docs/ERRORS.md).
12
2
  import { execFileSync } from 'node:child_process';
13
3
  import fs from 'node:fs';
14
4
  import { createRequire } from 'node:module';
@@ -51,8 +41,7 @@ function engine() {
51
41
  catch { /* not Bun, or no SQLite in this build */ }
52
42
  try {
53
43
  const { DatabaseSync } = requireOptional('node:sqlite');
54
- // Rows arrive keyed by column name unless the statement returns arrays. A duplicate name collapses
55
- // its key and shifts every later cell, so a Node that cannot return arrays is no engine at all.
44
+ // Rows arrive keyed by column name unless the statement returns arrays: a duplicate name collapses its key and shifts every later cell, so a Node that cannot return arrays is no engine.
56
45
  const probe = new DatabaseSync(':memory:');
57
46
  try {
58
47
  if (typeof probe.prepare('SELECT 1').setReturnArrays !== 'function')
@@ -26,8 +26,7 @@ function statusUi(ctx) {
26
26
  let remembered;
27
27
  let lastText;
28
28
  let lastUi;
29
- // Paint the host footer bar; same text on the same UI is skipped, a new ctx repaints it.
30
- // One key only: the combo line already names the caveman mode, and a second key renders as a second footer line.
29
+ // Paint the host footer bar; same text on the same UI is skipped. One key only: the combo line already names the caveman mode, and a second key renders as a second footer line.
31
30
  export function announceStatus(ctx) {
32
31
  const state = getSharedComboState();
33
32
  const text = formatStatus(state);