@krtclcdy/tersio 2.25.6 → 2.25.7
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 +30 -7
- package/dist/bench/code-out/pfull-0/debounce.js +13 -0
- package/dist/bench/code-out/pfull-0/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-0/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-1/debounce.js +13 -0
- package/dist/bench/code-out/pfull-1/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-1/groupBy.js +14 -0
- package/dist/bench/code-out/pfull-2/debounce.js +13 -0
- package/dist/bench/code-out/pfull-2/deepClone.js +18 -0
- package/dist/bench/code-out/pfull-2/groupBy.js +15 -0
- package/dist/bench/code-out/pfull-3/debounce.js +12 -0
- package/dist/bench/code-out/pfull-3/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-3/groupBy.js +7 -0
- package/dist/bench/code-out/pfull-4/debounce.js +10 -0
- package/dist/bench/code-out/pfull-4/deepClone.js +17 -0
- package/dist/bench/code-out/pfull-4/groupBy.js +14 -0
- package/dist/bench/code-out/plite-0/debounce.js +13 -0
- package/dist/bench/code-out/plite-0/deepClone.js +18 -0
- package/dist/bench/code-out/plite-0/groupBy.js +15 -0
- package/dist/bench/code-out/plite-1/debounce.js +13 -0
- package/dist/bench/code-out/plite-1/deepClone.js +17 -0
- package/dist/bench/code-out/plite-1/groupBy.js +11 -0
- package/dist/bench/code-out/plite-2/debounce.js +12 -0
- package/dist/bench/code-out/plite-2/deepClone.js +17 -0
- package/dist/bench/code-out/plite-2/groupBy.js +14 -0
- package/dist/bench/code-out/plite-3/debounce.js +12 -0
- package/dist/bench/code-out/plite-3/deepClone.js +17 -0
- package/dist/bench/code-out/plite-3/groupBy.js +14 -0
- package/dist/bench/code-out/plite-4/debounce.js +12 -0
- package/dist/bench/code-out/plite-4/deepClone.js +17 -0
- package/dist/bench/code-out/plite-4/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-0/debounce.js +12 -0
- package/dist/bench/code-out/pultra-0/deepClone.js +20 -0
- package/dist/bench/code-out/pultra-0/groupBy.js +14 -0
- package/dist/bench/code-out/pultra-1/debounce.js +10 -0
- package/dist/bench/code-out/pultra-1/deepClone.js +17 -0
- package/dist/bench/code-out/pultra-1/groupBy.js +7 -0
- package/dist/bench/code-out/pultra-2/debounce.js +13 -0
- package/dist/bench/code-out/pultra-2/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-2/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-3/debounce.js +13 -0
- package/dist/bench/code-out/pultra-3/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-3/groupBy.js +15 -0
- package/dist/bench/code-out/pultra-4/debounce.js +13 -0
- package/dist/bench/code-out/pultra-4/deepClone.js +18 -0
- package/dist/bench/code-out/pultra-4/groupBy.js +15 -0
- package/dist/bench/code-out/run-0/debounce.js +12 -0
- package/dist/bench/code-out/run-0/deepClone.js +17 -0
- package/dist/bench/code-out/run-0/groupBy.js +14 -0
- package/dist/bench/code-out/run-1/debounce.js +13 -0
- package/dist/bench/code-out/run-1/deepClone.js +15 -0
- package/dist/bench/code-out/run-1/groupBy.js +13 -0
- package/dist/bench/code-out/run-2/debounce.js +12 -0
- package/dist/bench/code-out/run-2/deepClone.js +18 -0
- package/dist/bench/code-out/run-2/groupBy.js +13 -0
- package/dist/bench/code-out/run-3/debounce.js +12 -0
- package/dist/bench/code-out/run-3/deepClone.js +18 -0
- package/dist/bench/code-out/run-3/groupBy.js +14 -0
- package/dist/bench/code-out/run-4/debounce.js +12 -0
- package/dist/bench/code-out/run-4/deepClone.js +16 -0
- package/dist/bench/code-out/run-4/groupBy.js +13 -0
- package/dist/cli/banner.js +1 -2
- package/dist/cli/common.js +1 -1
- package/dist/cli/doctor.js +48 -12
- package/dist/cli/hosts.js +1 -2
- package/dist/cli/install.js +7 -2
- package/dist/cli/manifest.js +25 -5
- package/dist/cli/uninstall.js +1 -1
- package/dist/extensions/caveman/index.js +6 -2
- package/dist/extensions/caveman/rule-lite.md +77 -0
- package/dist/extensions/caveman/rule-megacave-lite.md +59 -0
- package/dist/extensions/caveman/rule-megacave-ultra.md +59 -0
- package/dist/extensions/caveman/rule.md +1 -1
- package/dist/extensions/combo/index.js +26 -7
- package/dist/extensions/pstack/index.js +3 -1
- package/dist/extensions/pstack/rule.md +88 -12
- package/dist/extensions/rtk/session.js +18 -10
- package/dist/extensions/shared/rtk-eligible.js +84 -0
- package/dist/extensions/shared/rtk-gain.js +1 -1
- package/dist/extensions/shared/sqlite.js +7 -13
- package/dist/extensions/shared/status.js +1 -2
- package/dist/extensions/shared/usage-ledger.js +102 -20
- package/extensions/caveman/index.ts +6 -2
- 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 +27 -7
- package/extensions/pstack/index.ts +3 -1
- 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/session.ts +17 -9
- package/extensions/shared/rtk-eligible.ts +83 -0
- package/extensions/shared/rtk-gain.ts +1 -1
- package/extensions/shared/sqlite.ts +7 -13
- package/extensions/shared/status.ts +1 -2
- package/extensions/shared/usage-ledger.ts +97 -19
- package/extensions/timing/index.ts +1 -2
- package/package.json +7 -4
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Eligible command heads, taken from rtk's own contract rather than a hardcoded guess.
|
|
2
|
+
// `rtk rewrite <probe>` prints a rewrite and exits 0 or 3 when rtk handles the command,
|
|
3
|
+
// and exits 1 when no equivalent exists. Each probe below is a representative workload
|
|
4
|
+
// command, because subcommand-scoped tools such as `gh` rewrite `gh pr list` but not `gh`.
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { normalizeRtkVersion, tersioDataPath } from './utils.js';
|
|
9
|
+
/** Verified against rtk 1.0.1; the drift test fails when rtk's set moves. */
|
|
10
|
+
export const FALLBACK_ELIGIBLE_HEADS = [
|
|
11
|
+
'rtk', 'git', 'gh', 'glab', 'docker', 'kubectl', 'psql', 'aws', 'bunx', 'npx',
|
|
12
|
+
'pytest', 'mypy', 'cat', 'grep', 'rg', 'find', 'tree', 'diff', 'ls', 'ast-grep',
|
|
13
|
+
'wget', 'prettier', 'playwright', 'prisma', 'jest', 'vitest', 'ctest', 'lint',
|
|
14
|
+
'wc', 'tsc', 'curl', 'cargo', 'npm', 'pnpm', 'bun', 'go', 'ruff', 'next',
|
|
15
|
+
'dotnet', 'oc', 'jq', 'make',
|
|
16
|
+
];
|
|
17
|
+
const PROBES = {
|
|
18
|
+
git: 'git log', gh: 'gh pr list', glab: 'glab mr list', docker: 'docker ps',
|
|
19
|
+
kubectl: 'kubectl get pods', psql: 'psql --version', aws: 'aws --version',
|
|
20
|
+
bunx: 'bunx --version', npx: 'npx --version', pytest: 'pytest --version',
|
|
21
|
+
mypy: 'mypy --version', cat: 'cat README.md', grep: 'grep rtk README.md',
|
|
22
|
+
rg: 'rg rtk README.md', find: 'find . -name x', tree: 'tree .', diff: 'diff a b',
|
|
23
|
+
ls: 'ls -la', 'ast-grep': 'ast-grep --version', wget: 'wget --version',
|
|
24
|
+
prettier: 'prettier --version', playwright: 'playwright --version', prisma: 'prisma --version',
|
|
25
|
+
jest: 'jest --version', vitest: 'vitest --version', ctest: 'ctest --version',
|
|
26
|
+
lint: 'lint .', wc: 'wc -l x', tsc: 'tsc --version', curl: 'curl --version',
|
|
27
|
+
cargo: 'cargo test', npm: 'npm run build', pnpm: 'pnpm run build', bun: 'bun run test',
|
|
28
|
+
go: 'go test ./...', ruff: 'ruff check .', next: 'next build', dotnet: 'dotnet build',
|
|
29
|
+
oc: 'oc get pods', jq: 'jq . x', make: 'make',
|
|
30
|
+
};
|
|
31
|
+
function probe(bin, command) {
|
|
32
|
+
try {
|
|
33
|
+
const out = execFileSync(bin, ['rewrite', command], { encoding: 'utf8', timeout: 5000, windowsHide: true });
|
|
34
|
+
return out.trim().length > 0;
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
// Non-zero exit throws; exit 3 still prints a rewrite, exits 1 and 2 do not.
|
|
38
|
+
const err = e;
|
|
39
|
+
if (err.status !== 0 && err.status !== 3)
|
|
40
|
+
return false;
|
|
41
|
+
return String(err.stdout ?? '').trim().length > 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** One set of heads for this rtk build, read straight from its rewrite contract. */
|
|
45
|
+
export function deriveEligibleHeads(bin) {
|
|
46
|
+
const heads = new Set(['rtk']);
|
|
47
|
+
for (const [head, command] of Object.entries(PROBES)) {
|
|
48
|
+
if (probe(bin, command))
|
|
49
|
+
heads.add(head);
|
|
50
|
+
}
|
|
51
|
+
return heads;
|
|
52
|
+
}
|
|
53
|
+
function rtkVersion(bin) {
|
|
54
|
+
try {
|
|
55
|
+
return normalizeRtkVersion(execFileSync(bin, ['--version'], { encoding: 'utf8', timeout: 5000, windowsHide: true }));
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Heads rtk rewrites, cached per binary version so an upgrade re-derives once.
|
|
63
|
+
* Without a binary, or on any read failure, the verified fallback stands.
|
|
64
|
+
*/
|
|
65
|
+
export function eligibleHeads(bin) {
|
|
66
|
+
if (!bin)
|
|
67
|
+
return new Set(FALLBACK_ELIGIBLE_HEADS);
|
|
68
|
+
const version = rtkVersion(bin);
|
|
69
|
+
const file = tersioDataPath('rtk-eligible.json', 'rtk-eligible.json');
|
|
70
|
+
try {
|
|
71
|
+
const cached = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
72
|
+
if (cached.version === version && Array.isArray(cached.heads) && cached.heads.every((h) => typeof h === 'string')) {
|
|
73
|
+
return new Set(cached.heads);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* derive below */ }
|
|
77
|
+
const heads = deriveEligibleHeads(bin);
|
|
78
|
+
try {
|
|
79
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
80
|
+
fs.writeFileSync(file, `${JSON.stringify({ version, heads: [...heads] })}\n`, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
catch { /* the cache is best-effort */ }
|
|
83
|
+
return heads;
|
|
84
|
+
}
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
// One place that runs SQL against a database file.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
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 against a database file. Bun and Node 22.5+ carry SQLite in process, so the
|
|
2
|
+
// `sqlite3` binary is a fallback: hosts without it kept silently reading nothing. Reads return the sqlite3
|
|
3
|
+
// CLI shape (one row per line, cells tab-joined, NULL an empty cell) because every caller parses that.
|
|
4
|
+
// The write path leaves the journal mode alone: the schema sets WAL so a reader never blocks a writer,
|
|
5
|
+
// and the pragma needs exclusive access, so with a reader holding the file it fails after the caller's
|
|
6
|
+
// rows have already committed (see docs/ERRORS.md).
|
|
12
7
|
import { execFileSync } from 'node:child_process';
|
|
13
8
|
import fs from 'node:fs';
|
|
14
9
|
import { createRequire } from 'node:module';
|
|
@@ -51,8 +46,7 @@ function engine() {
|
|
|
51
46
|
catch { /* not Bun, or no SQLite in this build */ }
|
|
52
47
|
try {
|
|
53
48
|
const { DatabaseSync } = requireOptional('node:sqlite');
|
|
54
|
-
// Rows arrive keyed by column name unless the statement returns arrays
|
|
55
|
-
// its key and shifts every later cell, so a Node that cannot return arrays is no engine at all.
|
|
49
|
+
// 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
50
|
const probe = new DatabaseSync(':memory:');
|
|
57
51
|
try {
|
|
58
52
|
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
|
|
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);
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { execFileSync } from 'node:child_process';
|
|
5
5
|
import { homeDir, isPiProcess, piAgentDir, resolveRtkBinary, tersioDataPath } from './utils.js';
|
|
6
|
+
import { eligibleHeads } from './rtk-eligible.js';
|
|
6
7
|
import { sqliteRead } from './sqlite.js';
|
|
7
8
|
export function ledgerPath() {
|
|
8
9
|
const override = process.env.TERSIO_USAGE_FILE;
|
|
@@ -196,6 +197,60 @@ export function dayKey(ts) {
|
|
|
196
197
|
const p = (n) => String(n).padStart(2, '0');
|
|
197
198
|
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
198
199
|
}
|
|
200
|
+
// OpenCode tool parts carry the command in the message blob; sqliteRead is line-based, so newlines are flattened in SQL.
|
|
201
|
+
const OC_BASH_CMD = "replace(replace(json_extract(j.value,'$.state.input.command'), char(10), ' '), char(13), ' ')";
|
|
202
|
+
const OC_BASH_TOOL = "json_extract(j.value,'$.type')='tool' AND (json_extract(j.value,'$.name')='bash' OR json_extract(j.value,'$.name')='shell')";
|
|
203
|
+
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}`;
|
|
204
|
+
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}`;
|
|
205
|
+
/** Every executed bash command in an OpenCode db, v2 first and the legacy table second. */
|
|
206
|
+
export function readOpencodeBashRows(db) {
|
|
207
|
+
const rows = [];
|
|
208
|
+
try {
|
|
209
|
+
if (!fs.existsSync(db))
|
|
210
|
+
return rows;
|
|
211
|
+
let out = '';
|
|
212
|
+
try {
|
|
213
|
+
out += sqliteRead(db, ocBashCurrent);
|
|
214
|
+
}
|
|
215
|
+
catch { /* no v2 table */ }
|
|
216
|
+
try {
|
|
217
|
+
out += sqliteRead(db, ocBashLegacy);
|
|
218
|
+
}
|
|
219
|
+
catch { /* no legacy table */ }
|
|
220
|
+
for (const line of out.split('\n')) {
|
|
221
|
+
if (!line.trim())
|
|
222
|
+
continue;
|
|
223
|
+
const [id, sessionId, command] = line.split('\t');
|
|
224
|
+
if (typeof command !== 'string' || !command.trim())
|
|
225
|
+
continue;
|
|
226
|
+
rows.push({ id: id ?? '', sessionId: sessionId ?? '', command });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch { /* an unreadable db is a skip, not a failure */ }
|
|
230
|
+
return rows;
|
|
231
|
+
}
|
|
232
|
+
// Pre-v2 OpenCode stores one message per JSON file; the same tool-part shape holds the command.
|
|
233
|
+
function opencodeFileBash(file) {
|
|
234
|
+
try {
|
|
235
|
+
const message = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
236
|
+
const parts = Array.isArray(message.content) ? message.content : [];
|
|
237
|
+
const commands = [];
|
|
238
|
+
for (const part of parts) {
|
|
239
|
+
const p = part;
|
|
240
|
+
if (p?.type !== 'tool')
|
|
241
|
+
continue;
|
|
242
|
+
if (p.name !== 'bash' && p.name !== 'shell')
|
|
243
|
+
continue;
|
|
244
|
+
const command = p.state?.input?.command;
|
|
245
|
+
if (typeof command === 'string' && command)
|
|
246
|
+
commands.push(command);
|
|
247
|
+
}
|
|
248
|
+
return { commands, sessionId: typeof message.sessionID === 'string' ? message.sessionID : '' };
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return { commands: [], sessionId: '' };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
199
254
|
export function walkJsonl(dir, out, cap, ext = '.jsonl') {
|
|
200
255
|
let entries;
|
|
201
256
|
try {
|
|
@@ -217,11 +272,6 @@ export function walkJsonl(dir, out, cap, ext = '.jsonl') {
|
|
|
217
272
|
// Recent rows are a newest-first window over lifetime history; aggregates keep everything.
|
|
218
273
|
export const RECENT_LIMIT = 2000;
|
|
219
274
|
const FREE_SUFFIX = /(?::free|-free)$/i;
|
|
220
|
-
const RTK_ELIGIBLE_HEADS = new Set([
|
|
221
|
-
'rtk', 'git', 'grep', 'rg', 'find', 'cat', 'ls', 'tree', 'diff', 'log',
|
|
222
|
-
'npm', 'npx', 'pnpm', 'bun', 'bunx', 'cargo', 'go', 'python', 'pytest',
|
|
223
|
-
'ruff', 'mypy', 'docker', 'kubectl', 'psql', 'aws', 'gh', 'glab', 'wc',
|
|
224
|
-
]);
|
|
225
275
|
export function newSessionAccum() {
|
|
226
276
|
return { byModel: {}, byDay: {}, byDayModel: {}, byTool: {}, byModelMessages: {}, byHost: {}, byAgent: {}, byAgentMessages: {}, byModelMeasured: {}, byModelCostMessages: {}, totals: zeroBreakdown(), messages: 0, costMeasured: 0, recent: [], recentFloor: Number.POSITIVE_INFINITY };
|
|
227
277
|
}
|
|
@@ -569,7 +619,17 @@ export function agentOfSessionText(text) {
|
|
|
569
619
|
return 'main';
|
|
570
620
|
}
|
|
571
621
|
const adoptionFileCache = new Map();
|
|
572
|
-
function
|
|
622
|
+
function countBashCommands(commands, eligible, counts) {
|
|
623
|
+
for (const command of commands) {
|
|
624
|
+
counts.bashCalls += 1;
|
|
625
|
+
const head = leadBinary(command);
|
|
626
|
+
if (eligible.has(head))
|
|
627
|
+
counts.eligibleCalls += 1;
|
|
628
|
+
if (head === 'rtk')
|
|
629
|
+
counts.rtkCalls += 1;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function parseAdoptionFile(file, eligible) {
|
|
573
633
|
const counts = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
574
634
|
let text;
|
|
575
635
|
try {
|
|
@@ -578,7 +638,7 @@ function parseAdoptionFile(file) {
|
|
|
578
638
|
catch {
|
|
579
639
|
return { counts, sessions: 0 };
|
|
580
640
|
}
|
|
581
|
-
|
|
641
|
+
const commands = [];
|
|
582
642
|
for (const line of text.split('\n')) {
|
|
583
643
|
if (!line.includes('tool_execution_start'))
|
|
584
644
|
continue;
|
|
@@ -587,24 +647,22 @@ function parseAdoptionFile(file) {
|
|
|
587
647
|
if (row.type !== 'custom' || row.customType !== 'tool_execution_start' || row.data?.toolName !== 'bash')
|
|
588
648
|
continue;
|
|
589
649
|
const command = row.data.args?.command;
|
|
590
|
-
if (typeof command
|
|
591
|
-
|
|
592
|
-
sawBash = true;
|
|
593
|
-
counts.bashCalls += 1;
|
|
594
|
-
const head = leadBinary(command);
|
|
595
|
-
if (RTK_ELIGIBLE_HEADS.has(head))
|
|
596
|
-
counts.eligibleCalls += 1;
|
|
597
|
-
if (head === 'rtk')
|
|
598
|
-
counts.rtkCalls += 1;
|
|
650
|
+
if (typeof command === 'string')
|
|
651
|
+
commands.push(command);
|
|
599
652
|
}
|
|
600
653
|
catch { /* skip corrupt line */ }
|
|
601
654
|
}
|
|
602
|
-
|
|
655
|
+
countBashCommands(commands, eligible, counts);
|
|
656
|
+
return { counts, sessions: commands.length > 0 ? 1 : 0 };
|
|
603
657
|
}
|
|
604
658
|
export function clearRtkAdoptionCache() {
|
|
605
659
|
adoptionFileCache.clear();
|
|
606
660
|
}
|
|
607
661
|
export function readRtkAdoption() {
|
|
662
|
+
// The eligible set comes from rtk's rewrite contract, cached per binary version.
|
|
663
|
+
const eligible = eligibleHeads(resolveRtkBinary());
|
|
664
|
+
// A cached count is only valid for the head set that produced it.
|
|
665
|
+
const headsKey = [...eligible].sort().join(',');
|
|
608
666
|
const files = [];
|
|
609
667
|
for (const dir of sessionsDirs())
|
|
610
668
|
walkJsonl(dir, files, 2000);
|
|
@@ -626,9 +684,9 @@ export function readRtkAdoption() {
|
|
|
626
684
|
continue;
|
|
627
685
|
}
|
|
628
686
|
let entry = adoptionFileCache.get(file);
|
|
629
|
-
if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs) {
|
|
630
|
-
const parsed = parseAdoptionFile(file);
|
|
631
|
-
entry = { size: stat.size, mtimeMs: stat.mtimeMs, counts: parsed.counts };
|
|
687
|
+
if (!entry || entry.size !== stat.size || entry.mtimeMs !== stat.mtimeMs || entry.heads !== headsKey) {
|
|
688
|
+
const parsed = parseAdoptionFile(file, eligible);
|
|
689
|
+
entry = { size: stat.size, mtimeMs: stat.mtimeMs, heads: headsKey, counts: parsed.counts };
|
|
632
690
|
adoptionFileCache.set(file, entry);
|
|
633
691
|
sessions += parsed.sessions;
|
|
634
692
|
}
|
|
@@ -639,6 +697,30 @@ export function readRtkAdoption() {
|
|
|
639
697
|
eligibleCalls += entry.counts.eligibleCalls;
|
|
640
698
|
rtkCalls += entry.counts.rtkCalls;
|
|
641
699
|
}
|
|
700
|
+
// OpenCode keeps its own store: a v2 sqlite db, or one JSON file per message before v2.
|
|
701
|
+
const opencode = { bashCalls: 0, eligibleCalls: 0, rtkCalls: 0 };
|
|
702
|
+
const opencodeSessions = new Set();
|
|
703
|
+
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DB !== undefined) {
|
|
704
|
+
for (const row of readOpencodeBashRows(opencodeDbPath())) {
|
|
705
|
+
countBashCommands([row.command], eligible, opencode);
|
|
706
|
+
if (row.sessionId)
|
|
707
|
+
opencodeSessions.add(row.sessionId);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
const ocFiles = [];
|
|
711
|
+
if (process.env.TERSIO_SESSIONS_DIR === undefined || process.env.TERSIO_OPENCODE_DIR !== undefined) {
|
|
712
|
+
walkJsonl(opencodeSessionsDir(), ocFiles, 5000, '.json');
|
|
713
|
+
for (const file of ocFiles) {
|
|
714
|
+
const parsed = opencodeFileBash(file);
|
|
715
|
+
countBashCommands(parsed.commands, eligible, opencode);
|
|
716
|
+
if (parsed.sessionId)
|
|
717
|
+
opencodeSessions.add(parsed.sessionId);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
bashCalls += opencode.bashCalls;
|
|
721
|
+
eligibleCalls += opencode.eligibleCalls;
|
|
722
|
+
rtkCalls += opencode.rtkCalls;
|
|
723
|
+
sessions += opencodeSessions.size;
|
|
642
724
|
const missedCalls = Math.max(0, eligibleCalls - rtkCalls);
|
|
643
725
|
return { sessions, bashCalls, eligibleCalls, rtkCalls, missedCalls, adoptionPct: eligibleCalls ? (rtkCalls / eligibleCalls) * 100 : 0 };
|
|
644
726
|
}
|
|
@@ -8,8 +8,12 @@ import { readCavemanDefault } from '../shared/plugin-settings.ts';
|
|
|
8
8
|
import type { ExtensionApi, ExtensionCtx, InputEvent, SessionEntry, SystemPromptEvent } from '../shared/types.ts';
|
|
9
9
|
|
|
10
10
|
const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
//
|
|
12
|
-
|
|
11
|
+
// `rule.md`, `rule-ultra.md`, and `rule-megacave.md` track upstream's three registers: `rule.md` is bundled with
|
|
12
|
+
// one host-adapted line, and the other two are fetched from upstream on install and updated in-session.
|
|
13
|
+
// `lite`, `megacave-lite`, and `megacave-ultra` are our forks of upstream's rows at 84cc3c1, so they are
|
|
14
|
+
// bundled rather than fetched. rule-ultra.md and rule-megacave.md are never edited here: the in-session
|
|
15
|
+
// updater rewrites them from upstream and restores the backup when the write does not match.
|
|
16
|
+
const RULE_FILES = { lite: 'rule-lite.md', full: 'rule.md', ultra: 'rule-ultra.md', 'megacave-lite': 'rule-megacave-lite.md', 'megacave-full': 'rule-megacave.md', 'megacave-ultra': 'rule-megacave-ultra.md' };
|
|
13
17
|
|
|
14
18
|
const DEFAULT_MODE = 'off';
|
|
15
19
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lightcave
|
|
3
|
+
description: >
|
|
4
|
+
Caveman at its lightest register: no filler and no hedging, with articles and
|
|
5
|
+
full sentences kept. Professional but tight. Use for /caveman lite, or the lite
|
|
6
|
+
part of a Combo preset. Stays on until "stop caveman" or "normal mode".
|
|
7
|
+
source: >
|
|
8
|
+
Upstream JuliusBrussee/caveman, skills/caveman/SKILL.md at 84cc3c1, the "Intensity" table, row `lite`.
|
|
9
|
+
The row reads: "No filler/hedging. Keep articles + full sentences. Professional but tight"
|
|
10
|
+
The worked example under it is this register's model answer. The floor, the break-the-rules list,
|
|
11
|
+
and the pre-send check come from the same body. Adapted for Tersio's hosts and hook; no rule here
|
|
12
|
+
is Tersio's own.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# lightcave
|
|
16
|
+
|
|
17
|
+
Respond terse like smart caveman. All technical substance stay. Only fluff die. This is the light register: sentences stay sentences and articles stay, and the words that carry nothing go.
|
|
18
|
+
|
|
19
|
+
## Persistence
|
|
20
|
+
|
|
21
|
+
Every response, whole session, until "stop caveman" or "normal mode". Unsure if still on? It is. `/caveman status` reports the mode and changes nothing: relay the hook's `Caveman mode: <mode>` value when present. With no hook, report the mode you followed, or `off` if caveman was turned off or never active, plus `(not tracked by this host)`.
|
|
22
|
+
|
|
23
|
+
## What changes at lite
|
|
24
|
+
|
|
25
|
+
Drop filler, hedging, pleasantries, recap, and closers. Keep articles, keep full sentences, keep punctuation. Professional but tight.
|
|
26
|
+
|
|
27
|
+
Bad: "Sure! I'd be happy to help. The issue you're experiencing is likely caused by an inline object prop."
|
|
28
|
+
Good: "Your component re-renders because you create a new object reference on each render. Wrap it in `useMemo`."
|
|
29
|
+
|
|
30
|
+
## Floor
|
|
31
|
+
|
|
32
|
+
Never cut: code blocks, 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 one you were not asked to change.
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
### 1. Answer first
|
|
37
|
+
|
|
38
|
+
Answer, then reason, then next step. Pattern: `[thing] [action] [reason]. [next step].`
|
|
39
|
+
|
|
40
|
+
### 2. Kill ceremony
|
|
41
|
+
|
|
42
|
+
No greeting, hedging, pleasantries, recap, or closer. No "Sure!", "Let me", "I'll now", "Hope this helps". No just/really/basically/actually/simply.
|
|
43
|
+
|
|
44
|
+
### 3. Short word
|
|
45
|
+
|
|
46
|
+
"fix" not "implement a solution for". Standard acronyms fine (DB, API, HTTP). Invented abbreviations not (cfg, impl, fn): same tokens, harder read. No arrows.
|
|
47
|
+
|
|
48
|
+
### 4. One idea per sentence
|
|
49
|
+
|
|
50
|
+
20 words max, active voice, imperative for instructions, pronoun only with an obvious referent. Compression and clarity conflict? Clarity wins.
|
|
51
|
+
|
|
52
|
+
### 5. Payload verbatim
|
|
53
|
+
|
|
54
|
+
Code blocks unchanged. Commands, paths, API names exact. Errors quoted exact, shortest decisive line only. A code change in chat shows the changed lines plus 1-2 lines of context, not the whole file, unless the user asks for the whole file or most of it changes.
|
|
55
|
+
|
|
56
|
+
### 6. Tool runs: bounded status
|
|
57
|
+
|
|
58
|
+
No text between routine calls. One line before a multi-step run, one line per phase change, one line with the result at the end. Otherwise text before a call only to clarify, warn, or disambiguate.
|
|
59
|
+
|
|
60
|
+
### 7. User's language
|
|
61
|
+
|
|
62
|
+
Compress the style, not the language. An explicit reply-language instruction wins. Never switch because of quoted text. Technical terms and errors stay verbatim.
|
|
63
|
+
|
|
64
|
+
### 8. Never perform caveman
|
|
65
|
+
|
|
66
|
+
No "caveman mode on", no "me think", no "Caveman:" prefix, no normal answer plus caveman copy. No decorative tables or emoji.
|
|
67
|
+
|
|
68
|
+
## When to break the rules
|
|
69
|
+
|
|
70
|
+
Plain prose, then resume: security warning. Irreversible action, confirmed first. Step order a sentence could scramble. User confused or repeats the question. Anything persisted outside chat — code, comments, commits, docs, issues, PRs, tickets, memory files, third-party messages — with `/caveman-compress`, where the host offers it, exempt. A status line or confirmation the harness asks for. A question you ask the user, with its options.
|
|
71
|
+
|
|
72
|
+
## Pre-send check
|
|
73
|
+
|
|
74
|
+
1. First sentence announces what you will do? Delete.
|
|
75
|
+
2. Last sentence recaps or offers help? Delete.
|
|
76
|
+
3. Any filler or hedging left? Cut it.
|
|
77
|
+
4. Every code span, path, number, and error verbatim? Any sentence with two readings is a full sentence.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: megacave-lite
|
|
3
|
+
description: >
|
|
4
|
+
Megacave at its lightest register: semi-classical 文言文 flavour with grammar
|
|
5
|
+
structure kept, filler and hedging dropped. Use for /caveman megacave-lite. Stays
|
|
6
|
+
on until "stop caveman" or "normal mode".
|
|
7
|
+
source: >
|
|
8
|
+
Upstream JuliusBrussee/caveman, skills/caveman/SKILL.md at 84cc3c1, the "Intensity" table, row `wenyan-lite`.
|
|
9
|
+
The row reads: "Semi-classical. Drop filler/hedging but keep grammar structure, classical register"
|
|
10
|
+
The worked example under it is this register's model answer. The Floor comes from upstream
|
|
11
|
+
skills/megacave/SKILL.md at main. Adapted for Tersio's hosts and hook; no rule here is Tersio's own.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# megacave-lite
|
|
15
|
+
|
|
16
|
+
以文言答,而語法具存。技術之實皆存,唯贅言與游移去之。
|
|
17
|
+
|
|
18
|
+
Semi-classical register with grammar kept. Drop filler, hedging, pleasantries, recap, and closers. Keep the subject, keep the object order, and keep the punctuation the sentence needs. Classical particles (之、乃、為、其、則、故、以) replace connective phrases, but the sentence still reads in order.
|
|
19
|
+
|
|
20
|
+
Bad: 「當然,我很樂意協助您。您遇到的問題大概是因為每次渲染都建立了新的物件參照。」
|
|
21
|
+
Good: 「組件頻重繪,以每繪新生對象參照故。以 `useMemo` 包之。」
|
|
22
|
+
|
|
23
|
+
## Floor
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
### 1. Classical flavour, modern grammar
|
|
30
|
+
|
|
31
|
+
半文言, not 白話. Subject, verb, and object stay where a reader expects them. One particle, one relation.
|
|
32
|
+
|
|
33
|
+
### 2. Drop filler and hedging only
|
|
34
|
+
|
|
35
|
+
No 當然, 我想, 可能, 基本上, 其實. No pleasantries, no recap, no closer. Articles and sentence structure stay.
|
|
36
|
+
|
|
37
|
+
### 3. One idea per sentence
|
|
38
|
+
|
|
39
|
+
Short sentences, one relation each. Any clause with two readings becomes a full sentence.
|
|
40
|
+
|
|
41
|
+
### 4. Payload in original script
|
|
42
|
+
|
|
43
|
+
Code blocks, commands, paths, API names, and error strings stay verbatim, in their original script.
|
|
44
|
+
|
|
45
|
+
### 5. Tool runs: bounded status
|
|
46
|
+
|
|
47
|
+
One line in, one out. Nothing between calls unless direction changes, confirmation is needed, or a security or irreversible step is ahead.
|
|
48
|
+
|
|
49
|
+
### 6. Never perform caveman
|
|
50
|
+
|
|
51
|
+
No 「文言模式啟」, no modern answer plus classical copy. 半文言 not shorter than 白話 for a sentence? Use 白話.
|
|
52
|
+
|
|
53
|
+
## When to break the rules
|
|
54
|
+
|
|
55
|
+
白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirmed 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 — with `/caveman-compress`, where the host offers it, exempt. A status line or confirmation the harness asks for.
|
|
56
|
+
|
|
57
|
+
## Pre-send check
|
|
58
|
+
|
|
59
|
+
Identifiers verbatim, original script? Negations present? Two readings? 白話. Flavour word left? Cut.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: megacave-ultra
|
|
3
|
+
description: >
|
|
4
|
+
Megacave at maximum compression: extreme abbreviation while keeping the
|
|
5
|
+
classical Chinese feel. Use for /caveman megacave-ultra. Stays on until
|
|
6
|
+
"stop caveman" or "normal mode".
|
|
7
|
+
source: >
|
|
8
|
+
Upstream JuliusBrussee/caveman, skills/caveman/SKILL.md at 84cc3c1, the "Intensity" table, row `wenyan-ultra`.
|
|
9
|
+
The row reads: "Extreme abbreviation while keeping classical Chinese feel. Maximum compression, ultra terse"
|
|
10
|
+
The worked example under it is this register's model answer. The Floor comes from upstream
|
|
11
|
+
skills/megacave/SKILL.md at main. Adapted for Tersio's hosts and hook; no rule here is Tersio's own.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# megacave-ultra
|
|
15
|
+
|
|
16
|
+
以文言答,極簡其辭。技術之實皆存,唯字去之。
|
|
17
|
+
|
|
18
|
+
Extreme abbreviation, classical feel. Subjects, copulas, and connectives all go; one mark carries one relation. Arrows for causality. Maximum compression, ultra terse.
|
|
19
|
+
|
|
20
|
+
Bad: 「因為每次渲染都建立了新的物件參照,所以組件會重新渲染。請用 useMemo 包起來。」
|
|
21
|
+
Good: 「新參照→重繪。`useMemo` Wrap。」
|
|
22
|
+
|
|
23
|
+
## Floor
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
### 1. Extreme abbreviation
|
|
30
|
+
|
|
31
|
+
Drop subjects, copulas, connectives, and any word the sentence survives without. One mark per relation: → for cause, 、 for a list, 。 to stop.
|
|
32
|
+
|
|
33
|
+
### 2. Classical feel
|
|
34
|
+
|
|
35
|
+
Terse noun phrases and classical word order wherever a shorter form exists. A particle only when no shorter mark can carry the relation.
|
|
36
|
+
|
|
37
|
+
### 3. Each fact once
|
|
38
|
+
|
|
39
|
+
No restating, no summary after a list. Code change: changed lines plus context, not the whole file.
|
|
40
|
+
|
|
41
|
+
### 4. Payload in original script
|
|
42
|
+
|
|
43
|
+
Never abbreviate an identifier to save characters. Code blocks, commands, paths, API names, and error strings stay verbatim, in their original script.
|
|
44
|
+
|
|
45
|
+
### 5. Tool runs
|
|
46
|
+
|
|
47
|
+
One line in, one out. Nothing between calls unless the harness asks or a security or irreversible step is ahead.
|
|
48
|
+
|
|
49
|
+
### 6. Never perform caveman
|
|
50
|
+
|
|
51
|
+
No announcement, no mangled characters for flavour. Abbreviation not shorter than the sentence? Use the sentence.
|
|
52
|
+
|
|
53
|
+
## When to break the rules
|
|
54
|
+
|
|
55
|
+
白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirmed 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 — with `/caveman-compress`, where the host offers it, exempt. A status line or confirmation the harness asks for.
|
|
56
|
+
|
|
57
|
+
## Pre-send check
|
|
58
|
+
|
|
59
|
+
Identifiers verbatim, original script? Negations present? Two readings? 白話. Flavour character? Cut.
|
|
@@ -77,7 +77,7 @@ Plain prose, then resume:
|
|
|
77
77
|
2. Irreversible action. Confirm in full sentences first.
|
|
78
78
|
3. Step order a fragment could scramble.
|
|
79
79
|
4. User confused or repeats the question.
|
|
80
|
-
5. Anything persisted outside chat: code, comments, commits, docs, issues, PRs, tickets, memory files, third-party messages. `/caveman-compress
|
|
80
|
+
5. Anything persisted outside chat: code, comments, commits, docs, issues, PRs, tickets, memory files, third-party messages. `/caveman-compress`, where the host offers it, is exempt.
|
|
81
81
|
6. Harness asks for a status line or confirmation. Give it. Harness decides *when* you speak, caveman decides *how*.
|
|
82
82
|
7. You ask the user a question or offer options. Full sentences, so the answer comes back right first time.
|
|
83
83
|
|
|
@@ -51,8 +51,11 @@ function piHasUpstreamPonytail(): boolean {
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
// 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.
|
|
54
55
|
const PONYTAIL_FALLBACK_INTENSITY: Record<string, string> = {
|
|
55
|
-
lite: '
|
|
56
|
+
lite: 'Build what was asked. Name the smaller option in one line and let the user pick.',
|
|
57
|
+
full: 'The rules above. Default.',
|
|
58
|
+
ultra: 'Also question the request: before building, push back on any part the need does not justify.',
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
// Fallback text when Ponytail cannot be found; exported for its own test.
|
|
@@ -81,6 +84,15 @@ async function loadPonytailInstructions(mode: string): Promise<string> {
|
|
|
81
84
|
return ponytailFallback(mode);
|
|
82
85
|
}
|
|
83
86
|
|
|
87
|
+
// 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`.
|
|
88
|
+
function savedModes(): { caveman: string; rtk: string; ponytail: string; pstack: string } {
|
|
89
|
+
const hasModeDefaults = ['cavemanDefault', 'rtkDefault', 'ponytailDefault', 'pstackDefault'].some((key) => key in readPluginSettings());
|
|
90
|
+
const preset = COMBO_LEVELS[readComboDefault()];
|
|
91
|
+
return hasModeDefaults
|
|
92
|
+
? { caveman: readCavemanDefault(), rtk: readRtkDefault() ? 'on' : 'off', ponytail: readPonytailDefault(), pstack: readPstackDefault() }
|
|
93
|
+
: { caveman: preset.caveman, rtk: preset.rtk, ponytail: preset.ponytail, pstack: preset.pstack };
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
85
97
|
setExtensionLabel(pi, 'Combo session toggle (all four add-ons)');
|
|
86
98
|
|
|
@@ -99,14 +111,9 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
99
111
|
pi.appendEntry?.('combo-level', { level });
|
|
100
112
|
}
|
|
101
113
|
function restoreConfiguredDefaults(): void {
|
|
102
|
-
const settings = readPluginSettings();
|
|
103
|
-
const hasModeDefaults = ['cavemanDefault', 'rtkDefault', 'ponytailDefault'].some((key) => key in settings);
|
|
104
114
|
const combo = readComboDefault();
|
|
105
115
|
const preset = COMBO_LEVELS[combo];
|
|
106
|
-
|
|
107
|
-
const modes = hasModeDefaults
|
|
108
|
-
? { caveman: readCavemanDefault(), rtk: readRtkDefault() ? 'on' : 'off', ponytail: readPonytailDefault(), pstack: readPstackDefault() }
|
|
109
|
-
: { caveman: preset.caveman, rtk: preset.rtk, ponytail: preset.ponytail, pstack: preset.pstack };
|
|
116
|
+
const modes = savedModes();
|
|
110
117
|
setSharedComboModes(modes);
|
|
111
118
|
if (modes.caveman === 'off' && modes.rtk === 'off' && modes.ponytail === 'off' && modes.pstack === 'off') return;
|
|
112
119
|
pi.appendEntry?.('caveman-mode', { mode: modes.caveman });
|
|
@@ -216,6 +223,18 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
216
223
|
// The configured defaults apply when a fresh session has no persisted mode state.
|
|
217
224
|
const entries = sessionEntries(ctx);
|
|
218
225
|
if (!hasModeState(entries) && (ctx?.hasUI || OPENCODE)) restoreConfiguredDefaults();
|
|
226
|
+
else if (!hasModeState(entries)) {
|
|
227
|
+
// A headless root session (`host -p`) has no UI to publish the bridge, and Ponytail reads only
|
|
228
|
+
// the bridge, so it never loaded: a benchmark on omp measured Block 0 for every Ponytail level and
|
|
229
|
+
// for the Ponytail part of every Combo. Publish the saved modes only while the bridge is clear:
|
|
230
|
+
// once a parent or a toggle has published, the bridge carries that session's own choice and a
|
|
231
|
+
// subagent branch must keep inheriting it. All four go together, because Caveman, RTK, and pstack
|
|
232
|
+
// already restored their own levels and a disagreeing bridge resets them.
|
|
233
|
+
const shared = getSharedComboState();
|
|
234
|
+
if (shared.caveman === 'off' && shared.rtk === 'off' && shared.ponytail === 'off' && shared.pstack === 'off') {
|
|
235
|
+
setSharedComboModes(savedModes());
|
|
236
|
+
}
|
|
237
|
+
}
|
|
219
238
|
// Announced last, so any default above is reflected.
|
|
220
239
|
announceStatus(ctx);
|
|
221
240
|
});
|
|
@@ -231,6 +250,7 @@ export default function comboToggleExtension(pi: ExtensionApi): void {
|
|
|
231
250
|
|
|
232
251
|
pi.on<SystemPromptEvent>('before_agent_start', async (event, ctx) => {
|
|
233
252
|
if (ctx?.hasUI) reconcile(ctx);
|
|
253
|
+
// 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.
|
|
234
254
|
const mode = getSharedComboState().ponytail;
|
|
235
255
|
// On pi, an installed upstream pi-extension is the sole injector, so Tersio only falls back with none.
|
|
236
256
|
if (isPiProcess() && piHasUpstreamPonytail()) return;
|
|
@@ -23,7 +23,9 @@ function buildInstruction(): string {
|
|
|
23
23
|
try { body = readFileSync(join(PSTACK_DIR, RULE_FILE), 'utf8'); } catch {
|
|
24
24
|
return `pstack mode requested, but ${RULE_FILE} is missing. Run: tersio doctor --fix extensions`;
|
|
25
25
|
}
|
|
26
|
-
|
|
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}`;
|
|
27
29
|
return cachedInstruction;
|
|
28
30
|
}
|
|
29
31
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
### Authoring or modifying a skill
|
|
2
|
+
|
|
3
|
+
**You own the skill's voice.**
|
|
4
|
+
|
|
5
|
+
1. Use the **create-skill** skill (the host's built-in for authoring SKILL.md files).
|
|
6
|
+
2. Validate the skill: frontmatter has `name` and `description`, referenced files exist, cross-skill links resolve.
|
|
7
|
+
3. Test cases if structural. Skip if subjective.
|
|
8
|
+
4. Run **Opening a PR**.
|
|
9
|
+
|
|
10
|
+
When in doubt, delete. Keep only prose that changes a decision. Tell it to do the thing and skip the reason. Explain only when the rule is confusing without one. Match tone to scope. Point at structural sources (types, READMEs, config) per the **encode-lessons-in-structure** principle skill. Delegate to other skills by path. Don't restate. A workflow you keep hitting but isn't captured → propose a new skill.
|
|
11
|
+
|
|
12
|
+
**Reply:** summary of the skill, key design decisions, validation notes.
|