@krtclcdy/tersio 2.25.2 → 2.25.4
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 +3 -3
- package/dashboard/dist/index.html +1 -1
- package/{cli → dist/cli}/common.js +4 -3
- package/{cli → dist/cli}/dashboard.js +30 -13
- package/{cli → dist/cli}/doctor-fix.js +2 -4
- package/{cli → dist/cli}/doctor.js +13 -6
- package/{cli → dist/cli}/install.js +56 -15
- package/{cli → dist/cli}/manifest.js +4 -2
- package/{cli → dist/cli}/settings.js +5 -23
- package/{cli → dist/cli}/uninstall.js +1 -1
- package/{cli → dist/cli}/update.js +4 -5
- package/{extensions → dist/extensions}/ai-addons-updater/index.js +4 -4
- package/{extensions → dist/extensions}/caveman-session/index.js +12 -5
- package/dist/extensions/caveman-session/rule-ultra.md +63 -0
- package/dist/extensions/caveman-session/rule.md +79 -0
- package/{extensions → dist/extensions}/combo-toggle/index.js +39 -29
- package/{extensions → dist/extensions}/lib/utils.js +26 -1
- package/{extensions → dist/extensions}/opencode/server.js +45 -1
- package/dist/extensions/rtk-filter/index.js +87 -0
- package/{extensions → dist/extensions}/rtk-session/index.js +3 -2
- package/{extensions → dist/extensions}/shared/host.js +18 -12
- package/{extensions → dist/extensions}/shared/plugin-settings.js +1 -1
- package/{extensions → dist/extensions}/shared/session-state.js +17 -3
- package/{extensions → dist/extensions}/shared/status.js +7 -1
- package/{tersio.js → dist/tersio.js} +1 -1
- package/extensions/ai-addons-updater/index.ts +4 -5
- package/extensions/caveman-session/index.ts +12 -5
- package/extensions/caveman-session/rule-megacave.md +63 -0
- package/extensions/caveman-session/rule.md +4 -2
- package/extensions/combo-toggle/index.ts +37 -29
- package/extensions/lib/utils.ts +22 -1
- package/extensions/opencode/server.ts +38 -1
- package/extensions/rtk-filter/index.ts +90 -0
- package/extensions/rtk-session/index.ts +3 -2
- package/extensions/shared/host.ts +26 -13
- package/extensions/shared/plugin-settings.ts +1 -1
- package/extensions/shared/session-state.ts +18 -3
- package/extensions/shared/status.ts +8 -1
- package/extensions/shared/types.ts +10 -2
- package/package.json +16 -15
- package/extensions/opencode/index.js +0 -104
- /package/{cli → dist/cli}/banner.js +0 -0
- /package/{cli → dist/cli}/currency.js +0 -0
- /package/{cli → dist/cli}/hosts.js +0 -0
- /package/{cli → dist/cli}/interactive.js +0 -0
- /package/{cli → dist/cli}/profile.js +0 -0
- /package/{cli → dist/cli}/reset.js +0 -0
- /package/{cli → dist/cli}/rtk-wiring.js +0 -0
- /package/{cli → dist/cli}/usage.js +0 -0
- /package/{extensions/caveman-session/rule-wenyan.md → dist/extensions/caveman-session/rule-megacave.md} +0 -0
- /package/{extensions → dist/extensions}/shared/carbon.js +0 -0
- /package/{extensions → dist/extensions}/shared/pricing.js +0 -0
- /package/{extensions → dist/extensions}/shared/rtk-gain.js +0 -0
- /package/{extensions → dist/extensions}/shared/types.js +0 -0
- /package/{extensions → dist/extensions}/shared/usage-ledger.js +0 -0
- /package/{extensions → dist/extensions}/shared/usage-store.js +0 -0
- /package/{extensions → dist/extensions}/tersio-commands/index.js +0 -0
|
@@ -4,7 +4,8 @@ import { promises as fs } from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
-
import {
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { findPackageRoot, readTextIfExists } from '../extensions/lib/utils.js';
|
|
8
9
|
import { parseCurrencyFlag, readStoredCurrency } from './currency.js';
|
|
9
10
|
const IS_WINDOWS = process.platform === 'win32';
|
|
10
11
|
const RTK_BINARY_NAME = IS_WINDOWS ? 'rtk.exe' : 'rtk';
|
|
@@ -15,8 +16,8 @@ const BUN_BIN_DIR = path.join(HOME, '.bun', 'bin');
|
|
|
15
16
|
const OMP_BIN = IS_WINDOWS ? 'omp.cmd' : 'omp';
|
|
16
17
|
const PACKAGE_NAME = '@krtclcdy/tersio';
|
|
17
18
|
const PACKAGE_BIN = 'tersio';
|
|
18
|
-
const { version: PACKAGE_VERSION } = createRequire(import.meta.url)('
|
|
19
|
-
const CAVEMAN_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra', '
|
|
19
|
+
const { version: PACKAGE_VERSION } = createRequire(import.meta.url)(path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'package.json'));
|
|
20
|
+
const CAVEMAN_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra', 'megacave-lite', 'megacave-full', 'megacave-ultra']);
|
|
20
21
|
const PONYTAIL_DEFAULTS = new Set(['off', 'lite', 'full', 'ultra']);
|
|
21
22
|
const RTK_DEFAULTS = new Set(['on', 'off']);
|
|
22
23
|
const DIAG_SCHEDULES = new Set(['manual', 'daily', 'weekly', 'monthly']);
|
|
@@ -16,8 +16,8 @@ import { BACKUP_SCHEDULES, formatCliStatus, storedProfile, writePluginSettings }
|
|
|
16
16
|
import { normalizeComboLevel } from '../extensions/shared/session-state.js';
|
|
17
17
|
import { CAVEMAN_DEFAULTS, PONYTAIL_DEFAULTS } from './common.js';
|
|
18
18
|
import { PACKAGE_NAME } from './common.js';
|
|
19
|
-
import { resolveRtkBinary } from '../extensions/lib/utils.js';
|
|
20
|
-
const DASHBOARD_DIST = path.join(path.dirname(fileURLToPath(import.meta.url)), '
|
|
19
|
+
import { findPackageRoot, resolveRtkBinary } from '../extensions/lib/utils.js';
|
|
20
|
+
const DASHBOARD_DIST = path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'dashboard', 'dist');
|
|
21
21
|
const DASHBOARD_INDEX = path.join(DASHBOARD_DIST, 'index.html');
|
|
22
22
|
const DASHBOARD_BRAND = path.join(DASHBOARD_DIST, 'brand.webp');
|
|
23
23
|
function requireDashboardBundle() {
|
|
@@ -634,17 +634,34 @@ async function runDashboard(options) {
|
|
|
634
634
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
635
635
|
res.end(await readSegment(DASHBOARD_INDEX));
|
|
636
636
|
});
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
637
|
+
await new Promise((resolve, reject) => {
|
|
638
|
+
let stopping = false;
|
|
639
|
+
const rejectOnce = (err) => {
|
|
640
|
+
if (!stopping)
|
|
641
|
+
reject(err);
|
|
642
|
+
};
|
|
643
|
+
const stop = () => {
|
|
644
|
+
if (stopping)
|
|
645
|
+
return;
|
|
646
|
+
stopping = true;
|
|
647
|
+
server.close(() => resolve());
|
|
648
|
+
server.closeIdleConnections();
|
|
649
|
+
};
|
|
650
|
+
process.once('SIGINT', stop);
|
|
651
|
+
process.once('SIGTERM', stop);
|
|
652
|
+
server.once('error', rejectOnce);
|
|
653
|
+
server.listen(options.port, '127.0.0.1', () => {
|
|
654
|
+
try {
|
|
655
|
+
process.title = 'tersio dashboard';
|
|
656
|
+
}
|
|
657
|
+
catch { /* non-POSIX shells keep node */ }
|
|
658
|
+
const address = server.address();
|
|
659
|
+
const port = typeof address === 'object' && address ? address.port : options.port;
|
|
660
|
+
const url = `http://127.0.0.1:${port}`;
|
|
661
|
+
console.log(`[ok] Dashboard live → ${url} (Ctrl-C to stop)`);
|
|
662
|
+
if (options.open)
|
|
663
|
+
openBrowser(url);
|
|
664
|
+
});
|
|
648
665
|
});
|
|
649
666
|
}
|
|
650
667
|
export { runDashboard, saveDashboardCurrency, readDiagSchedule, setDiagSchedule };
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { existsSync, promises as fs } from 'node:fs';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
4
|
import { BUN_BIN_DIR, OMP_AGENT_DIR, OMP_PLUGINS_DIR, PACKAGE_NAME, PACKAGE_VERSION, RTK_BINARY_NAME, removeExtensionFromConfig, dryRun, verbose, debug, execP, readPluginsPackage, writeIfChanged, } from './common.js';
|
|
6
5
|
import { execNetwork, sayTagged } from './interactive.js';
|
|
7
6
|
import { wireRtkOmp, wireRtkOpencode, wireRtkPi, ensureRtkInConfig } from './rtk-wiring.js';
|
|
8
7
|
import { RTK_RELEASE_API, fetchJson, findFile, httpsGet, httpsDownload, parseChecksum, readTextIfExists, rtkPlatformSpec, sha256File, } from '../extensions/lib/utils.js';
|
|
9
8
|
import { runLatestUpdate } from './update.js';
|
|
10
|
-
import { OPENCODE_SERVER_SHIM, TREE_FILES, sourcePath } from './manifest.js';
|
|
9
|
+
import { EXT_DIR, OPENCODE_SERVER_SHIM, TREE_FILES, sourcePath } from './manifest.js';
|
|
11
10
|
import { detectHosts, hostExtensionsDir } from './hosts.js';
|
|
12
|
-
const EXT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'extensions');
|
|
13
11
|
// A written tree is the install for both hosts now, so --fix restores it for whichever hosts have it, alongside the OMP package when that is present.
|
|
14
12
|
async function fixExtensionTrees() {
|
|
15
13
|
// The opencode entrypoint is foreign to pi/omp loaders: never copy it there, and remove strays.
|
|
@@ -148,7 +146,7 @@ async function fixRtk(binDir) {
|
|
|
148
146
|
console.log(' Doctor --fix: repairing RTK binary + wiring');
|
|
149
147
|
const binDest = path.join(binDir, RTK_BINARY_NAME);
|
|
150
148
|
if (dryRun) {
|
|
151
|
-
sayTagged(` [dry-run] would download a checksum-verified rtk to ${binDest} and wire it into
|
|
149
|
+
sayTagged(` [dry-run] would download a checksum-verified rtk to ${binDest} and wire it into the installed hosts`);
|
|
152
150
|
return;
|
|
153
151
|
}
|
|
154
152
|
const spec = rtkPlatformSpec();
|
|
@@ -6,7 +6,7 @@ import { detectHosts, hostExtensionsDir, ompPackageDir } from './hosts.js';
|
|
|
6
6
|
import { askInteractiveChoice, askInteractiveConfirm, runInteractivePhase } from './interactive.js';
|
|
7
7
|
import { usageDbPath } from '../extensions/shared/usage-store.js';
|
|
8
8
|
import { pricesCachePath } from '../extensions/shared/pricing.js';
|
|
9
|
-
import { readTextIfExists, resolveRtkBinary } from '../extensions/lib/utils.js';
|
|
9
|
+
import { readTextIfExists, resolveRtkBinary, ruleBodyProblem } from '../extensions/lib/utils.js';
|
|
10
10
|
import { TREE_FILES } from './manifest.js';
|
|
11
11
|
async function runDoctor(recheck = false) {
|
|
12
12
|
console.log('\n=== Tersio Doctor ===');
|
|
@@ -30,7 +30,7 @@ async function runDoctor(recheck = false) {
|
|
|
30
30
|
cavemanIndexText: readTextIfExists(cavemanIndex),
|
|
31
31
|
cavemanRuleText: readTextIfExists(cavemanRule('rule.md')),
|
|
32
32
|
cavemanUltraText: readTextIfExists(cavemanRule('rule-ultra.md')),
|
|
33
|
-
|
|
33
|
+
cavemanMegacaveText: readTextIfExists(cavemanRule('rule-megacave.md')),
|
|
34
34
|
rtkIndexText: readTextIfExists(rtkIndex),
|
|
35
35
|
updaterIndexText: readTextIfExists(updaterIndex),
|
|
36
36
|
rtkMtime: rtkBin ? fs.stat(rtkBin).catch(() => null) : Promise.resolve(null),
|
|
@@ -42,7 +42,7 @@ async function runDoctor(recheck = false) {
|
|
|
42
42
|
const err = e;
|
|
43
43
|
return err.stdout?.trim() || err.stderr?.trim() || null;
|
|
44
44
|
}) : Promise.resolve(null);
|
|
45
|
-
const [[ompPkgText, configText], [cavemanIndexText, rtkIndexText, updaterIndexText, ponytailPkgText], [cavemanRuleText, cavemanUltraText,
|
|
45
|
+
const [[ompPkgText, configText], [cavemanIndexText, rtkIndexText, updaterIndexText, ponytailPkgText], [cavemanRuleText, cavemanUltraText, cavemanMegacaveText, ruleMtime, rtkBinText, rtkMtime, rtkVersion, ponytailMtime, pricesMtime]] = await runInteractivePhase('Checking installation', () => Promise.all([
|
|
46
46
|
Promise.all([
|
|
47
47
|
probes.ompPkgText,
|
|
48
48
|
probes.configText,
|
|
@@ -56,7 +56,7 @@ async function runDoctor(recheck = false) {
|
|
|
56
56
|
Promise.all([
|
|
57
57
|
probes.cavemanRuleText,
|
|
58
58
|
probes.cavemanUltraText,
|
|
59
|
-
probes.
|
|
59
|
+
probes.cavemanMegacaveText,
|
|
60
60
|
probes.ruleMtime,
|
|
61
61
|
probes.rtkBinText,
|
|
62
62
|
probes.rtkMtime,
|
|
@@ -133,9 +133,16 @@ async function runDoctor(recheck = false) {
|
|
|
133
133
|
check('Prices feed', pricesMtime !== null, pricesAge || pricesCachePath());
|
|
134
134
|
section('Add-ons');
|
|
135
135
|
const ruleAge = ruleMtime ? `(updated ${relTime(Date.now() - ruleMtime.mtimeMs)} · ${absDate(ruleMtime.mtimeMs)})` : '';
|
|
136
|
-
const cavemanRules = [['rule.md', cavemanRuleText], ['rule-ultra.md', cavemanUltraText], ['rule-
|
|
136
|
+
const cavemanRules = [['rule.md', cavemanRuleText], ['rule-ultra.md', cavemanUltraText], ['rule-megacave.md', cavemanMegacaveText]];
|
|
137
137
|
const missingRules = cavemanRules.filter(([, text]) => text === null).map(([name]) => name);
|
|
138
138
|
check('Caveman rule', missingRules.length === 0, missingRules.length ? `missing: ${missingRules.join(', ')}` : ruleAge);
|
|
139
|
+
for (const [name, text] of cavemanRules) {
|
|
140
|
+
if (text === null)
|
|
141
|
+
continue;
|
|
142
|
+
const problem = ruleBodyProblem(text);
|
|
143
|
+
if (problem)
|
|
144
|
+
warnLine(`Caveman ${name}`, `unusable body (${problem}) — run: tersio install`);
|
|
145
|
+
}
|
|
139
146
|
const rtkAge = rtkMtime ? `(updated ${relTime(Date.now() - rtkMtime.mtimeMs)} · ${absDate(rtkMtime.mtimeMs)})` : '';
|
|
140
147
|
check('RTK binary', rtkBin !== null, rtkBinText === null ? 'not found in PATH' : [rtkVersion, rtkAge].filter(Boolean).join(' '));
|
|
141
148
|
// rtk_run needs a host exec API, which doctor cannot assume.
|
|
@@ -245,7 +252,7 @@ async function runDoctorFix(tally) {
|
|
|
245
252
|
console.log('\n Doctor --fix: repairs done — rechecking.');
|
|
246
253
|
const again = await runDoctor(true);
|
|
247
254
|
if (again.missing + again.warn === 0)
|
|
248
|
-
console.log(' Doctor --fix: all checks pass. Restart
|
|
255
|
+
console.log(' Doctor --fix: all checks pass. Restart your agent.');
|
|
249
256
|
else
|
|
250
257
|
console.log(` Doctor --fix: still ${again.missing} missing + ${again.warn} warn — rerun or repair manually.`);
|
|
251
258
|
}
|
|
@@ -13,7 +13,7 @@ import { runReset } from './reset.js';
|
|
|
13
13
|
import { runUsage } from './usage.js';
|
|
14
14
|
import { runDashboard } from './dashboard.js';
|
|
15
15
|
import { wireRtkOmp, wireRtkOpencode, wireRtkPi } from './rtk-wiring.js';
|
|
16
|
-
import { CAVEMAN_REMOTE_ULTRA,
|
|
16
|
+
import { CAVEMAN_REMOTE_ULTRA, CAVEMAN_REMOTE_MEGACAVE, RTK_RELEASE_API, fetchJson, findFile, findHoistedPackage, httpsGet, findPackageRoot, httpsDownload, parseChecksum, piAgentDir, readTextIfExists, resolveRtkBinary, ruleBodyProblem, rtkPlatformSpec, sha256File, } from '../extensions/lib/utils.js';
|
|
17
17
|
import { formatCliStatus, storedProfile, storedProfileSync, writePluginSettings } from './profile.js';
|
|
18
18
|
import { runSettings } from './settings.js';
|
|
19
19
|
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
@@ -356,11 +356,18 @@ async function stepRtkSession(extDir, options) {
|
|
|
356
356
|
console.log(' RTK session — install session mode');
|
|
357
357
|
await copySources(extDir, filesUnder('rtk-session'), 'rtk-session/index.ts', options);
|
|
358
358
|
}
|
|
359
|
+
// pi's built-in grep and glob never reach the bash hook, so this one filters their output.
|
|
360
|
+
async function stepRtkFilter(extDir, options) {
|
|
361
|
+
if (!options.quiet)
|
|
362
|
+
console.log(' RTK filter — install built-in tool output filtering');
|
|
363
|
+
await copySources(extDir, filesUnder('rtk-filter'), 'rtk-filter/index.ts', options);
|
|
364
|
+
}
|
|
359
365
|
// rule.md ships with the package; the other bodies track upstream skills.
|
|
366
|
+
// rule.md is a deliberate fork of upstream skills/caveman/SKILL.md; a blind sync would drop the mode switch line and the STE100 rules.
|
|
360
367
|
const CAVEMAN_RULES = [
|
|
361
368
|
['rule.md', null],
|
|
362
369
|
['rule-ultra.md', CAVEMAN_REMOTE_ULTRA],
|
|
363
|
-
['rule-
|
|
370
|
+
['rule-megacave.md', CAVEMAN_REMOTE_MEGACAVE],
|
|
364
371
|
];
|
|
365
372
|
async function stepCaveman(extDir, options) {
|
|
366
373
|
if (!options.quiet)
|
|
@@ -373,7 +380,14 @@ async function stepCaveman(extDir, options) {
|
|
|
373
380
|
let rule = bundled;
|
|
374
381
|
if (remote && !options.dryRun) {
|
|
375
382
|
try {
|
|
376
|
-
|
|
383
|
+
const fetched = await withInteractiveSpinner(`Fetching Caveman ${name}`, () => httpsGet(remote));
|
|
384
|
+
const problem = ruleBodyProblem(fetched);
|
|
385
|
+
if (problem && bundled !== null) {
|
|
386
|
+
sayTagged(` [warn] ${name} fetch rejected (${problem}) — keeping the bundled body`);
|
|
387
|
+
rule = bundled;
|
|
388
|
+
}
|
|
389
|
+
else
|
|
390
|
+
rule = fetched;
|
|
377
391
|
}
|
|
378
392
|
catch (e) {
|
|
379
393
|
if (bundled === null)
|
|
@@ -392,6 +406,9 @@ async function stepCaveman(extDir, options) {
|
|
|
392
406
|
}
|
|
393
407
|
}
|
|
394
408
|
await copySources(extDir, filesUnder('caveman-session').filter(([, to]) => to.endsWith('index.ts')), 'caveman-session/index.ts', options);
|
|
409
|
+
// Renamed body: drop the stale copy an older install left behind.
|
|
410
|
+
if (!options.dryRun)
|
|
411
|
+
await fs.rm(path.join(cavemanDir, 'rule-wenyan.md'), { force: true });
|
|
395
412
|
}
|
|
396
413
|
async function stepTersioCommands(extDir, options) {
|
|
397
414
|
if (!options.quiet)
|
|
@@ -413,7 +430,7 @@ const PONYTAIL_BUNDLE_FILES = [
|
|
|
413
430
|
'skills/ponytail/SKILL.md',
|
|
414
431
|
];
|
|
415
432
|
async function stepPonytailBundle(treeDir, options) {
|
|
416
|
-
const root = findHoistedPackage('@dietrichgebert/ponytail', path.dirname(fileURLToPath(import.meta.url)));
|
|
433
|
+
const root = findHoistedPackage('@dietrichgebert/ponytail', findPackageRoot(path.dirname(fileURLToPath(import.meta.url))));
|
|
417
434
|
if (!root) {
|
|
418
435
|
if (!options.quiet)
|
|
419
436
|
console.log(' [skip] Ponytail package not found next to the CLI — OpenCode keeps the built-in text');
|
|
@@ -441,6 +458,7 @@ async function stepOpencode(options) {
|
|
|
441
458
|
console.log(` OpenCode — write the plugin tree (${dir})`);
|
|
442
459
|
await stepSharedSessionState(dir, options);
|
|
443
460
|
await copySources(dir, filesUnder('rtk-session'), 'rtk-session/index.ts', options);
|
|
461
|
+
await stepRtkFilter(dir, options);
|
|
444
462
|
await copySources(dir, filesUnder('caveman-session'), 'caveman-session/index.ts', options);
|
|
445
463
|
await stepCombo(dir, options);
|
|
446
464
|
await copySources(dir, filesUnder('tersio-commands'), 'tersio-commands/index.ts', options);
|
|
@@ -474,7 +492,7 @@ async function stepOpencode(options) {
|
|
|
474
492
|
async function resolveProfile(opts = {}) {
|
|
475
493
|
// Seed from the stored defaults so flag-less runs keep them.
|
|
476
494
|
const profile = await storedProfile();
|
|
477
|
-
//
|
|
495
|
+
// The chosen preset owns all three modes.
|
|
478
496
|
let comboChosen = comboDefaultFlag !== undefined;
|
|
479
497
|
if (tty() && !profileFlagsGiven && !applyUpdate) {
|
|
480
498
|
const choice = await askInteractiveChoice('Session-start defaults — Combo preset', [
|
|
@@ -650,6 +668,7 @@ async function stepPiLayer(options) {
|
|
|
650
668
|
await stepSharedSessionState(extDir, options);
|
|
651
669
|
// OMP loads rtk-session from its plugin manifest, so only the pi tree copies it.
|
|
652
670
|
await copySources(extDir, filesUnder('rtk-session'), 'rtk-session/index.ts', options);
|
|
671
|
+
await stepRtkFilter(extDir, options);
|
|
653
672
|
await stepCaveman(extDir, options);
|
|
654
673
|
await stepCombo(extDir, options);
|
|
655
674
|
await stepTersioCommands(extDir, options);
|
|
@@ -661,13 +680,14 @@ const PI_PONYTAIL_FILES = [
|
|
|
661
680
|
'pi-extension/index.js',
|
|
662
681
|
'hooks/ponytail-instructions.js',
|
|
663
682
|
'hooks/ponytail-config.js',
|
|
664
|
-
'
|
|
683
|
+
...['ponytail', 'ponytail-audit', 'ponytail-debt', 'ponytail-gain', 'ponytail-help', 'ponytail-review']
|
|
684
|
+
.map((skill) => `skills/${skill}/SKILL.md`),
|
|
665
685
|
];
|
|
666
686
|
const PI_PONYTAIL_PACKAGE = `${JSON.stringify({ name: 'ponytail', version: '0.0.0', private: true, pi: { extensions: ['./pi-extension/index.js'], skills: ['./skills'] } }, null, 2)}\n`;
|
|
667
687
|
async function stepPonytailForPi(agentDir, options) {
|
|
668
688
|
if (!options.quiet)
|
|
669
689
|
console.log(' Ponytail — write the local pi package');
|
|
670
|
-
const root = findHoistedPackage('@dietrichgebert/ponytail', path.dirname(fileURLToPath(import.meta.url)));
|
|
690
|
+
const root = findHoistedPackage('@dietrichgebert/ponytail', findPackageRoot(path.dirname(fileURLToPath(import.meta.url))));
|
|
671
691
|
const dest = path.join(agentDir, 'ponytail');
|
|
672
692
|
if (!root) {
|
|
673
693
|
console.log(' [skip] Ponytail package not found next to the CLI');
|
|
@@ -688,14 +708,30 @@ async function stepPonytailForPi(agentDir, options) {
|
|
|
688
708
|
}
|
|
689
709
|
if (options.dryRun)
|
|
690
710
|
return;
|
|
711
|
+
// Only when pi still declares the npm spec: with nothing to remove, `pi remove` still
|
|
712
|
+
// shells out to npm, which warns on stderr and exits non-zero.
|
|
713
|
+
const declared = await piDeclaredPackages(agentDir);
|
|
714
|
+
if (declared.some(isNpmPonytailPackage)) {
|
|
715
|
+
try {
|
|
716
|
+
await execNetwork('Removing the pi npm copy of Ponytail', 'pi', ['remove', 'npm:@dietrichgebert/ponytail'], { timeout: 120000 });
|
|
717
|
+
}
|
|
718
|
+
catch (e) {
|
|
719
|
+
sayTagged(` [warn] pi remove ponytail: ${shortError(e)}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
await registerPiLocalPonytail(agentDir, dest, options);
|
|
723
|
+
}
|
|
724
|
+
// pi declares its package sources in settings.json; [] when the file is absent or unreadable.
|
|
725
|
+
async function piDeclaredPackages(agentDir) {
|
|
691
726
|
try {
|
|
692
|
-
|
|
727
|
+
const settings = JSON.parse(await fs.readFile(path.join(agentDir, 'settings.json'), 'utf8'));
|
|
728
|
+
return Array.isArray(settings.packages) ? settings.packages : [];
|
|
693
729
|
}
|
|
694
|
-
catch
|
|
695
|
-
|
|
730
|
+
catch {
|
|
731
|
+
return [];
|
|
696
732
|
}
|
|
697
|
-
await registerPiLocalPonytail(agentDir, dest, options);
|
|
698
733
|
}
|
|
734
|
+
const isNpmPonytailPackage = (value) => typeof value === 'string' && value.startsWith('npm:@dietrichgebert/ponytail');
|
|
699
735
|
// pi lists package sources in settings; swap the npm spec for the local dir.
|
|
700
736
|
async function registerPiLocalPonytail(agentDir, dest, options) {
|
|
701
737
|
const settingsPath = path.join(agentDir, 'settings.json');
|
|
@@ -707,7 +743,7 @@ async function registerPiLocalPonytail(agentDir, dest, options) {
|
|
|
707
743
|
return;
|
|
708
744
|
}
|
|
709
745
|
const packages = (Array.isArray(settings.packages) ? settings.packages : []);
|
|
710
|
-
const kept = packages.filter((p) =>
|
|
746
|
+
const kept = packages.filter((p) => !isNpmPonytailPackage(p));
|
|
711
747
|
if (!kept.includes(dest))
|
|
712
748
|
kept.push(dest);
|
|
713
749
|
settings.packages = kept;
|
|
@@ -715,6 +751,10 @@ async function registerPiLocalPonytail(agentDir, dest, options) {
|
|
|
715
751
|
if (!options.quiet)
|
|
716
752
|
sayTagged(` [write] pi package source → ${dest}`);
|
|
717
753
|
}
|
|
754
|
+
// Name the preset this run actually chose, instead of a hardcoded level.
|
|
755
|
+
function comboHint(host, comboDefault) {
|
|
756
|
+
return comboDefault === 'off' ? `\nDone — restart ${host}.` : `\nDone — restart ${host}, then /combo ${comboDefault}.`;
|
|
757
|
+
}
|
|
718
758
|
async function runInstall() {
|
|
719
759
|
// Without this guard a picked command re-enters here with command === null and loops straight back into the picker.
|
|
720
760
|
if (command === null && tty() && !yes && !updatePromptDone) {
|
|
@@ -818,7 +858,7 @@ async function runInstall() {
|
|
|
818
858
|
if (failures.length > 0)
|
|
819
859
|
console.log(`\nDone with ${failures.length} failure(s) — see [fail] lines above.`);
|
|
820
860
|
else
|
|
821
|
-
console.log('
|
|
861
|
+
console.log(comboHint('OpenCode', profile.comboDefault));
|
|
822
862
|
closeRL();
|
|
823
863
|
return;
|
|
824
864
|
}
|
|
@@ -829,7 +869,7 @@ async function runInstall() {
|
|
|
829
869
|
if (failures.length > 0)
|
|
830
870
|
console.log(`\nDone with ${failures.length} failure(s) — see [fail] lines above.`);
|
|
831
871
|
else
|
|
832
|
-
console.log('
|
|
872
|
+
console.log(comboHint('pi', profile.comboDefault));
|
|
833
873
|
closeRL();
|
|
834
874
|
return;
|
|
835
875
|
}
|
|
@@ -839,6 +879,7 @@ async function runInstall() {
|
|
|
839
879
|
await capture('ponytail', () => stepPonytail(OMP_PLUGINS_DIR, installOptions));
|
|
840
880
|
await capture('rtk', () => stepRtk(BUN_BIN_DIR, installOptions));
|
|
841
881
|
await capture('rtk session', () => stepRtkSession(userExtDir, installOptions));
|
|
882
|
+
await capture('rtk filter', () => stepRtkFilter(userExtDir, installOptions));
|
|
842
883
|
await capture('caveman', () => stepCaveman(path.join(OMP_PLUGINS_DIR, 'node_modules', '@krtclcdy', 'tersio', 'extensions'), installOptions));
|
|
843
884
|
await capture('combo', () => stepCombo(userExtDir, installOptions));
|
|
844
885
|
await capture('commands', () => stepTersioCommands(userExtDir, installOptions));
|
|
@@ -850,7 +891,7 @@ async function runInstall() {
|
|
|
850
891
|
console.log(` add-ons: ${failures.length} failed (${failures.join(', ')}) — see [fail] lines above`);
|
|
851
892
|
}
|
|
852
893
|
else {
|
|
853
|
-
console.log(failures.length === 0 ? '
|
|
894
|
+
console.log(failures.length === 0 ? comboHint('OMP', profile.comboDefault) : `\nDone with ${failures.length} failure(s) — see [fail] lines above.`);
|
|
854
895
|
}
|
|
855
896
|
closeRL();
|
|
856
897
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Single list of install writes, shared by installer, doctor, and tests.
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
|
|
4
|
+
import { findPackageRoot } from '../extensions/lib/utils.js';
|
|
5
|
+
export const EXT_DIR = path.join(findPackageRoot(path.dirname(fileURLToPath(import.meta.url))), 'extensions');
|
|
5
6
|
/** Root entrypoint OpenCode resolves for the installed tree; re-exports the versioned entry beside it. Built by join so the path never reads as a relative import (repo-integrity scans literals). */
|
|
6
7
|
const SERVER_SPEC = `./${['opencode', 'server.ts'].join('/')}`;
|
|
7
8
|
export const OPENCODE_SERVER_SHIM = `// Generated by \`tersio install --host opencode\`: OpenCode resolves <pluginDir>/server.ts.
|
|
@@ -21,8 +22,9 @@ export const TREE_FILES = [
|
|
|
21
22
|
'caveman-session/index.ts',
|
|
22
23
|
'caveman-session/rule.md',
|
|
23
24
|
'caveman-session/rule-ultra.md',
|
|
24
|
-
'caveman-session/rule-
|
|
25
|
+
'caveman-session/rule-megacave.md',
|
|
25
26
|
'rtk-session/index.ts',
|
|
27
|
+
'rtk-filter/index.ts',
|
|
26
28
|
'combo-toggle/index.ts',
|
|
27
29
|
'tersio-commands/index.ts',
|
|
28
30
|
'ai-addons-updater/index.ts',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/settings.ts — view/edit session-start defaults and the display currency.
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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';
|
|
3
|
+
import { tersioSettingsFile } from '../extensions/shared/plugin-settings.js';
|
|
4
4
|
import { CURRENCY_CODES } from './currency.js';
|
|
5
5
|
import { textTable } from './usage.js';
|
|
6
6
|
import { ask, askInteractiveChoice, closeRL, tty } from './interactive.js';
|
|
@@ -45,25 +45,6 @@ async function askProfile(current) {
|
|
|
45
45
|
backupSchedule: current.backupSchedule,
|
|
46
46
|
subagentMarkers: current.subagentMarkers,
|
|
47
47
|
};
|
|
48
|
-
const caveman = await askInteractiveChoice('Caveman default', [...CAVEMAN_DEFAULTS].map((v) => ({
|
|
49
|
-
value: v, label: v,
|
|
50
|
-
})), next.cavemanDefault);
|
|
51
|
-
if (caveman.status !== 'selected')
|
|
52
|
-
return null;
|
|
53
|
-
next.cavemanDefault = caveman.value;
|
|
54
|
-
const rtk = await askInteractiveChoice('RTK default', [
|
|
55
|
-
{ value: 'on', label: 'on' },
|
|
56
|
-
{ value: 'off', label: 'off' },
|
|
57
|
-
], next.rtkDefault ? 'on' : 'off');
|
|
58
|
-
if (rtk.status !== 'selected')
|
|
59
|
-
return null;
|
|
60
|
-
next.rtkDefault = rtk.value === 'on';
|
|
61
|
-
const ponytail = await askInteractiveChoice('Ponytail default', [...PONYTAIL_DEFAULTS].map((v) => ({
|
|
62
|
-
value: v, label: v,
|
|
63
|
-
})), next.ponytailDefault);
|
|
64
|
-
if (ponytail.status !== 'selected')
|
|
65
|
-
return null;
|
|
66
|
-
next.ponytailDefault = ponytail.value;
|
|
67
48
|
const cur = await askInteractiveChoice('Display currency (usage/dashboard reports)', CURRENCY_CODES.map((v) => ({
|
|
68
49
|
value: v, label: v,
|
|
69
50
|
})), next.currency);
|
|
@@ -103,7 +84,7 @@ async function askDiagSchedule(current) {
|
|
|
103
84
|
}
|
|
104
85
|
const SETTING_NAMES = ['combo', 'caveman', 'rtk', 'ponytail', 'currency', 'diagnosis', 'markers'];
|
|
105
86
|
function settingsUsage() {
|
|
106
|
-
console.log(' Usage: tersio settings [combo|caveman|rtk|ponytail|currency|diagnosis|markers] [--combo-default off|medium|balanced|max] [--caveman-default off|lite|full|ultra|
|
|
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]');
|
|
107
88
|
}
|
|
108
89
|
// Single-setting jump: `tersio settings diagnosis` prompts only that value instead of walking the whole chain. Returns false when aborted.
|
|
109
90
|
async function runSingleSetting(name, current, nextDiag) {
|
|
@@ -198,7 +179,8 @@ function printSettingsTable(current) {
|
|
|
198
179
|
for (const l of textTable(['Setting', 'Current', 'Valid values'], rows, [false, false, false], 64)) {
|
|
199
180
|
console.log(l);
|
|
200
181
|
}
|
|
201
|
-
console.log(` Stored: ${
|
|
182
|
+
console.log(` Stored: ${tersioSettingsFile()}`);
|
|
183
|
+
console.log(` Combo ${current.comboDefault} sets caveman/rtk/ponytail. Tune one: tersio settings <name>`);
|
|
202
184
|
}
|
|
203
185
|
async function runSettings() {
|
|
204
186
|
const current = await storedProfile();
|
|
@@ -290,7 +290,7 @@ async function runUninstall(options = {}) {
|
|
|
290
290
|
}
|
|
291
291
|
// Cleared last: writeConfigLines above would just make a fresh one.
|
|
292
292
|
const cavemanDir = path.join(pluginsDir, 'node_modules', PACKAGE_NAME, 'extensions', 'caveman-session');
|
|
293
|
-
for (const backup of [`${configPath}.bak`, path.join(cavemanDir, 'rule.md.bak'), path.join(cavemanDir, 'rule-ultra.md.bak'), path.join(cavemanDir, 'rule-
|
|
293
|
+
for (const backup of [`${configPath}.bak`, path.join(cavemanDir, 'rule.md.bak'), path.join(cavemanDir, 'rule-ultra.md.bak'), path.join(cavemanDir, 'rule-megacave.md.bak')]) {
|
|
294
294
|
await removeUninstallTarget(backup, shouldDryRun, false);
|
|
295
295
|
}
|
|
296
296
|
// Remove the bundled Ponytail copy; its config.yml entry is already filtered.
|
|
@@ -6,7 +6,7 @@ import { IS_WINDOWS, OMP_PLUGINS_DIR, PACKAGE_BIN, PACKAGE_NAME, PACKAGE_VERSION
|
|
|
6
6
|
import { execNetwork, sayTagged } from './interactive.js';
|
|
7
7
|
import { readTextIfExists, resolveRtkBinary, tersioDataPath } from '../extensions/lib/utils.js';
|
|
8
8
|
import { refreshPrices } from '../extensions/shared/pricing.js';
|
|
9
|
-
import { CAVEMAN_REMOTE_ULTRA,
|
|
9
|
+
import { CAVEMAN_REMOTE_ULTRA, CAVEMAN_REMOTE_MEGACAVE, RTK_RELEASE_API, httpsGet, normalizeRtkVersion, sha256Hex, } from '../extensions/lib/utils.js';
|
|
10
10
|
import { parseHostArg } from './hosts.js';
|
|
11
11
|
const UPDATE_CHECK_TTL_MS = 6 * 60 * 60 * 1000;
|
|
12
12
|
function newerThan(a, b) {
|
|
@@ -84,9 +84,9 @@ async function probeUpdatePlan(cliLatest) {
|
|
|
84
84
|
const [rtkLocal, rtkRelease, ruleLocal, ruleRemote, ponytailLocalText] = await Promise.all([
|
|
85
85
|
settle(rtkBin ? execP(rtkBin, ['--version'], { timeout: 5000 }).then((r) => normalizeRtkVersion(r.stdout.trim() || r.stderr.trim()) || null) : Promise.resolve(null), 6000),
|
|
86
86
|
settle(httpsGet(RTK_RELEASE_API, { signal: controller.signal }).then((text) => normalizeRtkVersion(JSON.parse(text).tag_name) || null), 1500),
|
|
87
|
-
Promise.all([readTextIfExists(tersioRule('rule-ultra.md')), readTextIfExists(tersioRule('rule-
|
|
87
|
+
Promise.all([readTextIfExists(tersioRule('rule-ultra.md')), readTextIfExists(tersioRule('rule-megacave.md'))])
|
|
88
88
|
.then((texts) => (texts.every(Boolean) ? texts.map((t) => sha256Hex(t).slice(0, 8)).join('+') : null)),
|
|
89
|
-
settle(Promise.all([httpsGet(CAVEMAN_REMOTE_ULTRA, { signal: controller.signal }), httpsGet(
|
|
89
|
+
settle(Promise.all([httpsGet(CAVEMAN_REMOTE_ULTRA, { signal: controller.signal }), httpsGet(CAVEMAN_REMOTE_MEGACAVE, { signal: controller.signal })])
|
|
90
90
|
.then((texts) => texts.map((t) => sha256Hex(t).slice(0, 8)).join('+')), 1500),
|
|
91
91
|
readTextIfExists(ponytailPkg),
|
|
92
92
|
]);
|
|
@@ -214,8 +214,7 @@ async function runLatestUpdate() {
|
|
|
214
214
|
// Inherited stdio, no outer spinner; the delegated installer runs quiet and this parent owns both the plan line and the closing summary.
|
|
215
215
|
try {
|
|
216
216
|
await execInherit(npmCommand, npmCommandArgs);
|
|
217
|
-
|
|
218
|
-
console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION}. Restart ${hostName}.`);
|
|
217
|
+
console.log(`Done — tersio ${plan.cli ?? PACKAGE_VERSION} and add-ons updated.`);
|
|
219
218
|
if (!dryRun) {
|
|
220
219
|
try {
|
|
221
220
|
await refreshPrices();
|
|
@@ -6,7 +6,7 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { setExtensionLabel } from '../shared/host.js';
|
|
9
|
-
import { CAVEMAN_REMOTE_ULTRA,
|
|
9
|
+
import { CAVEMAN_REMOTE_ULTRA, CAVEMAN_REMOTE_MEGACAVE, RTK_RELEASE_API, fetchJson, findFile, findHoistedPackage, httpsGet, httpsDownload, sha256Hex, parseChecksum, normalizeRtkVersion, readTextIfExists, rtkPlatformSpec, resolveRtkBinary, } from '../lib/utils.js';
|
|
10
10
|
const IS_WINDOWS = process.platform === 'win32';
|
|
11
11
|
const EXTENSION_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
const PONYTAIL_REMOTE = 'https://raw.githubusercontent.com/DietrichGebert/ponytail/main/package.json';
|
|
@@ -16,10 +16,10 @@ const cavemanRule = (name) => path.join(EXTENSION_DIR, '..', 'caveman-session',
|
|
|
16
16
|
const CAVEMAN_LOCAL = cavemanRule('rule.md');
|
|
17
17
|
const CAVEMAN_TRACKED = [
|
|
18
18
|
{ file: cavemanRule('rule-ultra.md'), remote: CAVEMAN_REMOTE_ULTRA },
|
|
19
|
-
{ file: cavemanRule('rule-
|
|
19
|
+
{ file: cavemanRule('rule-megacave.md'), remote: CAVEMAN_REMOTE_MEGACAVE },
|
|
20
20
|
];
|
|
21
|
-
|
|
22
|
-
const RELOAD_MSG =
|
|
21
|
+
// Host-neutral: this updater runs inside pi, OMP, and OpenCode alike.
|
|
22
|
+
const RELOAD_MSG = 'Reminder: restart your agent (or reload extensions) for updates to take effect.';
|
|
23
23
|
// Ponytail is a hoisted dependency, so the installed copy is found by walking up rather than by naming a host directory.
|
|
24
24
|
function ponytailLocal() {
|
|
25
25
|
return findHoistedPackage('@dietrichgebert/ponytail', EXTENSION_DIR, 'package.json');
|
|
@@ -7,9 +7,14 @@ import { announceStatus } 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.
|
|
10
|
-
const RULE_FILES = { lite: 'rule.md', full: 'rule.md', ultra: 'rule-ultra.md', '
|
|
10
|
+
const RULE_FILES = { lite: 'rule.md', full: 'rule.md', ultra: 'rule-ultra.md', 'megacave-lite': 'rule-megacave.md', 'megacave-full': 'rule-megacave.md', 'megacave-ultra': 'rule-megacave.md' };
|
|
11
11
|
const DEFAULT_MODE = 'off';
|
|
12
|
+
// A rule file is immutable for the process, so read each level once; reinstalling needs a restart.
|
|
13
|
+
const instructionCache = new Map();
|
|
12
14
|
function buildInstruction(mode) {
|
|
15
|
+
const cached = instructionCache.get(mode);
|
|
16
|
+
if (cached)
|
|
17
|
+
return cached;
|
|
13
18
|
const name = RULE_FILES[mode] ?? RULE_FILES.full;
|
|
14
19
|
// Fail loud: a wrong rule reads to the model as the real one.
|
|
15
20
|
let body;
|
|
@@ -17,9 +22,11 @@ function buildInstruction(mode) {
|
|
|
17
22
|
body = readFileSync(join(CAVERN_DIR, name), 'utf8');
|
|
18
23
|
}
|
|
19
24
|
catch {
|
|
20
|
-
|
|
25
|
+
return `Caveman ${mode} requested, but ${name} is missing. Run: tersio doctor --fix extensions`;
|
|
21
26
|
}
|
|
22
|
-
|
|
27
|
+
const instruction = `Caveman ${mode} active for this session.\n${body}`;
|
|
28
|
+
instructionCache.set(mode, instruction);
|
|
29
|
+
return instruction;
|
|
23
30
|
}
|
|
24
31
|
function resolveMode(entries, fallback = DEFAULT_MODE) {
|
|
25
32
|
return lastCustomValue(entries, 'caveman-mode', (data) => normalizeMode('caveman', data?.mode)) ?? fallback;
|
|
@@ -62,7 +69,7 @@ export default function cavemanSessionExtension(pi) {
|
|
|
62
69
|
return;
|
|
63
70
|
}
|
|
64
71
|
if (!setMode(arg, ctx)) {
|
|
65
|
-
ctx?.ui?.notify?.('Usage: /caveman [lite|full|ultra|
|
|
72
|
+
ctx?.ui?.notify?.('Usage: /caveman [lite|full|ultra|megacave-lite|megacave-full|megacave-ultra|off|status]', 'warning');
|
|
66
73
|
}
|
|
67
74
|
},
|
|
68
75
|
});
|
|
@@ -110,6 +117,6 @@ export default function cavemanSessionExtension(pi) {
|
|
|
110
117
|
// The previous level's block is replaced, not stacked on.
|
|
111
118
|
const stale = lastInjected;
|
|
112
119
|
lastInjected = instruction;
|
|
113
|
-
return injectPromptText(pi, event, instruction, stale);
|
|
120
|
+
return injectPromptText(pi, event, { text: instruction, staleMarker: stale, sectionTag: 'caveman' });
|
|
114
121
|
});
|
|
115
122
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
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 or say `Caveman mode: unknown`.
|
|
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.
|
|
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.
|
|
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. 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.
|