@krtclcdy/tersio 2.25.5 → 2.25.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +17 -36
  2. package/dashboard/dist/index.html +15 -15
  3. package/dist/cli/backups.js +7 -14
  4. package/dist/cli/banner.js +4 -3
  5. package/dist/cli/common.js +28 -12
  6. package/dist/cli/currency.js +1 -1
  7. package/dist/cli/dashboard.js +66 -35
  8. package/dist/cli/doctor-fix.js +204 -19
  9. package/dist/cli/doctor.js +226 -39
  10. package/dist/cli/hosts.js +25 -5
  11. package/dist/cli/install.js +144 -48
  12. package/dist/cli/interactive.js +3 -3
  13. package/dist/cli/manifest.js +87 -7
  14. package/dist/cli/profile.js +13 -8
  15. package/dist/cli/rtk-wiring.js +1 -1
  16. package/dist/cli/settings.js +20 -5
  17. package/dist/cli/uninstall.js +3 -3
  18. package/dist/cli/update.js +6 -7
  19. package/dist/cli/usage.js +6 -9
  20. package/dist/extensions/caveman/index.js +4 -2
  21. package/dist/extensions/caveman/rule-megacave.md +5 -3
  22. package/dist/extensions/caveman/rule-ultra.md +4 -4
  23. package/dist/extensions/caveman/rule.md +55 -45
  24. package/dist/extensions/combo/index.js +62 -21
  25. package/dist/extensions/commands/index.js +1 -1
  26. package/dist/extensions/pstack/index.js +119 -0
  27. package/dist/extensions/pstack/rule.md +65 -0
  28. package/dist/extensions/rtk/filter.js +0 -1
  29. package/dist/extensions/rtk/session.js +4 -4
  30. package/dist/extensions/server.js +90 -6
  31. package/dist/extensions/shared/agent-stats.js +214 -278
  32. package/dist/extensions/shared/brand-marks.js +33 -34
  33. package/dist/extensions/shared/carbon.js +1 -1
  34. package/dist/extensions/shared/plugin-settings.js +11 -6
  35. package/dist/extensions/shared/pricing.js +8 -16
  36. package/dist/extensions/shared/rtk-gain.js +4 -9
  37. package/dist/extensions/shared/session-state.js +15 -10
  38. package/dist/extensions/shared/sqlite.js +168 -0
  39. package/dist/extensions/shared/status.js +8 -8
  40. package/dist/extensions/shared/types.js +1 -1
  41. package/dist/extensions/shared/usage-ledger.js +53 -41
  42. package/dist/extensions/shared/usage-store.js +111 -52
  43. package/dist/extensions/shared/utils.js +5 -5
  44. package/dist/extensions/timing/index.js +31 -0
  45. package/dist/extensions/updater/index.js +3 -6
  46. package/dist/tersio.js +8 -3
  47. package/extensions/caveman/index.ts +4 -2
  48. package/extensions/caveman/rule-megacave.md +5 -3
  49. package/extensions/caveman/rule-ultra.md +4 -4
  50. package/extensions/caveman/rule.md +55 -45
  51. package/extensions/combo/index.ts +62 -19
  52. package/extensions/commands/index.ts +1 -1
  53. package/extensions/pstack/index.ts +128 -0
  54. package/extensions/pstack/rule.md +65 -0
  55. package/extensions/rtk/filter.ts +0 -1
  56. package/extensions/rtk/session.ts +4 -4
  57. package/extensions/server.ts +72 -7
  58. package/extensions/shared/agent-stats.ts +226 -278
  59. package/extensions/shared/brand-marks.ts +34 -34
  60. package/extensions/shared/carbon.ts +1 -1
  61. package/extensions/shared/plugin-settings.ts +12 -6
  62. package/extensions/shared/pricing.ts +10 -26
  63. package/extensions/shared/rtk-gain.ts +4 -9
  64. package/extensions/shared/session-state.ts +18 -13
  65. package/extensions/shared/sqlite.ts +168 -0
  66. package/extensions/shared/status.ts +9 -8
  67. package/extensions/shared/types.ts +2 -1
  68. package/extensions/shared/usage-ledger.ts +60 -47
  69. package/extensions/shared/usage-store.ts +120 -52
  70. package/extensions/shared/utils.ts +5 -5
  71. package/extensions/timing/index.ts +45 -0
  72. package/extensions/updater/index.ts +3 -8
  73. package/package.json +17 -8
@@ -61,7 +61,7 @@ function rtkExtensionPath() {
61
61
  const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
62
62
  return path.join(home, '.omp', 'agent', 'extensions', 'rtk.ts');
63
63
  }
64
- // Append rtk.ts after the existing entries (or create the key) so the wire takes effect on the next OMP start.
64
+ // Append rtk.ts so the wire takes effect on the next OMP start.
65
65
  export async function ensureRtkInConfig(options) {
66
66
  const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
67
67
  const configPath = path.join(home, '.omp', 'agent', 'config.yml');
@@ -1,5 +1,5 @@
1
1
  // cli/settings.ts — view/edit session-start defaults and the display currency.
2
- import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, PONYTAIL_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, subagentMarkerFlags, subagentMarkersGiven, } from './common.js';
2
+ import { CAVEMAN_DEFAULTS, COMBO_DEFAULTS, COMBO_PRESET_MODES, PONYTAIL_DEFAULTS, PSTACK_DEFAULTS, cavemanDefaultFlag, comboDefaultFlag, currency, currencyGiven, diagScheduleFlag, dryRun, ponytailDefaultFlag, pstackDefaultFlag, profileFlagsGiven, rtkDefaultFlag, settingArg, subagentMarkerFlags, subagentMarkersGiven, } from './common.js';
3
3
  import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
4
4
  import { CURRENCY_CODES } from './currency.js';
5
5
  import { textTable } from './usage.js';
@@ -14,12 +14,15 @@ function applyFlags(base) {
14
14
  next.cavemanDefault = preset.caveman;
15
15
  next.rtkDefault = preset.rtk;
16
16
  next.ponytailDefault = preset.ponytail;
17
+ next.pstackDefault = preset.pstack;
17
18
  if (cavemanDefaultFlag !== undefined)
18
19
  next.cavemanDefault = cavemanDefaultFlag;
19
20
  if (rtkDefaultFlag !== undefined)
20
21
  next.rtkDefault = rtkDefaultFlag === 'on';
21
22
  if (ponytailDefaultFlag !== undefined)
22
23
  next.ponytailDefault = ponytailDefaultFlag;
24
+ if (pstackDefaultFlag !== undefined)
25
+ next.pstackDefault = pstackDefaultFlag;
23
26
  if (subagentMarkersGiven)
24
27
  next.subagentMarkers = subagentMarkerFlags;
25
28
  if (currencyGiven)
@@ -41,6 +44,7 @@ async function askProfile(current) {
41
44
  cavemanDefault: preset.caveman,
42
45
  rtkDefault: preset.rtk,
43
46
  ponytailDefault: preset.ponytail,
47
+ pstackDefault: preset.pstack,
44
48
  currency: current.currency,
45
49
  backupSchedule: current.backupSchedule,
46
50
  subagentMarkers: current.subagentMarkers,
@@ -82,11 +86,11 @@ async function askDiagSchedule(current) {
82
86
  return null;
83
87
  return picked.value;
84
88
  }
85
- const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis', 'markers'];
89
+ const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'pstack', 'currency', 'diagnosis', 'markers'];
86
90
  function settingsUsage() {
87
- console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|megacave-lite|megacave-full|megacave-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--subagent-marker <text>]... [--dry-run]');
91
+ console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|pstack|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|megacave-lite|megacave-full|megacave-ultra] [--rtk-default on|off] [--ponytail-default off|lite|full|ultra] [--pstack-default on|off] [--currency USD|PHP|EUR|GBP|JPY|KRW|SGD|AUD|CAD|INR] [--diag-schedule manual|daily|weekly|monthly] [--subagent-marker <text>]... [--dry-run]');
88
92
  }
89
- // Single-setting jump: `tersio settings diagnosis` prompts only that value instead of walking the whole chain. Returns false when aborted.
93
+ // Single-setting jump: prompts only that value instead of walking the whole chain; null when aborted.
90
94
  async function runSingleSetting(name, current, nextDiag) {
91
95
  const next = { ...current };
92
96
  let diag = nextDiag;
@@ -105,6 +109,7 @@ async function runSingleSetting(name, current, nextDiag) {
105
109
  next.cavemanDefault = preset.caveman;
106
110
  next.rtkDefault = preset.rtk;
107
111
  next.ponytailDefault = preset.ponytail;
112
+ next.pstackDefault = preset.pstack;
108
113
  break;
109
114
  }
110
115
  case 'caveman': {
@@ -135,6 +140,15 @@ async function runSingleSetting(name, current, nextDiag) {
135
140
  next.ponytailDefault = picked.value;
136
141
  break;
137
142
  }
143
+ case 'pstack': {
144
+ const picked = await askInteractiveChoice('pstack default (rigor mode)', [...PSTACK_DEFAULTS].map((v) => ({
145
+ value: v, label: v,
146
+ })), next.pstackDefault);
147
+ if (picked.status !== 'selected')
148
+ return null;
149
+ next.pstackDefault = picked.value;
150
+ break;
151
+ }
138
152
  case 'currency': {
139
153
  const picked = await askInteractiveChoice('Display currency (usage/dashboard reports)', CURRENCY_CODES.map((v) => ({
140
154
  value: v, label: v,
@@ -173,6 +187,7 @@ function printSettingsTable(current) {
173
187
  ['caveman', current.cavemanDefault, [...CAVEMAN_DEFAULTS].join(' | ')],
174
188
  ['rtk', current.rtkDefault ? 'on' : 'off', 'on | off'],
175
189
  ['ponytail', current.ponytailDefault, [...PONYTAIL_DEFAULTS].join(' | ')],
190
+ ['pstack', current.pstackDefault, [...PSTACK_DEFAULTS].join(' | ')],
176
191
  ['diagnosis', readDiagSchedule(), 'manual | daily | weekly | monthly'],
177
192
  ['markers', current.subagentMarkers.length === 0 ? 'default' : current.subagentMarkers.join(' | '), 'host subagent prompt text'],
178
193
  ];
@@ -180,7 +195,7 @@ function printSettingsTable(current) {
180
195
  console.log(l);
181
196
  }
182
197
  console.log(` Stored: ${tersioSettingsFile()}`);
183
- console.log(` Combo ${current.comboDefault} sets caveman/rtk/ponytail. Tune one: tersio settings <name>`);
198
+ console.log(` Combo ${current.comboDefault} sets all four modes. Tune one: tersio settings <name>`);
184
199
  }
185
200
  async function runSettings() {
186
201
  const current = await storedProfile();
@@ -11,6 +11,7 @@ const PONYTAIL_PKG = '@dietrichgebert/ponytail';
11
11
  const PI_TREE_DIRS = [
12
12
  // What an install writes now.
13
13
  'caveman',
14
+ 'pstack',
14
15
  'rtk',
15
16
  'updater',
16
17
  'combo',
@@ -100,7 +101,7 @@ async function removePiLayer(host, shouldDryRun, shouldRemovePonytail, shouldRem
100
101
  await removeCopiedPonytailSkills(shouldDryRun);
101
102
  }
102
103
  await Promise.all(targets.map((t) => removeUninstallTarget(t, shouldDryRun)));
103
- // rtk's wiring is a loose file, not one of the tree dirs, so it survived --remove-rtk on pi while OMP removed its copy.
104
+ // rtk's wiring is a loose file, not a tree dir, so it survived --remove-rtk on pi while OMP removed its copy.
104
105
  if (shouldRemoveRtk) {
105
106
  await removeUninstallTarget(rtkWiring, shouldDryRun, false);
106
107
  await removeUninstallTarget(path.join(BUN_BIN_DIR, RTK_BINARY_NAME), shouldDryRun, false);
@@ -281,7 +282,6 @@ async function runUninstall(options = {}) {
281
282
  return false;
282
283
  // Remove extension directories (independent paths, so concurrently).
283
284
  await Promise.all(targets.map((t) => removeUninstallTarget(t, shouldDryRun)));
284
- // Remove Combo, mode-reinforcement, and rtk registrations; Ponytail on request.
285
285
  const configRaw = await readTextIfExists(configPath);
286
286
  if (configRaw) {
287
287
  let lines = configRaw.split('\n');
@@ -291,7 +291,7 @@ async function runUninstall(options = {}) {
291
291
  return false;
292
292
  if (shouldRemovePonytail && l.includes('ponytail') && l.includes('pi-extension'))
293
293
  return false;
294
- // Our rtk wiring, absolute or relative. Leaving it listed would keep OMP loading a hook for a package that is no longer installed.
294
+ // Our rtk wiring, absolute or relative. Leaving it listed would keep OMP loading a hook for an uninstalled package.
295
295
  const entry = l.trim().replace(/^-\s*/, '').replace(/^['"]|['"]$/g, '');
296
296
  if (entry.endsWith(`extensions${path.sep}rtk.ts`) || entry.endsWith('extensions/rtk.ts'))
297
297
  return false;
@@ -35,7 +35,7 @@ async function latestPublishedVersion() {
35
35
  return null;
36
36
  }
37
37
  }
38
- // A newer published version, null when up to date, 'unknown' when the registry is unreachable. Cached for a TTL unless force, which must not trust a cache.
38
+ // A newer version, null when up to date, 'unknown' when the registry is unreachable. Cached for a TTL unless force.
39
39
  async function checkForUpdate(force = false) {
40
40
  const cachePath = tersioDataPath('update-check.json', 'tersio-update-check.json');
41
41
  const cached = parseJsonObject(await readTextIfExists(cachePath));
@@ -48,7 +48,7 @@ async function checkForUpdate(force = false) {
48
48
  await fs.writeFile(cachePath, JSON.stringify({ latest, lastCheck: Date.now() }) + '\n', 'utf8').catch(() => { });
49
49
  return newerThan(latest, PACKAGE_VERSION) ? latest : null;
50
50
  }
51
- // Unreachable registry: a stale cache naming a newer release is still actionable, else report unknown so nobody claims "latest".
51
+ // Unreachable registry: a stale cache naming a newer release is actionable, else unknown, so none claim "latest".
52
52
  return cached?.latest && newerThan(cached.latest, PACKAGE_VERSION) ? cached.latest : 'unknown';
53
53
  }
54
54
  // Race a probe against a timeout; failures resolve null so nothing blocks.
@@ -74,7 +74,7 @@ async function execInherit(cmd, args) {
74
74
  if (code !== 0)
75
75
  throw new Error(`${cmd} exited with code ${code}`);
76
76
  }
77
- // Current → latest per add-on. Every probe is capped and nullable, and the plan prints `unknown` for anything unreachable.
77
+ // Current → latest per add-on. Probes are capped and nullable; the plan prints `unknown` for anything unreachable.
78
78
  async function probeUpdatePlan(cliLatest) {
79
79
  const rtkBin = resolveRtkBinary();
80
80
  const ponytailPkg = path.join(OMP_PLUGINS_DIR, 'node_modules', '@dietrichgebert', 'ponytail', 'package.json');
@@ -98,7 +98,7 @@ async function probeUpdatePlan(cliLatest) {
98
98
  ponytail: ponytailLocalText ? parseJsonObject(ponytailLocalText)?.version ?? null : null,
99
99
  };
100
100
  }
101
- // Every add-on prints a version jump when stale, "up to date" otherwise, and "unknown" when its source cannot be reached.
101
+ // Every add-on prints a version jump when stale, "up to date" otherwise, and "unknown" when unreachable.
102
102
  function planLines(plan) {
103
103
  const stale = [];
104
104
  const status = [];
@@ -147,7 +147,6 @@ async function runLatestUpdate() {
147
147
  // `--prefer-online` beats the local cache, so a stale @latest cannot pin down.
148
148
  const cliLatest = await latestPublishedVersion();
149
149
  const target = cliLatest && /^\d+\.\d+\.\d+$/.test(cliLatest) ? `@${cliLatest}` : '@latest';
150
- // Best-effort: unreachable probes print as unknown.
151
150
  const plan = await probeUpdatePlan(cliLatest);
152
151
  const { stale, status } = planLines(plan);
153
152
  if (dryRun) {
@@ -181,7 +180,7 @@ async function runLatestUpdate() {
181
180
  process.exitCode = 1;
182
181
  return;
183
182
  }
184
- // The npx delegation below only refreshes the OMP-side files; the globally installed CLI keeps its old version until npm -g runs. Refresh both.
183
+ // The npx delegation below only refreshes the OMP-side files; the global CLI keeps its old version until npm -g runs.
185
184
  const globalArgs = IS_WINDOWS
186
185
  ? ['/d', '/s', '/c', 'npm', 'install', '-g', `${PACKAGE_NAME}${target}`, '--no-audit', '--no-fund', '--prefer-online']
187
186
  : ['install', '-g', `${PACKAGE_NAME}${target}`, '--no-audit', '--no-fund', '--prefer-online'];
@@ -211,7 +210,7 @@ async function runLatestUpdate() {
211
210
  ...forwardedArgs,
212
211
  ];
213
212
  const npmCommandArgs = IS_WINDOWS ? ['/d', '/s', '/c', 'npm', ...npmArgs] : npmArgs;
214
- // Inherited stdio, no outer spinner; the delegated installer runs quiet and this parent owns both the plan line and the closing summary.
213
+ // Inherited stdio, no outer spinner; the quiet installer runs, so this parent owns the plan line and closing summary.
215
214
  try {
216
215
  await execInherit(npmCommand, npmCommandArgs);
217
216
  console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION} and add-ons updated.`);
package/dist/cli/usage.js CHANGED
@@ -6,7 +6,7 @@ import { readUsageDb, reparseGuardReport, syncUsageDb, usageDbPath } from '../ex
6
6
  import { withInteractiveSpinner } from './interactive.js';
7
7
  import { PACKAGE_VERSION, currency } from './common.js';
8
8
  import { formatCurrency } from './currency.js';
9
- // RTK's history is machine-wide; rows group by command alone. 2000 groups covers a long history while still bounding /data.json.
9
+ // RTK history is machine-wide; rows group by command. 2000 groups covers a long history while bounding /data.json.
10
10
  const RTK_COMMAND_ROWS = 2000;
11
11
  export function summarizeUsage(rows) {
12
12
  refreshPricesIfStale();
@@ -17,7 +17,7 @@ export function summarizeUsage(rows) {
17
17
  detailCounts[r.detail] = (detailCounts[r.detail] ?? 0) + 1;
18
18
  }
19
19
  const byDetail = Object.entries(detailCounts).sort((a, b) => b[1] - a[1]).slice(0, 10);
20
- // usage.db is a cache of the same per-message rows: sync best-effort, read stored, fall back to the live parse when the store is missing or stale.
20
+ // usage.db caches the same per-message rows: sync best-effort, read stored, fall back to the live parse when stale.
21
21
  let synced = false;
22
22
  try {
23
23
  synced = syncUsageDb();
@@ -62,8 +62,7 @@ export function summarizeUsage(rows) {
62
62
  const weakModels = [];
63
63
  const measuredModels = [];
64
64
  for (const [model, t] of Object.entries(session.byModel)) {
65
- // A model is measured only when the host priced every one of its messages; a partly measured
66
- // model would read its unpriced messages as zero, which is the one error worse than a guess.
65
+ // A model counts as measured only when the host priced every message, or unpriced ones would read as zero.
67
66
  const covered = (session.byModelCostMessages[model] ?? 0) >= (session.byModelMessages[model] ?? 0);
68
67
  const measured = covered ? session.byModelMeasured[model] : undefined;
69
68
  const c = usdCost(t, model, measured);
@@ -71,8 +70,7 @@ export function summarizeUsage(rows) {
71
70
  priceTiers[c.tier] = (priceTiers[c.tier] ?? 0) + 1;
72
71
  if (c.tier === 'measured')
73
72
  measuredModels.push(model);
74
- // A tier that is not firm is an estimate, so it is kept out of the firm total rather than
75
- // summed beside a measured cost that would make the whole figure read as a bill.
73
+ // A non-firm tier is kept out of the firm total, or a guessed cost would make the figure read as a bill.
76
74
  if (!PRICE_TIERS_FIRM.includes(c.tier)) {
77
75
  estimatedUsd += c.usd;
78
76
  weakModels.push(model);
@@ -180,7 +178,7 @@ const ROW_CAP = 15;
180
178
  function pad(s, n) {
181
179
  return s.length >= n ? s : s + ' '.repeat(n - s.length);
182
180
  }
183
- // Box-drawing table, plain text (piped-safe, byte-stable). Numeric columns right-align; long cells truncate with an ellipsis. Shared with cli/settings.ts.
181
+ // Box-drawing table, plain text (piped-safe, byte-stable). Numbers right-align; long cells truncate with an ellipsis.
184
182
  function textTable(headers, rows, right = [], maxW = 32) {
185
183
  const cells = [headers, ...rows].map((r) => r.map((c) => (c.length > maxW ? c.slice(0, maxW - 1) + '…' : c)));
186
184
  const widths = headers.map((_, i) => Math.max(...cells.map((r) => r[i].length)));
@@ -203,8 +201,7 @@ function printReport(report) {
203
201
  // A tier that is not firm is named, so a figure that is partly a guess never reads as a bill.
204
202
  const weak = ['tail', 'default'].reduce((sum, tier) => sum + (report.priceTiers[tier] ?? 0), 0);
205
203
  const overridden = report.priceTiers.override ?? 0;
206
- // The count stays in the cell; the names go on their own line below, because the cell is a
207
- // fixed 118 columns and a list of model ids would be truncated to nothing.
204
+ // The count stays in the cell; names go below, since a 118-column cell would truncate a model-id list.
208
205
  const listed = report.weakModels.map((m) => displayModelId(m));
209
206
  const measuredCount = report.measuredModels.length;
210
207
  const costNote = weak > 0
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'node:url';
3
3
  import { getSharedComboState, hasModeState, isOmpSubagentPrompt, lastCustomValue, normalizeInputCommand, normalizeMode, reconcileSharedComboEntries, sessionEntries, setSharedComboListener, setSharedComboMode, systemPromptIncludes } from '../shared/session-state.js';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { injectPromptText, onHostEvent, setExtensionLabel } from '../shared/host.js';
6
- import { announceStatus } from '../shared/status.js';
6
+ import { announceStatus, formatCavemanStatus } from '../shared/status.js';
7
7
  import { readCavemanDefault } from '../shared/plugin-settings.js';
8
8
  const CAVERN_DIR = dirname(fileURLToPath(import.meta.url));
9
9
  // Upstream ships one skill per register: default, ultracave, megacave.
@@ -66,6 +66,8 @@ export default function cavemanSessionExtension(pi) {
66
66
  }
67
67
  if (arg === 'status') {
68
68
  announceStatus(ctx);
69
+ // The footer carries one line, so the mode is answered here instead of standing as a second status key.
70
+ ctx?.ui?.notify?.(formatCavemanStatus(currentMode), 'info');
69
71
  return;
70
72
  }
71
73
  if (!setMode(arg, ctx)) {
@@ -84,7 +86,7 @@ export default function cavemanSessionExtension(pi) {
84
86
  // A subagent's branch has no Tersio entries; reconciling it would wipe shared state.
85
87
  if (hasModeState(entries))
86
88
  reconcileSharedComboEntries(entries);
87
- // Persisted state wins, then a mode already chosen this session, then the configured default. `off` is the start value, not "unset".
89
+ // Persisted state wins, then a mode already chosen, then the default. `off` is the start value, not unset.
88
90
  const persisted = resolveMode(entries, '');
89
91
  const alreadyChosen = currentMode !== DEFAULT_MODE;
90
92
  currentMode = persisted || (alreadyChosen ? currentMode : normalizeMode('caveman', readCavemanDefault())) || DEFAULT_MODE;
@@ -15,11 +15,11 @@ Megacave is caveman in Classical Chinese. 文言文: subjects omitted where reco
15
15
 
16
16
  ## Persistence
17
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 or say `Caveman mode: unknown`.
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
19
 
20
20
  ## Floor
21
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 白話.
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
23
 
24
24
  ## Rules
25
25
 
@@ -39,6 +39,8 @@ Good: "池蓄已開之連,不逐請而新開,省握手之費。"
39
39
 
40
40
  ### 3. Each fact once
41
41
 
42
+ Code change: changed lines plus context, not the whole file.
43
+
42
44
  Good: "新參照則重繪。`useMemo` 包之。"
43
45
 
44
46
  ### 4. Payload in original script
@@ -56,7 +58,7 @@ No "文言模式啟", no modern answer plus classical copy. 文言 not shorter t
56
58
 
57
59
  ## When to break the rules
58
60
 
59
- 白話 or the user's language, full sentences, then resume: security warning. Irreversible action, confirm first. Any clause with two readings. User confused. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
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.
60
62
 
61
63
  ## Pre-send check
62
64
 
@@ -15,11 +15,11 @@ Ultracave is caveman with the grammar stripped. Payload only.
15
15
 
16
16
  ## Persistence
17
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 or say `Caveman mode: unknown`.
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
19
 
20
20
  ## Floor
21
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.
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
23
 
24
24
  ## Rules
25
25
 
@@ -37,7 +37,7 @@ Good: "Yes. Null check first."
37
37
 
38
38
  ### 3. Each fact once
39
39
 
40
- No restating, no summary after a list.
40
+ No restating, no summary after a list. Code change: changed lines plus context, not the whole file.
41
41
 
42
42
  Good: "Pool reuses open DB connections. No per-request handshake."
43
43
 
@@ -56,7 +56,7 @@ No prefix, no announcement, no mangled verbs for flavor. Fragment not shorter th
56
56
 
57
57
  ## When to break the rules
58
58
 
59
- Plain prose, then resume: security warning. Irreversible action, confirm first. Any fragment with two readings. User confused. Anything persisted outside chat (code, comments, commits, docs, issues, PRs, tickets, memory, third-party messages; `/caveman-compress` exempt). Harness asks for a status line.
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
60
 
61
61
  ## Pre-send check
62
62
 
@@ -1,79 +1,89 @@
1
1
  ---
2
2
  name: caveman
3
3
  description: >
4
- Ultra-compressed communication mode that cuts output tokens while keeping
5
- technical accuracy. Levels: lite, full, ultra and the megacave variants. Use for
6
- /caveman, "caveman mode", "talk like caveman", "be brief" or "less tokens".
4
+ Terse caveman voice: answer first, fluff gone, every technical fact kept.
5
+ Use for /caveman, "caveman mode", "talk like caveman", "be brief", "less
6
+ tokens". Stays on until "stop caveman" or "normal mode".
7
7
  ---
8
8
 
9
+ # caveman
10
+
9
11
  Respond terse like smart caveman. All technical substance stay. Only fluff die.
10
12
 
13
+ Caveman is a voice, not broken grammar. Reader pays per token and reads in a terminal. Every word earns its place. Every fact survives.
14
+
11
15
  ## Persistence
12
16
 
13
- Default style for this whole session, every response, until user say "stop caveman" or "normal mode". Keep terse on long sessions no filler drift.
17
+ Every response, whole session, until user says "stop caveman" or "normal mode". Unsure if still on? It is. Confirm the switch-off in one line.
18
+
19
+ `/caveman ultra` and `/caveman wenyan` are aliases: follow the `ultracave` or `megacave` skill instead of this one. `/caveman status` reports the mode and changes nothing. Relay the hook's `Caveman mode: <mode>` value when present. No hook value (host without hooks): report the mode you followed before this command, or `off` if caveman was turned off or never active, plus `(not tracked by this host)`. Example: `Caveman mode: caveman (not tracked by this host)`. Loading this skill to answer status is not activation. Never infer a mode from the configured default.
20
+
21
+ ## Why
14
22
 
15
- Default: **full**. Switch: `/caveman lite|full|ultra|megacave-lite|megacave-full|megacave-ultra|off`.
23
+ 1. Every output token is billed and read. Filler costs twice.
24
+ 2. Code, commands, paths, numbers, errors are the payload. One changed character breaks them.
25
+ 3. Ceremony is expensive, grammar is cheap. "Sure, I'd be happy to help" is ten tokens. "the" is one.
26
+ 4. A dropped negation costs more than every token saved. Clarity beats compression.
16
27
 
17
28
  ## Rules
18
29
 
19
- Drop: articles (a/an/the), filler (just/really/basically/actually/simply), pleasantries (sure/certainly/of course/happy to), hedging. Fragments OK. Short synonyms (big not extensive, fix not "implement a solution for"). No tool-call narration, no decorative tables/emoji, no dumping long raw error logs unless asked quote shortest decisive line. Standard well-known tech acronyms OK (DB/API/HTTP); never invent new abbreviations (cfg/impl/req/res/fn) tokenizer split them same as full word: zero token saved, reader still decode. Full word cheaper AND clearer. No causal arrows (→) either own token, save nothing. Technical terms exact. Code blocks unchanged. Errors quoted exact.
30
+ ### 1. Answer first
31
+
32
+ Answer, then reason, then next step. Pattern: `[thing] [action] [reason]. [next step].`
33
+
34
+ Bad: "Sure! I'd be happy to help. The issue you're experiencing is likely caused by..."
35
+ Good: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
36
+
37
+ ### 2. Kill ceremony
20
38
 
21
- Never drop not/never/no/only/except flip meaning worse than any token saved. Numbers, units exact.
39
+ No greeting, hedging, pleasantries, recap, or closer. No "Sure!", "Let me", "I'll now", "Hope this helps". No just/really/basically/actually/simply.
22
40
 
23
- Never ADD word to sound caveman. Compression only style never grow output. No inserted pronoun or copula to fake broken grammar: "when it not" cost one token more than "when not" and say same thing. Keep correct verb form when correct form cost same "sees" one token, "see" one token, so mangle buy nothing and read worse. Same rule as abbreviations and arrows: if caveman phrasing not shorter than plain phrasing, use plain.
41
+ ### 3. Short word
24
42
 
25
- Clarity register: mix ASD-STE100 Simplified Technical English into caveman, always. One idea per sentence. Sentence short, target 20 words max. Active voice. Present tense where true. One word one meaning: same term for same thing every time, no synonym rotation. Instruction = imperative: "Run X", not "X should be run". Noun cluster 3 words max. Pronoun only with one clear referent, else repeat noun. Caveman cut filler; STE keep what make meaning unambiguous. Conflict between them → clarity win.
43
+ "fix" not "implement a solution for". Standard acronyms fine (DB, API, HTTP). Invented abbreviations not (cfg, impl, fn): same tokens, harder read. No arrows.
26
44
 
27
- Tool calls: fire direct. No preamble, plan, or progress note before or between calls. After result: next call direct or final answer never announce next call. Text before call only to clarify, warn security/irreversible, or resolve ambiguity.
45
+ ### 4. Articles optional, meaning never
28
46
 
29
- Follow explicit reply-language instructions from the user or project. Otherwise preserve the user's dominant language. Never switch because of example text or multilingual context elsewhere. Compress the style, not the language. Every emitted line in that language openings, pre-tool status lines, all not just final reply. ALWAYS keep technical terms, code, API names, CLI commands, commit-type keywords (feat/fix/...), and exact error strings verbatim unless user explicitly ask for translation.
47
+ Drop a/an/the when the sentence still reads in one pass. Fragments fine. Never drop not/never/no/only/except. Numbers and units exact.
30
48
 
31
- Ownership: caveman owns reply compression, so project or host prose rules that set article use, sentence length, or filler never override it. Treat those as applying to persisted artifacts instead: code, comments, commits, docs, and issue text. Any project rule naming caveman or tersio directly wins over both.
49
+ Bad: "Migration drop column backup first."
50
+ Good: "Back up first. Then run migration: it drops the column."
32
51
 
33
- 'Drop articles' = article languages only. Where small markers carry case/role (particles, postpositions), keep them grammar, not filler; compress politeness/filler instead.
52
+ ### 5. One idea per sentence
34
53
 
35
- Answer directly in this style. Skip "caveman mode on", "me caveman think", "Caveman:" prefix or recap redundant with the reply itself. No normal answer plus caveman duplicate. User ask what mode is → say so plainly.
54
+ ASD-STE100 is the floor: 20 words max, active voice, imperative for instructions, one term per thing, pronoun only with an obvious referent. Compression and clarity conflict? Clarity wins.
36
55
 
37
- Pattern: `[thing] [action] [reason]. [next step].`
56
+ ### 6. Payload verbatim
38
57
 
39
- Not: "Sure! I'd be happy to help you with that. The issue you're experiencing is likely caused by..."
40
- Yes: "Bug in auth middleware. Token expiry check use `<` not `<=`. Fix:"
58
+ Code blocks unchanged. Commands, paths, API names exact. Errors quoted exact, shortest decisive line only. Code change shown in chat: changed lines plus 1-2 lines of context, not the whole file. Whole file only if the user asks, the file is new, or most of it changes. Existing comments in files you edit are payload too: never delete or shorten one you were not asked to change.
41
59
 
42
- ## Intensity
60
+ ### 7. Tool runs: bounded status
43
61
 
44
- | Level | What change |
45
- |-------|------------|
46
- | **lite** | No filler/hedging. Keep articles + full sentences. Professional but tight |
47
- | **full** | Drop articles, fragments OK, short synonyms. Classic caveman. No tool-call narration, no decorative tables/emoji, no long raw error-log dumps unless asked. Standard acronyms OK; no invented abbreviations |
62
+ 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.
48
63
 
49
- Example "Why React component re-render?"
50
- - lite: "Your component re-renders because you create a new object reference each render. Wrap it in `useMemo`."
51
- - full: "New object ref each render. Inline object prop = new ref = re-render. Wrap in `useMemo`."
64
+ ### 8. User's language
52
65
 
53
- Example "Explain database connection pooling."
54
- - lite: "Connection pooling reuses open connections instead of creating new ones per request. Avoids repeated handshake overhead."
55
- - full: "Pool reuse open DB connections. No new connection per request. Skip handshake overhead."
66
+ Compress the style, not the language. An explicit reply-language instruction wins. Never switch because of quoted text. Technical terms and errors stay verbatim. Particles and case markers are grammar, not filler.
56
67
 
57
- ## Auto-Clarity
68
+ ### 9. Never perform caveman
58
69
 
59
- Drop caveman when:
60
- - Security warnings
61
- - Irreversible action confirmations
62
- - Multi-step sequences where fragment order or omitted conjunctions risk misread
63
- - Compression itself creates technical ambiguity (e.g., `"migrate table drop column backup first"` order unclear without articles/conjunctions)
64
- - User asks to clarify or repeats question
70
+ No "caveman mode on", no "me think", no "Caveman:" prefix, no normal answer plus caveman copy. No decorative tables or emoji. Never add a word to sound caveman. Caveman phrasing not shorter than plain? Use plain.
65
71
 
66
- Resume caveman after clear part done.
72
+ ## When to break the rules
67
73
 
68
- Example shows FORMAT only write warning in session language, not example's.
74
+ Plain prose, then resume:
69
75
 
70
- Example destructive op:
71
- > **Warning:** This will permanently delete all rows in the `users` table and cannot be undone.
72
- > ```sql
73
- > DROP TABLE users;
74
- > ```
75
- > Caveman resume. Verify backup exist first.
76
+ 1. Security warning.
77
+ 2. Irreversible action. Confirm in full sentences first.
78
+ 3. Step order a fragment could scramble.
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` exempt.
81
+ 6. Harness asks for a status line or confirmation. Give it. Harness decides *when* you speak, caveman decides *how*.
82
+ 7. You ask the user a question or offer options. Full sentences, so the answer comes back right first time.
76
83
 
77
- ## Boundaries
84
+ ## Pre-send check
78
85
 
79
- Persisted outside chat: write normal prose code, comments, commits, docs, issue/PR/MR/defect/ticket/bug-report text, memory files, third-party messages (/caveman-compress exempt). "Open a defect" or "file a bug" mean the same as "open issue": body go to other humans, so body normal English. "stop caveman" or "normal mode": revert. Level persist until changed or session end.
86
+ 1. First sentence announces what you will do? Delete.
87
+ 2. Last sentence recaps or offers help? Delete.
88
+ 3. Every not/never/no/only present? Every code span, path, number, error verbatim?
89
+ 4. Any sentence with two readings? Make it a full sentence.