@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
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
|
-
import { homeDir, isPiProcess, piAgentDir, resolveRtkBinary, tersioDataPath } from './utils.ts';
|
|
5
|
+
import { homeDir, isPiProcess, piAgentDir, resolveRtkBinary, rtkDirs, shimCommand, tersioDataPath } from './utils.ts';
|
|
6
|
+
import { eligibleHeads } from './rtk-eligible.ts';
|
|
6
7
|
import { sqliteRead } from './sqlite.ts';
|
|
7
8
|
|
|
8
9
|
export type UsageKind = 'command' | 'toggle' | 'install' | 'update' | 'rtk-audit';
|
|
@@ -104,6 +105,14 @@ export interface SessionTokens {
|
|
|
104
105
|
recent: RecentRequest[];
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
export interface RtkHostAdoption {
|
|
109
|
+
bashCalls: number;
|
|
110
|
+
eligibleCalls: number;
|
|
111
|
+
rtkCalls: number;
|
|
112
|
+
missedCalls: number;
|
|
113
|
+
adoptionPct: number;
|
|
114
|
+
}
|
|
115
|
+
|
|
107
116
|
export interface RtkAdoption {
|
|
108
117
|
sessions: number;
|
|
109
118
|
bashCalls: number;
|
|
@@ -111,6 +120,8 @@ export interface RtkAdoption {
|
|
|
111
120
|
rtkCalls: number;
|
|
112
121
|
missedCalls: number;
|
|
113
122
|
adoptionPct: number;
|
|
123
|
+
/** One entry per host that recorded bash calls, so a blended number cannot hide a host. */
|
|
124
|
+
byHost: Record<string, RtkHostAdoption>;
|
|
114
125
|
}
|
|
115
126
|
|
|
116
127
|
export interface RtkRecallDiagnostics {
|
|
@@ -273,6 +284,55 @@ export function dayKey(ts: string | number): string | null {
|
|
|
273
284
|
const p = (n: number): string => String(n).padStart(2, '0');
|
|
274
285
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
275
286
|
}
|
|
287
|
+
|
|
288
|
+
// OpenCode tool parts carry the command in the message blob; sqliteRead is line-based, so newlines are flattened in SQL.
|
|
289
|
+
const OC_BASH_CMD = "replace(replace(json_extract(j.value,'$.state.input.command'), char(10), ' '), char(13), ' ')";
|
|
290
|
+
const OC_BASH_TOOL = "json_extract(j.value,'$.type')='tool' AND (json_extract(j.value,'$.name')='bash' OR json_extract(j.value,'$.name')='shell')";
|
|
291
|
+
const ocBashLegacy = `SELECT message.id, json_extract(message.data,'$.sessionID'), ${OC_BASH_CMD} FROM message, json_each(message.data,'$.content') j WHERE json_extract(message.data,'$.role')='assistant' AND ${OC_BASH_TOOL}`;
|
|
292
|
+
const ocBashCurrent = `SELECT session_message.id, session_message.session_id, ${OC_BASH_CMD} FROM session_message, json_each(session_message.data,'$.content') j WHERE session_message.type='assistant' AND ${OC_BASH_TOOL}`;
|
|
293
|
+
|
|
294
|
+
export interface OpencodeBashRow {
|
|
295
|
+
id: string;
|
|
296
|
+
sessionId: string;
|
|
297
|
+
command: string;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Every executed bash command in an OpenCode db, v2 first and the legacy table second. */
|
|
301
|
+
export function readOpencodeBashRows(db: string): OpencodeBashRow[] {
|
|
302
|
+
const rows: OpencodeBashRow[] = [];
|
|
303
|
+
try {
|
|
304
|
+
if (!fs.existsSync(db)) return rows;
|
|
305
|
+
let out = '';
|
|
306
|
+
try { out += sqliteRead(db, ocBashCurrent); } catch { /* no v2 table */ }
|
|
307
|
+
try { out += sqliteRead(db, ocBashLegacy); } catch { /* no legacy table */ }
|
|
308
|
+
for (const line of out.split('\n')) {
|
|
309
|
+
if (!line.trim()) continue;
|
|
310
|
+
const [id, sessionId, command] = line.split('\t');
|
|
311
|
+
if (typeof command !== 'string' || !command.trim()) continue;
|
|
312
|
+
rows.push({ id: id ?? '', sessionId: sessionId ?? '', command });
|
|
313
|
+
}
|
|
314
|
+
} catch { /* an unreadable db is a skip, not a failure */ }
|
|
315
|
+
return rows;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Pre-v2 OpenCode stores one message per JSON file; the same tool-part shape holds the command.
|
|
319
|
+
function opencodeFileBash(file: string): { commands: string[]; sessionId: string } {
|
|
320
|
+
try {
|
|
321
|
+
const message = JSON.parse(fs.readFileSync(file, 'utf8')) as { sessionID?: unknown; content?: unknown };
|
|
322
|
+
const parts = Array.isArray(message.content) ? message.content : [];
|
|
323
|
+
const commands: string[] = [];
|
|
324
|
+
for (const part of parts) {
|
|
325
|
+
const p = part as { type?: unknown; name?: unknown; state?: { input?: { command?: unknown } } };
|
|
326
|
+
if (p?.type !== 'tool') continue;
|
|
327
|
+
if (p.name !== 'bash' && p.name !== 'shell') continue;
|
|
328
|
+
const command = p.state?.input?.command;
|
|
329
|
+
if (typeof command === 'string' && command) commands.push(command);
|
|
330
|
+
}
|
|
331
|
+
return { commands, sessionId: typeof message.sessionID === 'string' ? message.sessionID : '' };
|
|
332
|
+
} catch {
|
|
333
|
+
return { commands: [], sessionId: '' };
|
|
334
|
+
}
|
|
335
|
+
}
|
|
276
336
|
export function walkJsonl(dir: string, out: string[], cap: number, ext = '.jsonl'): void {
|
|
277
337
|
let entries: fs.Dirent[];
|
|
278
338
|
try {
|
|
@@ -290,11 +350,6 @@ export function walkJsonl(dir: string, out: string[], cap: number, ext = '.jsonl
|
|
|
290
350
|
// Recent rows are a newest-first window over lifetime history; aggregates keep everything.
|
|
291
351
|
export const RECENT_LIMIT = 2000;
|
|
292
352
|
const FREE_SUFFIX = /(?::free|-free)$/i;
|
|
293
|
-
const RTK_ELIGIBLE_HEADS = new Set([
|
|
294
|
-
'rtk', 'git', 'grep', 'rg', 'find', 'cat', 'ls', 'tree', 'diff', 'log',
|
|
295
|
-
'npm', 'npx', 'pnpm', 'bun', 'bunx', 'cargo', 'go', 'python', 'pytest',
|
|
296
|
-
'ruff', 'mypy', 'docker', 'kubectl', 'psql', 'aws', 'gh', 'glab', 'wc',
|
|
297
|
-
]);
|
|
298
353
|
// Parse buffer shared by live reads and usage.db syncs, so the store is a cache and never a fork.
|
|
299
354
|
export interface SessionAccum {
|
|
300
355
|
byModel: Record<string, TokenBreakdown>;
|
|
@@ -650,43 +705,104 @@ export function agentOfSessionText(text: string): string {
|
|
|
650
705
|
return 'main';
|
|
651
706
|
}
|
|
652
707
|
|
|
653
|
-
const adoptionFileCache = new Map<string, { size: number; mtimeMs: number;
|
|
708
|
+
const adoptionFileCache = new Map<string, { size: number; mtimeMs: number; heads: string; counts: BashCounts }>();
|
|
654
709
|
|
|
655
|
-
|
|
656
|
-
|
|
710
|
+
interface BashCounts { bashCalls: number; eligibleCalls: number; rtkCalls: number }
|
|
711
|
+
|
|
712
|
+
function countBashCommands(commands: readonly string[], eligible: ReadonlySet<string>, counts: BashCounts): void {
|
|
713
|
+
for (const command of commands) {
|
|
714
|
+
counts.bashCalls += 1;
|
|
715
|
+
const head = leadBinary(command);
|
|
716
|
+
if (eligible.has(head)) counts.eligibleCalls += 1;
|
|
717
|
+
if (head === 'rtk') counts.rtkCalls += 1;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
interface PiToolCall {
|
|
722
|
+
type?: unknown;
|
|
723
|
+
name?: unknown;
|
|
724
|
+
id?: unknown;
|
|
725
|
+
arguments?: unknown;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function parseAdoptionFile(file: string, eligible: ReadonlySet<string>): { counts: BashCounts; sessions: number } {
|
|
729
|
+
const counts: BashCounts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
657
730
|
let text: string;
|
|
658
731
|
try {
|
|
659
732
|
text = fs.readFileSync(file, 'utf8');
|
|
660
733
|
} catch {
|
|
661
734
|
return { counts, sessions: 0 };
|
|
662
735
|
}
|
|
663
|
-
|
|
736
|
+
// omp emits a custom row per tool start; pi records the same call inside the assistant message.
|
|
737
|
+
// A host that writes both shapes would double every call, so the custom ids gate the message pass.
|
|
738
|
+
const commands: string[] = [];
|
|
739
|
+
const customIds = new Set<string>();
|
|
740
|
+
const messageCalls: Array<{ id: string | undefined; command: string }> = [];
|
|
664
741
|
for (const line of text.split('\n')) {
|
|
665
|
-
if (!line.includes('tool_execution_start')) continue;
|
|
742
|
+
if (!line.includes('tool_execution_start') && !line.includes('"toolCall"')) continue;
|
|
666
743
|
try {
|
|
667
744
|
const row = JSON.parse(line) as {
|
|
668
|
-
type?:
|
|
669
|
-
customType?:
|
|
670
|
-
data?: { toolName?: unknown; args?: { command?: unknown } };
|
|
745
|
+
type?: unknown;
|
|
746
|
+
customType?: unknown;
|
|
747
|
+
data?: { toolCallId?: unknown; toolName?: unknown; args?: { command?: unknown } };
|
|
748
|
+
message?: { content?: unknown };
|
|
671
749
|
};
|
|
672
|
-
if (row.type
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
750
|
+
if (row.type === 'custom' && row.customType === 'tool_execution_start' && row.data?.toolName === 'bash') {
|
|
751
|
+
const command = row.data.args?.command;
|
|
752
|
+
if (typeof command !== 'string') continue;
|
|
753
|
+
if (typeof row.data.toolCallId === 'string') customIds.add(row.data.toolCallId);
|
|
754
|
+
commands.push(command);
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
const content = row.message?.content;
|
|
758
|
+
if (row.type !== 'message' || !Array.isArray(content)) continue;
|
|
759
|
+
for (const part of content) {
|
|
760
|
+
if (typeof part !== 'object' || part === null) continue;
|
|
761
|
+
const call: PiToolCall = part;
|
|
762
|
+
if (call.type !== 'toolCall' || call.name !== 'bash') continue;
|
|
763
|
+
const args = call.arguments;
|
|
764
|
+
if (typeof args !== 'object' || args === null || !('command' in args)) continue;
|
|
765
|
+
const command = args.command;
|
|
766
|
+
if (typeof command !== 'string') continue;
|
|
767
|
+
messageCalls.push({ id: typeof call.id === 'string' ? call.id : undefined, command });
|
|
768
|
+
}
|
|
680
769
|
} catch { /* skip corrupt line */ }
|
|
681
770
|
}
|
|
682
|
-
|
|
771
|
+
for (const call of messageCalls) {
|
|
772
|
+
if (call.id !== undefined && customIds.has(call.id)) continue;
|
|
773
|
+
commands.push(call.command);
|
|
774
|
+
}
|
|
775
|
+
countBashCommands(commands, eligible, counts);
|
|
776
|
+
return { counts, sessions: commands.length > 0 ? 1 : 0 };
|
|
683
777
|
}
|
|
684
778
|
|
|
685
779
|
export function clearRtkAdoptionCache(): void {
|
|
686
780
|
adoptionFileCache.clear();
|
|
687
781
|
}
|
|
688
782
|
|
|
783
|
+
/** Rewrites the hook performed on hosts whose transcript records the pre-hook command, by host. */
|
|
784
|
+
function countRtkAuditRows(): Record<string, number> {
|
|
785
|
+
const byHost: Record<string, number> = {};
|
|
786
|
+
let text: string;
|
|
787
|
+
try { text = fs.readFileSync(ledgerPath(), 'utf8'); } catch { return byHost; }
|
|
788
|
+
for (const line of text.split('\n')) {
|
|
789
|
+
if (!line.includes('rtk-audit')) continue;
|
|
790
|
+
try {
|
|
791
|
+
const row = JSON.parse(line) as { kind?: unknown; detail?: unknown };
|
|
792
|
+
if (row.kind !== 'rtk-audit' || typeof row.detail !== 'string') continue;
|
|
793
|
+
const host = row.detail.split(' ')[0];
|
|
794
|
+
if (!host) continue;
|
|
795
|
+
byHost[host] = (byHost[host] ?? 0) + 1;
|
|
796
|
+
} catch { /* skip corrupt line */ }
|
|
797
|
+
}
|
|
798
|
+
return byHost;
|
|
799
|
+
}
|
|
800
|
+
|
|
689
801
|
export function readRtkAdoption(): RtkAdoption {
|
|
802
|
+
// The eligible set comes from rtk's rewrite contract, cached per binary version.
|
|
803
|
+
const eligible = eligibleHeads(resolveRtkBinary());
|
|
804
|
+
// A cached count is only valid for the head set that produced it.
|
|
805
|
+
const headsKey = [...eligible].sort().join(',');
|
|
690
806
|
const files: string[] = [];
|
|
691
807
|
for (const dir of sessionsDirs()) walkJsonl(dir, files, 2000);
|
|
692
808
|
const live = new Set(files);
|
|
@@ -697,6 +813,14 @@ export function readRtkAdoption(): RtkAdoption {
|
|
|
697
813
|
let eligibleCalls = 0;
|
|
698
814
|
let rtkCalls = 0;
|
|
699
815
|
let sessions = 0;
|
|
816
|
+
const byHost: Record<string, BashCounts> = {};
|
|
817
|
+
const hostCounts = (host: string, counts: BashCounts): void => {
|
|
818
|
+
const into = byHost[host] ?? { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
819
|
+
into.bashCalls += counts.bashCalls;
|
|
820
|
+
into.eligibleCalls += counts.eligibleCalls;
|
|
821
|
+
into.rtkCalls += counts.rtkCalls;
|
|
822
|
+
byHost[host] = into;
|
|
823
|
+
};
|
|
700
824
|
for (const file of files) {
|
|
701
825
|
let stat: fs.Stats;
|
|
702
826
|
try {
|
|
@@ -705,9 +829,9 @@ export function readRtkAdoption(): RtkAdoption {
|
|
|
705
829
|
continue;
|
|
706
830
|
}
|
|
707
831
|
let entry = adoptionFileCache.get(file);
|
|
708
|
-
if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs) {
|
|
709
|
-
const parsed = parseAdoptionFile(file);
|
|
710
|
-
entry = { size: stat.size, mtimeMs: stat.mtimeMs, counts: parsed.counts };
|
|
832
|
+
if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs || entry.heads !== headsKey) {
|
|
833
|
+
const parsed = parseAdoptionFile(file, eligible);
|
|
834
|
+
entry = { size: stat.size, mtimeMs: stat.mtimeMs, heads: headsKey, counts: parsed.counts };
|
|
711
835
|
adoptionFileCache.set(file, entry);
|
|
712
836
|
sessions += parsed.sessions;
|
|
713
837
|
} else if (entry.counts.bashCalls > 0) {
|
|
@@ -716,9 +840,53 @@ export function readRtkAdoption(): RtkAdoption {
|
|
|
716
840
|
bashCalls += entry.counts.bashCalls;
|
|
717
841
|
eligibleCalls += entry.counts.eligibleCalls;
|
|
718
842
|
rtkCalls += entry.counts.rtkCalls;
|
|
843
|
+
hostCounts(hostOfSessionFile(file), entry.counts);
|
|
844
|
+
}
|
|
845
|
+
// OpenCode keeps its own store: a v2 sqlite db, or one JSON file per message before v2.
|
|
846
|
+
const opencode: BashCounts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
847
|
+
const opencodeSessions = new Set<string>();
|
|
848
|
+
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DB !== undefined) {
|
|
849
|
+
for (const row of readOpencodeBashRows(opencodeDbPath())) {
|
|
850
|
+
countBashCommands([row.command], eligible, opencode);
|
|
851
|
+
if (row.sessionId) opencodeSessions.add(row.sessionId);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
const ocFiles: string[] = [];
|
|
855
|
+
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DIR !== undefined) {
|
|
856
|
+
walkJsonl(opencodeSessionsDir(), ocFiles, 5000, '.json');
|
|
857
|
+
for (const file of ocFiles) {
|
|
858
|
+
const parsed = opencodeFileBash(file);
|
|
859
|
+
countBashCommands(parsed.commands, eligible, opencode);
|
|
860
|
+
if (parsed.sessionId) opencodeSessions.add(parsed.sessionId);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
bashCalls += opencode.bashCalls;
|
|
864
|
+
eligibleCalls += opencode.eligibleCalls;
|
|
865
|
+
rtkCalls += opencode.rtkCalls;
|
|
866
|
+
sessions += opencodeSessions.size;
|
|
867
|
+
if (opencode.bashCalls > 0) hostCounts('opencode', opencode);
|
|
868
|
+
// The hook audit carries rewrites on pi and opencode, whose transcripts record the pre-hook command.
|
|
869
|
+
for (const [host, rows] of Object.entries(countRtkAuditRows())) {
|
|
870
|
+
rtkCalls += rows;
|
|
871
|
+
hostCounts(host, { bashCalls: 0, eligibleCalls: 0, rtkCalls: rows });
|
|
719
872
|
}
|
|
720
873
|
const missedCalls = Math.max(0, eligibleCalls - rtkCalls);
|
|
721
|
-
|
|
874
|
+
const host = (counts: BashCounts): RtkHostAdoption => ({
|
|
875
|
+
bashCalls: counts.bashCalls,
|
|
876
|
+
eligibleCalls: counts.eligibleCalls,
|
|
877
|
+
rtkCalls: counts.rtkCalls,
|
|
878
|
+
missedCalls: Math.max(0, counts.eligibleCalls - counts.rtkCalls),
|
|
879
|
+
adoptionPct: counts.eligibleCalls ? (counts.rtkCalls / counts.eligibleCalls) * 100 : 0,
|
|
880
|
+
});
|
|
881
|
+
return {
|
|
882
|
+
sessions,
|
|
883
|
+
bashCalls,
|
|
884
|
+
eligibleCalls,
|
|
885
|
+
rtkCalls,
|
|
886
|
+
missedCalls,
|
|
887
|
+
adoptionPct: eligibleCalls ? (rtkCalls / eligibleCalls) * 100 : 0,
|
|
888
|
+
byHost: Object.fromEntries(Object.entries(byHost).map(([name, counts]) => [name, host(counts)])),
|
|
889
|
+
};
|
|
722
890
|
}
|
|
723
891
|
export function processSessionText(accum: SessionAccum, state: { codexProvider: string | null; codexModel?: string | null }, text: string, host?: string, agent?: string): void {
|
|
724
892
|
for (const line of text.split('\n')) {
|
|
@@ -751,15 +919,13 @@ export function processSessionText(accum: SessionAccum, state: { codexProvider:
|
|
|
751
919
|
export function readRtkRecallDiagnostics(binary: string | null = resolveRtkBinary()): RtkRecallDiagnostics {
|
|
752
920
|
if (!binary) return { mode: 'unknown', entries: 0, available: false };
|
|
753
921
|
try {
|
|
754
|
-
const configPath = process.env.RTK_CONFIG
|
|
755
|
-
|| (process.platform === 'darwin'
|
|
756
|
-
? path.join(homeDir(), 'Library', 'Application Support', 'rtk', 'config.toml')
|
|
757
|
-
: path.join(process.env.XDG_DATA_HOME || path.join(homeDir(), '.local', 'share'), 'rtk', 'config.toml'));
|
|
922
|
+
const configPath = process.env.RTK_CONFIG || path.join(rtkDirs().config, 'config.toml');
|
|
758
923
|
let config = '';
|
|
759
924
|
try { config = fs.readFileSync(configPath, 'utf8'); } catch { /* use RTK CLI fallback */ }
|
|
760
925
|
const modeMatch = /^\s*mode\s*=\s*"(sqlite|tee|disabled)"/m.exec(config);
|
|
761
926
|
const mode = (modeMatch?.[1] ?? 'unknown') as RtkRecallDiagnostics['mode'];
|
|
762
|
-
const
|
|
927
|
+
const shim = shimCommand(binary, ['recall', '--list']);
|
|
928
|
+
const output = execFileSync(shim.cmd, shim.args, { encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
763
929
|
const entries = /\((\d+)\s+(?:entries?|stored)\)/i.exec(output)?.[1] ?? /^\s*(\d+)\s+entries?/im.exec(output)?.[1] ?? '0';
|
|
764
930
|
return { mode, entries: Number(entries) || 0, available: true };
|
|
765
931
|
} catch {
|
|
@@ -26,9 +26,32 @@ export function homeDir(): string {
|
|
|
26
26
|
return home && home.trim() !== '' ? home : os.homedir();
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** Windows cannot run a `.cmd` or `.bat` directly, so a shim path goes through the shell; a real `.exe` or POSIX binary passes through. */
|
|
30
|
+
export function shimCommand(cmd: string, args: string[]): { cmd: string; args: string[] } {
|
|
31
|
+
if (process.platform !== 'win32' || !/\.(?:cmd|bat)$/i.test(cmd)) return { cmd, args };
|
|
32
|
+
return { cmd: process.env.ComSpec || 'cmd.exe', args: ['/d', '/s', '/c', cmd, ...args] };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** rtk's own dirs: `config_dir()` for config.toml and `data_local_dir()` for history.db, which differ per platform; a guess here empties the Gain charts. */
|
|
36
|
+
export function rtkDirs(): { data: string; config: string } {
|
|
37
|
+
if (process.platform === 'darwin') {
|
|
38
|
+
const base = path.join(homeDir(), 'Library', 'Application Support', 'rtk');
|
|
39
|
+
return { data: base, config: base };
|
|
40
|
+
}
|
|
41
|
+
if (process.platform === 'win32') {
|
|
42
|
+
const local = process.env.LOCALAPPDATA || path.join(homeDir(), 'AppData', 'Local');
|
|
43
|
+
const roaming = process.env.APPDATA || path.join(homeDir(), 'AppData', 'Roaming');
|
|
44
|
+
return { data: path.join(local, 'rtk'), config: path.join(roaming, 'rtk') };
|
|
45
|
+
}
|
|
46
|
+
const xdgData = process.env.XDG_DATA_HOME || path.join(homeDir(), '.local', 'share');
|
|
47
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(homeDir(), '.config');
|
|
48
|
+
return { data: path.join(xdgData, 'rtk'), config: path.join(xdgConfig, 'rtk') };
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
export const RTK_RELEASE_API = 'https://api.github.com/repos/rtk-ai/rtk/releases/latest';
|
|
30
52
|
export const CAVEMAN_REMOTE_ULTRA = 'https://raw.githubusercontent.com/JuliusBrussee/caveman/main/skills/ultracave/SKILL.md';
|
|
31
53
|
export const CAVEMAN_REMOTE_MEGACAVE = 'https://raw.githubusercontent.com/JuliusBrussee/caveman/main/skills/megacave/SKILL.md';
|
|
54
|
+
export const CAVEMAN_REMOTE_FULL = 'https://raw.githubusercontent.com/JuliusBrussee/caveman/main/skills/caveman/SKILL.md';
|
|
32
55
|
|
|
33
56
|
// A fetch that returns an error page or a truncated body must not reach the extension tree.
|
|
34
57
|
export function ruleBodyProblem(text: string): string | null {
|
|
@@ -104,6 +127,12 @@ export function findPackageRoot(fromDir: string): string {
|
|
|
104
127
|
}
|
|
105
128
|
}
|
|
106
129
|
|
|
130
|
+
/** Where a mode's rule bodies sit: beside the module, else the package's own `extensions/<name>`. */
|
|
131
|
+
export function ruleDir(name: string, ownDir: string): string {
|
|
132
|
+
if (existsSync(path.join(ownDir, 'rule.md'))) return ownDir;
|
|
133
|
+
try { return path.join(findPackageRoot(ownDir), 'extensions', name); } catch { return ownDir; }
|
|
134
|
+
}
|
|
135
|
+
|
|
107
136
|
// The 3xx+location redirect rule shared by httpsGet/httpsDownload.
|
|
108
137
|
function redirectNext(res: { statusCode?: number; headers: { location?: string } }, url: string): string | null {
|
|
109
138
|
if (res.statusCode !== undefined && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// pi records
|
|
2
|
-
// omp writes its own `ttft` and OpenCode records `time.streamed`, so this fills the gap only for pi.
|
|
1
|
+
// pi records total duration but no first-token latency, so TTFT reads null there; omp writes its own `ttft` and OpenCode records `time.streamed`, so this fills the gap only for pi.
|
|
3
2
|
import type { ExtensionApi } from '../shared/types.ts';
|
|
4
3
|
|
|
5
4
|
interface AssistantMessage {
|
|
@@ -12,10 +12,12 @@ import { setExtensionLabel } from '../shared/host.ts';
|
|
|
12
12
|
import {
|
|
13
13
|
CAVEMAN_REMOTE_ULTRA,
|
|
14
14
|
CAVEMAN_REMOTE_MEGACAVE,
|
|
15
|
+
CAVEMAN_REMOTE_FULL,
|
|
15
16
|
RTK_RELEASE_API,
|
|
16
17
|
fetchJson,
|
|
17
18
|
findFile,
|
|
18
19
|
findHoistedPackage,
|
|
20
|
+
homeDir,
|
|
19
21
|
httpsGet,
|
|
20
22
|
httpsDownload,
|
|
21
23
|
sha256Hex,
|
|
@@ -38,15 +40,20 @@ const CAVEMAN_SIBLING = (name: string): string => path.join(EXTENSION_DIR, '..',
|
|
|
38
40
|
function cavemanRule(name: string): string {
|
|
39
41
|
const beside = CAVEMAN_SIBLING(name);
|
|
40
42
|
if (fsSync.existsSync(beside)) return beside;
|
|
41
|
-
const plugin = path.join(
|
|
43
|
+
const plugin = path.join(homeDir(), '.omp', 'plugins', 'node_modules', '@krtclcdy', 'tersio', 'extensions', 'caveman', name);
|
|
42
44
|
return fsSync.existsSync(plugin) ? plugin : beside;
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
const CAVEMAN_LOCAL = cavemanRule('rule.md');
|
|
46
|
-
|
|
48
|
+
// `rule.md` is upstream's body with one adapted line, so a refresh re-applies the adaptation.
|
|
49
|
+
const adaptCavemanFull = (text: string): string =>
|
|
50
|
+
text.replace('`/caveman-compress` exempt.', '`/caveman-compress`, where the host offers it, is exempt.');
|
|
51
|
+
const CAVEMAN_TRACKED: ReadonlyArray<{ file: string; remote: string; adapt?: (text: string) => string }> = [
|
|
52
|
+
{ file: CAVEMAN_LOCAL, remote: CAVEMAN_REMOTE_FULL, adapt: adaptCavemanFull },
|
|
47
53
|
{ file: cavemanRule('rule-ultra.md'), remote: CAVEMAN_REMOTE_ULTRA },
|
|
48
54
|
{ file: cavemanRule('rule-megacave.md'), remote: CAVEMAN_REMOTE_MEGACAVE },
|
|
49
55
|
];
|
|
56
|
+
export { adaptCavemanFull };
|
|
50
57
|
// Host-neutral: this updater runs inside pi, OMP, and OpenCode alike.
|
|
51
58
|
const RELOAD_MSG = 'Reminder: restart your agent (or reload extensions) for updates to take effect.';
|
|
52
59
|
|
|
@@ -144,11 +151,13 @@ function checkRtk(): Promise<AddonStatus> {
|
|
|
144
151
|
function checkCaveman(): Promise<AddonStatus> {
|
|
145
152
|
return runCheck('Caveman', async () => {
|
|
146
153
|
const parts: string[] = [(await readTextIfExists(CAVEMAN_LOCAL)) ? 'rule.md ok' : 'rule.md missing'];
|
|
147
|
-
for (const { file, remote } of CAVEMAN_TRACKED) {
|
|
154
|
+
for (const { file, remote, adapt } of CAVEMAN_TRACKED) {
|
|
148
155
|
const name = path.basename(file);
|
|
149
156
|
try {
|
|
150
157
|
const [remoteText, localText] = await Promise.all([httpsGet(remote), readTextIfExists(file)]);
|
|
151
|
-
|
|
158
|
+
// Compare the text a refresh would write, so the adapted line never reads as drift.
|
|
159
|
+
const nextHash = shortHash(adapt ? adapt(remoteText) : remoteText);
|
|
160
|
+
parts.push(!localText ? `${name} missing` : shortHash(localText) === nextHash ? `${name} up to date` : `${name} update available`);
|
|
152
161
|
} catch {
|
|
153
162
|
parts.push(`${name} check skipped`);
|
|
154
163
|
}
|
|
@@ -366,13 +375,15 @@ async function updateRtk(ctx: AddonUpdaterCtx, dryRun = false): Promise<string>
|
|
|
366
375
|
|
|
367
376
|
async function updateCaveman(ctx: AddonUpdaterCtx, dryRun = false): Promise<string> {
|
|
368
377
|
const results: string[] = [];
|
|
369
|
-
for (const { file, remote } of CAVEMAN_TRACKED) {
|
|
378
|
+
for (const { file, remote, adapt } of CAVEMAN_TRACKED) {
|
|
370
379
|
const name = path.basename(file);
|
|
371
380
|
let remoteText: string;
|
|
372
381
|
try { remoteText = await httpsGet(remote); }
|
|
373
382
|
catch (e) { results.push(report(ctx, `Caveman ${name} update failed: ${(e as Error).message}`, 'warning')); continue; }
|
|
374
383
|
|
|
375
|
-
|
|
384
|
+
// A tracked fork compares against the text it would write, so an adapted body still reads up to date.
|
|
385
|
+
const nextText = adapt ? adapt(remoteText) : remoteText;
|
|
386
|
+
const remoteHash = shortHash(nextText);
|
|
376
387
|
const oldLocal = await readTextIfExists(file);
|
|
377
388
|
const oldHash = oldLocal ? shortHash(oldLocal) : null;
|
|
378
389
|
if (oldHash === remoteHash) { results.push(`${name} up to date: ${remoteHash}`); continue; }
|
|
@@ -386,7 +397,7 @@ async function updateCaveman(ctx: AddonUpdaterCtx, dryRun = false): Promise<stri
|
|
|
386
397
|
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
387
398
|
const backupPath = `${file}.bak`;
|
|
388
399
|
if (oldLocal !== null) await fs.writeFile(backupPath, oldLocal, 'utf8');
|
|
389
|
-
await fs.writeFile(file,
|
|
400
|
+
await fs.writeFile(file, nextText, 'utf8');
|
|
390
401
|
const writtenHash = shortHash(await fs.readFile(file, 'utf8'));
|
|
391
402
|
if (writtenHash !== remoteHash) {
|
|
392
403
|
if (oldLocal !== null) await fs.writeFile(file, oldLocal, 'utf8');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krtclcdy/tersio",
|
|
3
|
-
"version": "2.25.
|
|
4
|
-
"description": "✂️ Token saver and usage tracker for coding agents: terse Caveman replies, compact RTK shell output, lean Ponytail code
|
|
3
|
+
"version": "2.25.8",
|
|
4
|
+
"description": "✂️ Token saver and usage tracker for coding agents: terse Caveman replies, compact RTK shell output, lean Ponytail code, pstack rigor, and a gain dashboard — one-command combos over one shared extension tree.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"token-saving",
|
|
7
7
|
"ai-tools",
|
|
@@ -123,18 +123,19 @@
|
|
|
123
123
|
},
|
|
124
124
|
"files": [
|
|
125
125
|
"dist/**/*.js",
|
|
126
|
-
"dist/extensions/caveman/*.md",
|
|
127
|
-
"dist/extensions/pstack/*.md",
|
|
128
126
|
"dashboard/dist/**/*",
|
|
129
127
|
"extensions/**/*.ts",
|
|
130
128
|
"extensions/caveman/rule.md",
|
|
129
|
+
"extensions/caveman/rule-lite.md",
|
|
130
|
+
"extensions/caveman/rule-megacave-lite.md",
|
|
131
|
+
"extensions/caveman/rule-megacave-ultra.md",
|
|
131
132
|
"extensions/caveman/rule-ultra.md",
|
|
132
133
|
"extensions/caveman/rule-megacave.md",
|
|
133
|
-
"extensions/pstack
|
|
134
|
+
"extensions/pstack/**/*.md"
|
|
134
135
|
],
|
|
135
136
|
"scripts": {
|
|
136
|
-
"clean": "
|
|
137
|
-
"build": "tsc && bun run build:dashboard
|
|
137
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
138
|
+
"build": "tsc && bun run build:dashboard",
|
|
138
139
|
"build:dashboard": "bun install --cwd dashboard/app --frozen-lockfile && bun run --cwd dashboard/app build",
|
|
139
140
|
"prepublishOnly": "bun run build",
|
|
140
141
|
"check": "tsc --noEmit",
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: megacave
|
|
3
|
-
description: >
|
|
4
|
-
Caveman in Classical Chinese: 文言文 register, far fewer characters, technical
|
|
5
|
-
terms verbatim. Invoke only with /megacave or /caveman wenyan. Stays on until
|
|
6
|
-
"stop caveman" or "normal mode".
|
|
7
|
-
disable-model-invocation: true
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# megacave
|
|
11
|
-
|
|
12
|
-
以文言答。技術之實皆存,唯贅言去之。
|
|
13
|
-
|
|
14
|
-
Megacave is caveman in Classical Chinese. 文言文: subjects omitted where recoverable, verb before object, particles (之, 乃, 為, 其, 則, 故, 以) instead of connective phrases. Fewer characters on screen. Token savings are small and noisy; never claim them.
|
|
15
|
-
|
|
16
|
-
## Persistence
|
|
17
|
-
|
|
18
|
-
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value; with no hook, the mode you were in (or `off`) plus `(not tracked by this host)`.
|
|
19
|
-
|
|
20
|
-
## Floor
|
|
21
|
-
|
|
22
|
-
Never cut or translate: code, commands, paths, API names, error strings, in their original script. Negation (不, 非, 勿, 未, 毋). Numbers and units, Arabic numerals. One term per thing. A clause ambiguous in 文言 becomes 白話. Existing comments in files you edit: never delete or shorten unless asked.
|
|
23
|
-
|
|
24
|
-
## Rules
|
|
25
|
-
|
|
26
|
-
### 1. Classical register
|
|
27
|
-
|
|
28
|
-
文言, not 白話. Verb before object. Subjects omitted where recoverable.
|
|
29
|
-
|
|
30
|
-
Bad: "你的組件每次渲染都會重新渲染,因為你每次都創建了新的對象引用。請用 useMemo 包起來。"
|
|
31
|
-
Good: "每繪新生對象參照,故重繪;以 `useMemo` 包之則免。"
|
|
32
|
-
|
|
33
|
-
### 2. Particles carry structure
|
|
34
|
-
|
|
35
|
-
One particle, one relation.
|
|
36
|
-
|
|
37
|
-
Bad: "因為連接池會重複使用已經打開的連接,所以不需要每次請求都新建連接,這樣就省去了握手的開銷。"
|
|
38
|
-
Good: "池蓄已開之連,不逐請而新開,省握手之費。"
|
|
39
|
-
|
|
40
|
-
### 3. Each fact once
|
|
41
|
-
|
|
42
|
-
Code change: changed lines plus context, not the whole file.
|
|
43
|
-
|
|
44
|
-
Good: "新參照則重繪。`useMemo` 包之。"
|
|
45
|
-
|
|
46
|
-
### 4. Payload in original script
|
|
47
|
-
|
|
48
|
-
Bad: "以記憶化鉤子包之。"
|
|
49
|
-
Good: "以 `useMemo` 包之。"
|
|
50
|
-
|
|
51
|
-
### 5. Tool runs
|
|
52
|
-
|
|
53
|
-
One 文言 line in, one out. Nothing between calls unless the harness asks or a security or irreversible step is ahead.
|
|
54
|
-
|
|
55
|
-
### 6. Never perform
|
|
56
|
-
|
|
57
|
-
No "文言模式啟", no modern answer plus classical copy. 文言 not shorter than 白話 for a sentence? Use 白話.
|
|
58
|
-
|
|
59
|
-
## When to break the rules
|
|
60
|
-
|
|
61
|
-
白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirm first. Any clause with two readings. User confused. Question to the user, with its options. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
|
|
62
|
-
|
|
63
|
-
## Pre-send check
|
|
64
|
-
|
|
65
|
-
Identifiers verbatim, original script? Negations present? Two readings? 白話. Flavor character? Cut.
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ultracave
|
|
3
|
-
description: >
|
|
4
|
-
Caveman at maximum compression: fragments, one word when one word is enough,
|
|
5
|
-
each fact once. Invoke only with /ultracave or /caveman ultra. Stays on until
|
|
6
|
-
"stop caveman" or "normal mode".
|
|
7
|
-
disable-model-invocation: true
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# ultracave
|
|
11
|
-
|
|
12
|
-
Respond terse like smart caveman. All technical substance stay. Only fluff die. Then cut again.
|
|
13
|
-
|
|
14
|
-
Ultracave is caveman with the grammar stripped. Payload only.
|
|
15
|
-
|
|
16
|
-
## Persistence
|
|
17
|
-
|
|
18
|
-
Every response, whole session, until "stop caveman" or "normal mode". Unsure? Still on. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value; with no hook, the mode you were in (or `off`) plus `(not tracked by this host)`.
|
|
19
|
-
|
|
20
|
-
## Floor
|
|
21
|
-
|
|
22
|
-
Never cut: code, commands, paths, API names, error strings (verbatim). not/never/no/only/except. Numbers and units. The user's language. One term per thing. No invented abbreviations, no arrows. Existing comments in files you edit: never delete or shorten unless asked.
|
|
23
|
-
|
|
24
|
-
## Rules
|
|
25
|
-
|
|
26
|
-
### 1. Fragments
|
|
27
|
-
|
|
28
|
-
Drop articles, copulas, connectives when order stays clear.
|
|
29
|
-
|
|
30
|
-
Bad: "The component re-renders because an inline object prop creates a new reference each render."
|
|
31
|
-
Good: "Inline object prop, new ref, re-render. `useMemo`."
|
|
32
|
-
|
|
33
|
-
### 2. One word when one word is enough
|
|
34
|
-
|
|
35
|
-
Bad: "Yes, that should work, though you'll want a null check first."
|
|
36
|
-
Good: "Yes. Null check first."
|
|
37
|
-
|
|
38
|
-
### 3. Each fact once
|
|
39
|
-
|
|
40
|
-
No restating, no summary after a list. Code change: changed lines plus context, not the whole file.
|
|
41
|
-
|
|
42
|
-
Good: "Pool reuses open DB connections. No per-request handshake."
|
|
43
|
-
|
|
44
|
-
### 4. Cut conjunctions only when order survives
|
|
45
|
-
|
|
46
|
-
Bad: "Migrate table drop column backup first."
|
|
47
|
-
Good: "Back up first. Then migrate: drops column."
|
|
48
|
-
|
|
49
|
-
### 5. Tool runs
|
|
50
|
-
|
|
51
|
-
One line in, one line out. Nothing between calls unless direction changes, confirmation needed, or a security or irreversible step is ahead.
|
|
52
|
-
|
|
53
|
-
### 6. Never perform
|
|
54
|
-
|
|
55
|
-
No prefix, no announcement, no mangled verbs for flavor. Fragment not shorter than the sentence? Use the sentence.
|
|
56
|
-
|
|
57
|
-
## When to break the rules
|
|
58
|
-
|
|
59
|
-
Plain prose, then resume: security warning. Irreversible action, confirm first. Any fragment with two readings. User confused. Question to the user, with its options. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
|
|
60
|
-
|
|
61
|
-
## Pre-send check
|
|
62
|
-
|
|
63
|
-
Two readings? Full sentence. Negations present? Payload verbatim? Flavor word? Cut.
|