@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.
- package/README.md +43 -13
- package/dashboard/dist/index.html +1 -1
- package/dist/cli/banner.js +1 -2
- package/dist/cli/common.js +26 -4
- package/dist/cli/dashboard.js +7 -2
- package/dist/cli/doctor-fix.js +12 -7
- package/dist/cli/doctor.js +64 -15
- package/dist/cli/hosts.js +1 -2
- package/dist/cli/install.js +12 -7
- package/dist/cli/manifest.js +25 -5
- package/dist/cli/profile.js +28 -3
- package/dist/cli/rtk-wiring.js +13 -10
- package/dist/cli/uninstall.js +1 -1
- package/dist/cli/usage.js +5 -0
- package/dist/extensions/caveman/index.js +4 -3
- package/dist/extensions/combo/index.js +21 -7
- package/dist/extensions/commands/index.js +7 -2
- package/dist/extensions/pstack/index.js +5 -2
- package/dist/extensions/rtk/filter.js +45 -4
- package/dist/extensions/rtk/session.js +21 -11
- package/dist/extensions/server.js +44 -29
- package/dist/extensions/shared/agent-stats.js +21 -13
- package/dist/extensions/shared/pricing.js +5 -4
- package/dist/extensions/shared/rtk-eligible.js +80 -0
- package/dist/extensions/shared/rtk-gain.js +3 -7
- package/dist/extensions/shared/sqlite.js +2 -13
- package/dist/extensions/shared/status.js +1 -2
- package/dist/extensions/shared/usage-ledger.js +196 -29
- package/dist/extensions/shared/utils.js +33 -0
- package/dist/extensions/updater/index.js +15 -7
- package/extensions/caveman/index.ts +4 -3
- package/extensions/caveman/rule-lite.md +77 -0
- package/extensions/caveman/rule-megacave-lite.md +59 -0
- package/extensions/caveman/rule-megacave-ultra.md +59 -0
- package/extensions/caveman/rule.md +1 -1
- package/extensions/combo/index.ts +22 -7
- package/extensions/commands/index.ts +7 -2
- package/extensions/pstack/index.ts +5 -2
- package/extensions/pstack/playbooks/authoring-a-skill.md +12 -0
- package/extensions/pstack/playbooks/autonomous-run.md +13 -0
- package/extensions/pstack/playbooks/autopilot-full.md +13 -0
- package/extensions/pstack/playbooks/autopilot-stack.md +16 -0
- package/extensions/pstack/playbooks/babysit.md +23 -0
- package/extensions/pstack/playbooks/bug-fix.md +15 -0
- package/extensions/pstack/playbooks/eval.md +25 -0
- package/extensions/pstack/playbooks/feature.md +21 -0
- package/extensions/pstack/playbooks/hillclimb.md +21 -0
- package/extensions/pstack/playbooks/investigation.md +14 -0
- package/extensions/pstack/playbooks/multi-phase-plan.md +155 -0
- package/extensions/pstack/playbooks/opening-a-pr.md +34 -0
- package/extensions/pstack/playbooks/orchestrate.md +111 -0
- package/extensions/pstack/playbooks/pause-safely.md +10 -0
- package/extensions/pstack/playbooks/perf-issue.md +25 -0
- package/extensions/pstack/playbooks/prototype.md +14 -0
- package/extensions/pstack/playbooks/refactoring.md +16 -0
- package/extensions/pstack/playbooks/runtime-forensics.md +11 -0
- package/extensions/pstack/playbooks/session-pickup.md +11 -0
- package/extensions/pstack/playbooks/shipping.md +17 -0
- package/extensions/pstack/playbooks/trace-forensics.md +14 -0
- package/extensions/pstack/playbooks/visual-parity.md +11 -0
- package/extensions/pstack/playbooks/worktree-cleanup.md +12 -0
- package/extensions/pstack/principles/attack-the-premise.md +22 -0
- package/extensions/pstack/principles/boundary-discipline.md +33 -0
- package/extensions/pstack/principles/build-the-lever.md +22 -0
- package/extensions/pstack/principles/encode-lessons-in-structure.md +10 -0
- package/extensions/pstack/principles/exhaust-the-design-space.md +8 -0
- package/extensions/pstack/principles/experience-first.md +18 -0
- package/extensions/pstack/principles/explain-the-number.md +16 -0
- package/extensions/pstack/principles/fix-root-causes.md +23 -0
- package/extensions/pstack/principles/foundational-thinking.md +20 -0
- package/extensions/pstack/principles/guard-the-context-window.md +11 -0
- package/extensions/pstack/principles/laziness-protocol.md +9 -0
- package/extensions/pstack/principles/make-operations-idempotent.md +23 -0
- package/extensions/pstack/principles/migrate-callers-then-delete-legacy-apis.md +21 -0
- package/extensions/pstack/principles/minimize-reader-load.md +17 -0
- package/extensions/pstack/principles/model-the-domain.md +23 -0
- package/extensions/pstack/principles/never-block-on-the-human.md +14 -0
- package/extensions/pstack/principles/outcome-oriented-execution.md +20 -0
- package/extensions/pstack/principles/prove-it-works.md +21 -0
- package/extensions/pstack/principles/redesign-from-first-principles.md +15 -0
- package/extensions/pstack/principles/separate-before-serializing-shared-state.md +15 -0
- package/extensions/pstack/principles/sequence-verifiable-units.md +16 -0
- package/extensions/pstack/principles/subtract-before-you-add.md +17 -0
- package/extensions/pstack/principles/test-behavior-not-implementation.md +20 -0
- package/extensions/pstack/principles/type-system-discipline.md +17 -0
- package/extensions/pstack/references/bugbot-triage.md +142 -0
- package/extensions/pstack/rule.md +88 -12
- package/extensions/pstack/skills/architect/SKILL.md +78 -0
- package/extensions/pstack/skills/architect/references/design-red-flags.md +57 -0
- package/extensions/pstack/skills/architect/references/rationale-template.md +35 -0
- package/extensions/pstack/skills/architect/references/runner-prompt.md +20 -0
- package/extensions/pstack/skills/arena/SKILL.md +47 -0
- package/extensions/pstack/skills/benchmark-checklist/SKILL.md +38 -0
- package/extensions/pstack/skills/blast-radius/SKILL.md +49 -0
- package/extensions/pstack/skills/bro/SKILL.md +6 -0
- package/extensions/pstack/skills/correct/SKILL.md +31 -0
- package/extensions/pstack/skills/figure-it-out/SKILL.md +51 -0
- package/extensions/pstack/skills/how/SKILL.md +35 -0
- package/extensions/pstack/skills/how/references/explainer-prompt.md +55 -0
- package/extensions/pstack/skills/how/references/explorer-prompt.md +35 -0
- package/extensions/pstack/skills/interrogate/SKILL.md +73 -0
- package/extensions/pstack/skills/interrogate/references/code-quality-review.md +47 -0
- package/extensions/pstack/skills/interrogate/references/lead-judgment.md +58 -0
- package/extensions/pstack/skills/interrogate/references/reviewer-prompt.md +62 -0
- package/extensions/pstack/skills/interrogate/references/rubric.md +77 -0
- package/extensions/pstack/skills/no-comments/SKILL.md +23 -0
- package/extensions/pstack/skills/reflect/SKILL.md +66 -0
- package/extensions/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
- package/extensions/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
- package/extensions/pstack/skills/reflect/references/synthesizer.md +56 -0
- package/extensions/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
- package/extensions/pstack/skills/show-me-your-work/SKILL.md +81 -0
- package/extensions/pstack/skills/swarm/SKILL.md +45 -0
- package/extensions/pstack/skills/tdd/SKILL.md +41 -0
- package/extensions/pstack/skills/technical-writing/SKILL.md +105 -0
- package/extensions/pstack/skills/unslop/SKILL.md +61 -0
- package/extensions/pstack/skills/why/SKILL.md +97 -0
- package/extensions/pstack/skills/why/references/epistemics.md +71 -0
- package/extensions/pstack/skills/why/references/investigator-prompt.md +84 -0
- package/extensions/pstack/skills/why/references/source-playbook.md +17 -0
- package/extensions/pstack/skills/why/references/sources/code-archaeology.md +51 -0
- package/extensions/pstack/skills/why/references/sources/databricks.md +70 -0
- package/extensions/pstack/skills/why/references/sources/datadog.md +97 -0
- package/extensions/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
- package/extensions/pstack/skills/why/references/sources/linear.md +48 -0
- package/extensions/pstack/skills/why/references/sources/notion.md +55 -0
- package/extensions/pstack/skills/why/references/sources/sentry.md +66 -0
- package/extensions/pstack/skills/why/references/sources/slack.md +17 -0
- package/extensions/pstack/skills/why/references/synthesizer-prompt.md +129 -0
- package/extensions/rtk/filter.ts +51 -4
- package/extensions/rtk/session.ts +20 -10
- package/extensions/server.ts +31 -23
- package/extensions/shared/agent-stats.ts +21 -12
- package/extensions/shared/pricing.ts +7 -5
- package/extensions/shared/rtk-eligible.ts +79 -0
- package/extensions/shared/rtk-gain.ts +3 -7
- package/extensions/shared/sqlite.ts +2 -13
- package/extensions/shared/status.ts +1 -2
- package/extensions/shared/usage-ledger.ts +198 -32
- package/extensions/shared/utils.ts +29 -0
- package/extensions/timing/index.ts +1 -2
- package/extensions/updater/index.ts +18 -7
- package/package.json +8 -7
- package/dist/extensions/caveman/rule-megacave.md +0 -65
- package/dist/extensions/caveman/rule-ultra.md +0 -63
- package/dist/extensions/caveman/rule.md +0 -89
- package/dist/extensions/pstack/rule.md +0 -65
- package/dist/probe-catalog.js +0 -48
- package/dist/probe-catalog188.js +0 -44
package/extensions/server.ts
CHANGED
|
@@ -12,7 +12,8 @@ import comboToggleExtension from './combo/index.ts';
|
|
|
12
12
|
import tersioCommandsExtension from './commands/index.ts';
|
|
13
13
|
import { getSharedComboState } from './shared/session-state.ts';
|
|
14
14
|
import { formatStatus } from './shared/status.ts';
|
|
15
|
-
import { resolveRtkBinary } from './shared/utils.ts';
|
|
15
|
+
import { resolveRtkBinary, shimCommand } from './shared/utils.ts';
|
|
16
|
+
import { appendUsage } from './shared/usage-ledger.ts';
|
|
16
17
|
import type { ExtensionApi, ExtensionCtx, InputEvent, SessionEntry, SystemPromptEvent } from './shared/types.ts';
|
|
17
18
|
|
|
18
19
|
interface CommandConfig {
|
|
@@ -221,7 +222,11 @@ const plugin: Plugin.Plugin = {
|
|
|
221
222
|
if (sid !== undefined) await ensureSession(sid);
|
|
222
223
|
if (!rtkActive(sid === undefined ? undefined : sessionEntriesOf(sid))) return;
|
|
223
224
|
const rewritten = await rewriteCommand(input.command);
|
|
224
|
-
if (rewritten
|
|
225
|
+
if (!rewritten || rewritten === input.command) return;
|
|
226
|
+
const original = input.command;
|
|
227
|
+
input.command = rewritten;
|
|
228
|
+
// The transcript stores the pre-hook command, so the rewrite leaves its own row.
|
|
229
|
+
appendUsage('rtk-audit', `opencode ${original}`);
|
|
225
230
|
});
|
|
226
231
|
|
|
227
232
|
// Built-in grep and glob output never reaches the bash hook; mutate result.content in place.
|
|
@@ -250,15 +255,17 @@ const plugin: Plugin.Plugin = {
|
|
|
250
255
|
|
|
251
256
|
/** execFile with the promise shape `filterThroughRtk` expects. */
|
|
252
257
|
function execRtk(cmd: string, args: string[], opts?: { signal?: AbortSignal; timeout?: number }): Promise<{ stdout: string; code: number }> {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
});
|
|
258
|
+
const { promise, resolve } = Promise.withResolvers<{ stdout: string; code: number }>();
|
|
259
|
+
// 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.
|
|
260
|
+
const shim = shimCommand(cmd, args);
|
|
261
|
+
execFile(shim.cmd, shim.args, { signal: opts?.signal, timeout: opts?.timeout, windowsVerbatimArguments: process.platform === 'win32' }, (error, stdout) => {
|
|
262
|
+
const out = stdout ?? '';
|
|
263
|
+
if (!error) { resolve({ stdout: out, code: 0 }); return; }
|
|
264
|
+
// A non-numeric code is a spawn failure (ENOENT, EACCES, killed) and must not read as success.
|
|
265
|
+
const raw = (error as { code?: unknown }).code;
|
|
266
|
+
resolve({ stdout: out, code: typeof raw === 'number' ? raw : 1 });
|
|
261
267
|
});
|
|
268
|
+
return promise;
|
|
262
269
|
}
|
|
263
270
|
|
|
264
271
|
/** `rtk rewrite` exit codes: 0 rewrite found, 1 no equivalent, 3 advisory; the code is the contract. */
|
|
@@ -309,20 +316,21 @@ function bundledSkills(): ReturnType<typeof parseSkillFile>[] {
|
|
|
309
316
|
function rewriteCommand(command: string): Promise<string | null> {
|
|
310
317
|
const bin = resolveRtkBinary();
|
|
311
318
|
if (!bin) return Promise.resolve(null);
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
319
|
+
const { promise, resolve } = Promise.withResolvers<string | null>();
|
|
320
|
+
// A `.cmd` shim cannot be spawned directly on Windows, so it runs through the shell.
|
|
321
|
+
const shim = shimCommand(bin, ['rewrite', command]);
|
|
322
|
+
execFile(shim.cmd, shim.args, { timeout: 5000 }, (error, stdout) => {
|
|
323
|
+
// A kill or timeout means untrustworthy output; pass the command through.
|
|
324
|
+
const err = error as { killed?: boolean; signal?: unknown; code?: unknown } | null;
|
|
325
|
+
if (err && (err.killed || err.signal)) { resolve(null); return; }
|
|
326
|
+
const code = typeof err?.code === 'number' ? err.code : RTK_REWRITE_FOUND;
|
|
327
|
+
// Exit 1 is the only "no equivalent" answer, so anything else with output rewrites.
|
|
328
|
+
if (code === 1) { resolve(null); return; }
|
|
329
|
+
const out = String(stdout).trim();
|
|
330
|
+
if (!out || out === command || (code !== RTK_REWRITE_FOUND && code !== RTK_REWRITE_ADVISORY)) { resolve(null); return; }
|
|
331
|
+
resolve(out);
|
|
325
332
|
});
|
|
333
|
+
return promise;
|
|
326
334
|
}
|
|
327
335
|
|
|
328
336
|
export default plugin;
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { sqliteRead } from './sqlite.ts';
|
|
6
|
+
import { homeDir } from './utils.ts';
|
|
6
7
|
import { usageDbPath } from './usage-store.ts';
|
|
7
8
|
|
|
8
9
|
export type RangeKey = '1h' | '24h' | '7d' | '30d' | '90d' | 'all';
|
|
@@ -83,7 +84,7 @@ export function statsDbPath(): string {
|
|
|
83
84
|
|
|
84
85
|
/** Quota snapshots. Only omp records these, so this stays omp's own database. */
|
|
85
86
|
export function agentDbPath(): string {
|
|
86
|
-
return process.env.TERSIO_AGENT_SESSIONS_DB ?? path.join(
|
|
87
|
+
return process.env.TERSIO_AGENT_SESSIONS_DB ?? path.join(homeDir(), '.omp', 'agent', 'agent.db');
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
/** The row shape every query groups by: a message joined to its file, since path and facts live apart. */
|
|
@@ -117,14 +118,19 @@ function providerExpr(model: string): string {
|
|
|
117
118
|
return `CASE WHEN instr(${model}, '/') = 0 THEN '' ELSE CASE ${named} ${cases} ELSE ${named} END END`;
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
/** SQL: the project a transcript
|
|
121
|
+
/** SQL: the project from a transcript's session folder; the separator follows the stored path, since a Windows run stores `\`. */
|
|
121
122
|
function projectExpr(file: string): string {
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
const
|
|
123
|
+
// Position of the session marker under either separator; 0 when the path has none.
|
|
124
|
+
const posAt = `instr(${file}, '/sessions/')`;
|
|
125
|
+
const posWin = `instr(${file}, '\\sessions\\')`;
|
|
126
|
+
const at = `CASE WHEN ${posAt} > 0 THEN ${posAt} ELSE ${posWin} END`;
|
|
127
|
+
// Which separator to slice on: the one the matched marker used, else the one the path uses.
|
|
128
|
+
const sep = `CASE WHEN ${posAt} > 0 THEN '/' WHEN ${posWin} > 0 THEN '\\' WHEN instr(${file}, '\\') > 0 THEN '\\' ELSE '/' END`;
|
|
129
|
+
const seg = `substr(${file}, ${at} + 10, instr(substr(${file}, ${at} + 10) || ${sep}, ${sep}) - 1)`;
|
|
130
|
+
const base = `replace(${file}, rtrim(${file}, replace(${file}, ${sep}, '')), '')`;
|
|
125
131
|
const dir = `substr(${file}, 1, length(${file}) - length(${base}) - 1)`;
|
|
126
|
-
const dirBase = `replace(${dir}, rtrim(${dir}, replace(${dir},
|
|
127
|
-
return `CASE WHEN ${
|
|
132
|
+
const dirBase = `replace(${dir}, rtrim(${dir}, replace(${dir}, ${sep}, '')), '')`;
|
|
133
|
+
return `CASE WHEN ${at} > 0 THEN ${seg} ELSE ${dirBase} END`;
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
/** Every column a query reads, mapped to the store's schema in one place. */
|
|
@@ -825,16 +831,18 @@ function foldTraces(rows: AgentTraceRow[]): AgentTraceRow[] {
|
|
|
825
831
|
.toSorted((a, b) => b.lastTs - a.lastTs);
|
|
826
832
|
}
|
|
827
833
|
|
|
828
|
-
/** The root session file for a transcript, which is itself when it is already a root.
|
|
834
|
+
/** The root session file for a transcript, which is itself when it is already a root.
|
|
835
|
+
* Splits on whichever separator the stored path uses: a Windows run writes `\`. */
|
|
829
836
|
export function rootSessionFile(sessionFile: string): string {
|
|
830
|
-
const marker = '/sessions/';
|
|
837
|
+
const marker = sessionFile.includes('/sessions/') ? '/sessions/' : '\\sessions\\';
|
|
831
838
|
const at = sessionFile.indexOf(marker);
|
|
832
839
|
if (at < 0) return sessionFile;
|
|
833
840
|
const base = sessionFile.slice(0, at + marker.length);
|
|
834
|
-
const parts = sessionFile.slice(at + marker.length).split(
|
|
841
|
+
const parts = sessionFile.slice(at + marker.length).split(marker[0]);
|
|
835
842
|
// `<project>/<root>.jsonl` is a root; `<project>/<root>/<child>.jsonl` belongs to that root.
|
|
836
843
|
if (parts.length < 3) return sessionFile;
|
|
837
|
-
|
|
844
|
+
// Rejoined with the same separator, so a child's root matches its root file exactly.
|
|
845
|
+
return `${base}${parts[0]}${marker[0]}${parts[1]}.jsonl`;
|
|
838
846
|
}
|
|
839
847
|
|
|
840
848
|
/** Same normalization as the reference: ids and counters collapse, an HTTP status does not. */
|
|
@@ -1304,7 +1312,8 @@ const AGENT_TOOLS = new Set(['task', 'subagent']);
|
|
|
1304
1312
|
|
|
1305
1313
|
/** One transcript as spans, tracks and markers: a model span runs `ts - duration` to `ts`. */
|
|
1306
1314
|
export function readSessionTrace(sessionFile: string, limit = TRACE_SPAN_LIMIT): AgentSessionTrace {
|
|
1307
|
-
|
|
1315
|
+
// Either separator: a Windows host writes `<drive>\...\.omp\agent\sessions\<project>\<file>`.
|
|
1316
|
+
const project = sessionFile.split(/[/\\]sessions[/\\]/)[1]?.split(/[/\\]/)[0] ?? '';
|
|
1308
1317
|
const empty: AgentSessionTrace = {
|
|
1309
1318
|
sessionFile, project, title: '', cwd: project, startedAt: 0, endedAt: 0,
|
|
1310
1319
|
tracks: [], markers: [], summary: emptyTraceSummary(), toolStats: [], truncated: false,
|
|
@@ -105,16 +105,18 @@ function asPrice(v: unknown): ModelPrice | null {
|
|
|
105
105
|
return null;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
let liveCache: { path: string; mtime: number; prices: LivePrices } | null = null;
|
|
108
|
+
let liveCache: { path: string; mtime: number; size: number; prices: LivePrices } | null = null;
|
|
109
109
|
|
|
110
110
|
export function loadLivePrices(): LivePrices | null {
|
|
111
|
-
// One stat per call beats
|
|
111
|
+
// 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.
|
|
112
112
|
try {
|
|
113
113
|
const file = pricesCachePath();
|
|
114
|
-
const
|
|
115
|
-
if (liveCache && liveCache.path === file && liveCache.mtime ===
|
|
114
|
+
const stat = fs.statSync(file);
|
|
115
|
+
if (liveCache && liveCache.path === file && liveCache.mtime === stat.mtimeMs && liveCache.size === stat.size) {
|
|
116
|
+
return liveCache.prices;
|
|
117
|
+
}
|
|
116
118
|
const prices = readLivePrices();
|
|
117
|
-
if (prices) liveCache = { path: file, mtime, prices };
|
|
119
|
+
if (prices) liveCache = { path: file, mtime: stat.mtimeMs, size: stat.size, prices };
|
|
118
120
|
return prices;
|
|
119
121
|
} catch {
|
|
120
122
|
return null;
|
|
@@ -0,0 +1,79 @@
|
|
|
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.ts';
|
|
6
|
+
|
|
7
|
+
/** Verified against rtk 1.0.1; the drift test fails when rtk's set moves. */
|
|
8
|
+
export const FALLBACK_ELIGIBLE_HEADS: readonly string[] = [
|
|
9
|
+
'rtk', 'git', 'gh', 'glab', 'docker', 'kubectl', 'psql', 'aws', 'bunx', 'npx',
|
|
10
|
+
'pytest', 'mypy', 'cat', 'grep', 'rg', 'find', 'tree', 'diff', 'ls', 'ast-grep',
|
|
11
|
+
'wget', 'prettier', 'playwright', 'prisma', 'jest', 'vitest', 'ctest', 'lint',
|
|
12
|
+
'wc', 'tsc', 'curl', 'cargo', 'npm', 'pnpm', 'bun', 'go', 'ruff', 'next',
|
|
13
|
+
'dotnet', 'oc', 'jq', 'make',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const PROBES: Record<string, string> = {
|
|
17
|
+
git: 'git log', gh: 'gh pr list', glab: 'glab mr list', docker: 'docker ps',
|
|
18
|
+
kubectl: 'kubectl get pods', psql: 'psql --version', aws: 'aws --version',
|
|
19
|
+
bunx: 'bunx --version', npx: 'npx --version', pytest: 'pytest --version',
|
|
20
|
+
mypy: 'mypy --version', cat: 'cat README.md', grep: 'grep rtk README.md',
|
|
21
|
+
rg: 'rg rtk README.md', find: 'find . -name x', tree: 'tree .', diff: 'diff a b',
|
|
22
|
+
ls: 'ls -la', 'ast-grep': 'ast-grep --version', wget: 'wget --version',
|
|
23
|
+
prettier: 'prettier --version', playwright: 'playwright --version', prisma: 'prisma --version',
|
|
24
|
+
jest: 'jest --version', vitest: 'vitest --version', ctest: 'ctest --version',
|
|
25
|
+
lint: 'lint .', wc: 'wc -l x', tsc: 'tsc --version', curl: 'curl --version',
|
|
26
|
+
cargo: 'cargo test', npm: 'npm run build', pnpm: 'pnpm run build', bun: 'bun run test',
|
|
27
|
+
go: 'go test ./...', ruff: 'ruff check .', next: 'next build', dotnet: 'dotnet build',
|
|
28
|
+
oc: 'oc get pods', jq: 'jq . x', make: 'make',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function probe(bin: string, command: string): boolean {
|
|
32
|
+
try {
|
|
33
|
+
const shim = shimCommand(bin, ['rewrite', command]);
|
|
34
|
+
const out = execFileSync(shim.cmd, shim.args, { encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
35
|
+
return out.trim().length > 0;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Non-zero exit throws; exit 3 still prints a rewrite, exits 1 and 2 do not.
|
|
38
|
+
const err = e as { status?: number; stdout?: string | Buffer };
|
|
39
|
+
if (err.status !== 0 && err.status !== 3) return false;
|
|
40
|
+
return String(err.stdout ?? '').trim().length > 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** One set of heads for this rtk build, read straight from its rewrite contract. */
|
|
45
|
+
export function deriveEligibleHeads(bin: string): Set<string> {
|
|
46
|
+
const heads = new Set<string>(['rtk']);
|
|
47
|
+
for (const [head, command] of Object.entries(PROBES)) {
|
|
48
|
+
if (probe(bin, command)) heads.add(head);
|
|
49
|
+
}
|
|
50
|
+
return heads;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function rtkVersion(bin: string): string {
|
|
54
|
+
try {
|
|
55
|
+
const shim = shimCommand(bin, ['--version']);
|
|
56
|
+
return normalizeRtkVersion(execFileSync(shim.cmd, shim.args, { encoding: 'utf8', timeout: 5000, windowsHide: true }));
|
|
57
|
+
} catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Heads rtk rewrites, cached per binary version; without a binary, or on any read failure, the verified fallback stands. */
|
|
63
|
+
export function eligibleHeads(bin: string | null): ReadonlySet<string> {
|
|
64
|
+
if (!bin) return new Set(FALLBACK_ELIGIBLE_HEADS);
|
|
65
|
+
const version = rtkVersion(bin);
|
|
66
|
+
const file = tersioDataPath('rtk-eligible.json', 'rtk-eligible.json');
|
|
67
|
+
try {
|
|
68
|
+
const cached = JSON.parse(fs.readFileSync(file, 'utf8')) as { version?: unknown; heads?: unknown };
|
|
69
|
+
if (cached.version === version && Array.isArray(cached.heads) && cached.heads.every((h) => typeof h === 'string')) {
|
|
70
|
+
return new Set(cached.heads as string[]);
|
|
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
|
+
} catch { /* the cache is best-effort */ }
|
|
78
|
+
return heads;
|
|
79
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Savings read from rtk history, never estimated; modes bench in docs/
|
|
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.ts';
|
|
5
5
|
import { sqliteRead } from './sqlite.ts';
|
|
6
6
|
|
|
7
7
|
export interface RtkCommandRow {
|
|
@@ -32,11 +32,7 @@ const CMD_ONE_LINE = `replace(replace(replace(rtk_cmd, char(10), ' '), char(13),
|
|
|
32
32
|
export function rtkDbPath(): string {
|
|
33
33
|
const override = process.env.TERSIO_RTK_DB;
|
|
34
34
|
if (override) return override;
|
|
35
|
-
|
|
36
|
-
return path.join(os.homedir(), 'Library', 'Application Support', 'rtk', 'history.db');
|
|
37
|
-
}
|
|
38
|
-
const xdg = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
|
|
39
|
-
return path.join(xdg, 'rtk', 'history.db');
|
|
35
|
+
return path.join(rtkDirs().data, 'history.db');
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
function query(db: string, sql: string): string[][] {
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
// One place that runs SQL
|
|
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';
|
|
@@ -65,8 +55,7 @@ function engine(): Engine | null {
|
|
|
65
55
|
} catch { /* not Bun, or no SQLite in this build */ }
|
|
66
56
|
try {
|
|
67
57
|
const { DatabaseSync } = requireOptional('node:sqlite') as { DatabaseSync: new (db: string, opts?: { readOnly?: boolean }) => NodeDatabase };
|
|
68
|
-
// Rows arrive keyed by column name unless the statement returns arrays
|
|
69
|
-
// its key and shifts every later cell, so a Node that cannot return arrays is no engine at all.
|
|
58
|
+
// 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.
|
|
70
59
|
const probe = new DatabaseSync(':memory:');
|
|
71
60
|
try {
|
|
72
61
|
if (typeof probe.prepare('SELECT 1').setReturnArrays !== 'function') throw new Error('no positional rows');
|
|
@@ -38,8 +38,7 @@ let remembered: ExtensionCtx | undefined;
|
|
|
38
38
|
let lastText: string | undefined;
|
|
39
39
|
let lastUi: UiApi | undefined;
|
|
40
40
|
|
|
41
|
-
// Paint the host footer bar; same text on the same UI is skipped, a
|
|
42
|
-
// One key only: the combo line already names the caveman mode, and a second key renders as a second footer line.
|
|
41
|
+
// 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.
|
|
43
42
|
export function announceStatus(ctx: ExtensionCtx | undefined): void {
|
|
44
43
|
const state = getSharedComboState();
|
|
45
44
|
const text = formatStatus(state);
|