liteagents 2.24.1 → 3.0.0
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/CHANGELOG.md +85 -0
- package/README.md +27 -11
- package/installer/cli.js +36 -3
- package/installer/installation-engine.js +8 -0
- package/package.json +2 -2
- package/packages/ampcode/AGENT.md +1 -2
- package/packages/ampcode/agents/orchestrator.md +2 -2
- package/packages/ampcode/commands/refactor.md +8 -2
- package/packages/ampcode/commands/remember/stub-check.cjs +197 -0
- package/packages/ampcode/commands/remember/sync-rules.cjs +169 -0
- package/packages/ampcode/commands/remember/version-check.cjs +214 -0
- package/packages/ampcode/commands/remember.md +78 -10
- package/packages/claude/CLAUDE.md +1 -2
- package/packages/claude/agents/orchestrator.md +2 -2
- package/packages/claude/commands/refactor.md +8 -2
- package/packages/claude/commands/remember/stub-check.cjs +197 -0
- package/packages/claude/commands/remember/sync-rules.cjs +169 -0
- package/packages/claude/commands/remember/version-check.cjs +214 -0
- package/packages/claude/commands/remember.md +78 -10
- package/packages/droid/AGENTS.md +1 -2
- package/packages/droid/commands/refactor.md +8 -2
- package/packages/droid/commands/remember/stub-check.cjs +197 -0
- package/packages/droid/commands/remember/sync-rules.cjs +169 -0
- package/packages/droid/commands/remember/version-check.cjs +214 -0
- package/packages/droid/commands/remember.md +78 -10
- package/packages/droid/droids/orchestrator.md +2 -2
- package/packages/opencode/AGENTS.md +1 -2
- package/packages/opencode/agent/orchestrator.md +2 -2
- package/packages/opencode/command/refactor.md +8 -2
- package/packages/opencode/command/remember/stub-check.cjs +197 -0
- package/packages/opencode/command/remember/sync-rules.cjs +169 -0
- package/packages/opencode/command/remember/version-check.cjs +214 -0
- package/packages/opencode/command/remember.md +78 -10
- package/packages/opencode/opencode.jsonc +0 -10
- package/packages/subagentic-manual.md +15 -15
- package/packages/ampcode/agents/context-builder.md +0 -144
- package/packages/claude/agents/context-builder.md +0 -145
- package/packages/droid/droids/context-builder.md +0 -144
- package/packages/opencode/agent/context-builder.md +0 -148
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* sync-rules.cjs — keeps a repo's AGENT_RULES.md current with the installed
|
|
6
|
+
* template, without ever destroying what was there.
|
|
7
|
+
*
|
|
8
|
+
* Run every /remember, from the target repo. Before this existed, the rules
|
|
9
|
+
* were bootstrapped once and never refreshed, so a measured 35 local repos
|
|
10
|
+
* drifted to a body many releases old and three hand sweeps failed to hold.
|
|
11
|
+
*
|
|
12
|
+
* Three outcomes, decided by a byte compare — not a stored hash, because the
|
|
13
|
+
* only question is "am I about to change this file?", which any careful copy
|
|
14
|
+
* asks anyway:
|
|
15
|
+
*
|
|
16
|
+
* absent copy it in and say so
|
|
17
|
+
* identical do nothing at all: no write, no backup, no output
|
|
18
|
+
* differs move the old body aside, copy the new one in, say so loudly
|
|
19
|
+
*
|
|
20
|
+
* THE BACKUP IS A SINGLE FILE and is overwritten on each differing run. A
|
|
21
|
+
* customised file therefore survives exactly one update: fold your changes
|
|
22
|
+
* into the new AGENT_RULES.md before the next release, or the next sync
|
|
23
|
+
* replaces the backup with a vanilla body. This is a deliberate trade — see
|
|
24
|
+
* docs/product/agent-rules-freshness-prd.md §5 — chosen over accumulating
|
|
25
|
+
* timestamped backups.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
// Per-kit PROJECT dir. NOTE this is not always the global config dir: amp
|
|
32
|
+
// installs to ~/.config/amp but writes .amp/ in a repo, and opencode likewise.
|
|
33
|
+
// This is the ONE line that differs across packages.
|
|
34
|
+
const PROJECT_DIR = '.opencode';
|
|
35
|
+
|
|
36
|
+
const RULES = 'AGENT_RULES.md';
|
|
37
|
+
const BACKUP = 'AGENT_RULES.md.bak'; // keeps the origin name; not .md, so doc tooling ignores it
|
|
38
|
+
|
|
39
|
+
/** lstat, not existsSync: existsSync follows links, so a DANGLING link reads
|
|
40
|
+
* as absent and gets walked straight past. */
|
|
41
|
+
function lexists(p) {
|
|
42
|
+
try { fs.lstatSync(p); return true; } catch (e) { return false; }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* True when writing to `target` would land outside `repo`.
|
|
47
|
+
*
|
|
48
|
+
* There are two ways out and a guard on only one of them is false safety:
|
|
49
|
+
* `target` may itself be a symlink — including a dangling one, which reads as
|
|
50
|
+
* "the file is absent" and is still followed on write — or any parent
|
|
51
|
+
* directory may be a link pointing elsewhere. This runs across a whole fleet
|
|
52
|
+
* of repos, so a relative link only has to reach a sibling checkout.
|
|
53
|
+
*/
|
|
54
|
+
function escapesRepo(repo, target) {
|
|
55
|
+
let root;
|
|
56
|
+
try { root = fs.realpathSync(repo); } catch (e) { return true; }
|
|
57
|
+
|
|
58
|
+
// Walk up to the deepest ancestor that exists; anything below it cannot be
|
|
59
|
+
// a link yet, so only the existing part needs resolving.
|
|
60
|
+
const tail = [];
|
|
61
|
+
let dir = path.dirname(target);
|
|
62
|
+
while (!lexists(dir)) {
|
|
63
|
+
tail.unshift(path.basename(dir));
|
|
64
|
+
const up = path.dirname(dir);
|
|
65
|
+
if (up === dir) return true; // walked off the filesystem root
|
|
66
|
+
dir = up;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let resolved;
|
|
70
|
+
try { resolved = path.join(fs.realpathSync(dir), ...tail, path.basename(target)); }
|
|
71
|
+
catch (e) { return true; } // an ancestor is a dangling link
|
|
72
|
+
|
|
73
|
+
// The last component can be a link even when every directory above it is
|
|
74
|
+
// clean — that is the dangling-file case.
|
|
75
|
+
try { if (fs.lstatSync(resolved).isSymbolicLink()) return true; } catch (e) { /* absent: fine */ }
|
|
76
|
+
|
|
77
|
+
return resolved !== root && !resolved.startsWith(root + path.sep);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function templatePath() {
|
|
81
|
+
// Ships beside this script, so no path guessing and no dependence on where
|
|
82
|
+
// the kit was installed.
|
|
83
|
+
return path.join(__dirname, RULES);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function targetPath(repo) {
|
|
87
|
+
return path.join(repo, PROJECT_DIR, 'remember', RULES);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @returns {{action:string, detail?:string}} action is one of:
|
|
92
|
+
* 'no-template' | 'bootstrapped' | 'unchanged' | 'updated' | 'failed'
|
|
93
|
+
*/
|
|
94
|
+
function sync(repo) {
|
|
95
|
+
const tpl = templatePath();
|
|
96
|
+
let template;
|
|
97
|
+
try {
|
|
98
|
+
template = fs.readFileSync(tpl);
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return { action: 'no-template', detail: tpl };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const target = targetPath(repo);
|
|
104
|
+
if (escapesRepo(repo, target)) return { action: 'escapes', detail: target };
|
|
105
|
+
|
|
106
|
+
let current = null;
|
|
107
|
+
try { current = fs.readFileSync(target); } catch (e) { /* absent */ }
|
|
108
|
+
|
|
109
|
+
// Byte compare. No normalisation on either side: the copy below is a plain
|
|
110
|
+
// byte write, so a mismatch here means the content really differs.
|
|
111
|
+
if (current && current.equals(template)) return { action: 'unchanged' };
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
115
|
+
if (current) {
|
|
116
|
+
// Single backup, overwritten. rename() is atomic on the same filesystem
|
|
117
|
+
// and cannot leave a half-written backup the way copy+truncate could.
|
|
118
|
+
fs.renameSync(target, path.join(path.dirname(target), BACKUP));
|
|
119
|
+
}
|
|
120
|
+
fs.writeFileSync(target, template);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return { action: 'failed', detail: e.message };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return current ? { action: 'updated' } : { action: 'bootstrapped' };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function main() {
|
|
129
|
+
const repo = process.argv[2] || process.cwd();
|
|
130
|
+
const r = sync(repo);
|
|
131
|
+
const rel = path.join(PROJECT_DIR, 'remember', RULES);
|
|
132
|
+
|
|
133
|
+
switch (r.action) {
|
|
134
|
+
case 'unchanged':
|
|
135
|
+
break; // silent: nothing happened
|
|
136
|
+
case 'bootstrapped':
|
|
137
|
+
process.stdout.write(`${rel} created from the installed template\n`);
|
|
138
|
+
break;
|
|
139
|
+
case 'updated':
|
|
140
|
+
process.stdout.write(
|
|
141
|
+
`${rel} updated from the installed template `
|
|
142
|
+
+ `(previous body kept as ${BACKUP} — fold your changes in before the `
|
|
143
|
+
+ `next release, it is a single file and the next update replaces it)\n`);
|
|
144
|
+
break;
|
|
145
|
+
case 'escapes':
|
|
146
|
+
// Loud, never repaired: the path is under the repo but does not stay
|
|
147
|
+
// there, so any write lands somewhere the run was not invited.
|
|
148
|
+
process.stdout.write(
|
|
149
|
+
`${rel} not synced: it leaves the repo via a symlink — refusing to `
|
|
150
|
+
+ `write through it\n`);
|
|
151
|
+
break;
|
|
152
|
+
case 'no-template':
|
|
153
|
+
// Loud: this means the install is incomplete, not that nothing changed.
|
|
154
|
+
process.stdout.write(
|
|
155
|
+
`AGENT_RULES.md not synced: no template beside this script (${r.detail})\n`);
|
|
156
|
+
break;
|
|
157
|
+
case 'failed':
|
|
158
|
+
process.stdout.write(`AGENT_RULES.md not synced: ${r.detail}\n`);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (require.main === module) {
|
|
164
|
+
// A passenger on /remember, like version-check.cjs: it never gets to fail
|
|
165
|
+
// the run it rides in.
|
|
166
|
+
try { main(); } catch (e) { /* silent */ }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { sync, PROJECT_DIR, RULES, BACKUP };
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* version-check.cjs — tells the user their liteagents install is behind the
|
|
6
|
+
* registry, and nothing else.
|
|
7
|
+
*
|
|
8
|
+
* Run as step 0 of /remember, alongside friction.cjs. It rides along with a
|
|
9
|
+
* memory consolidation run, so the only hard requirement is that it can never
|
|
10
|
+
* cost that run anything: it exits 0 on every path, prints at most one line,
|
|
11
|
+
* writes nothing but its own cache, and is bounded in wall-clock time.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately NOT part of friction.cjs: that file mines friction signals, and
|
|
14
|
+
* a registry lookup is an unrelated concern.
|
|
15
|
+
*
|
|
16
|
+
* A POC (2026-09-03) found the one non-obvious defect guarded here:
|
|
17
|
+
* req.setTimeout is a socket-INACTIVITY timeout and does not bound connect
|
|
18
|
+
* time. Against an unroutable host a 2000ms budget overran to 5146ms. Only an
|
|
19
|
+
* explicit deadline bounds the total, so both are set.
|
|
20
|
+
*
|
|
21
|
+
* Environment:
|
|
22
|
+
* npm_config_registry registry base (npm sets this; mirrors work)
|
|
23
|
+
* LITEAGENTS_INSTALLED_VERSION skip local version discovery
|
|
24
|
+
* LITEAGENTS_SKIP_NPM_LOOKUP skip the `npm ls -g` fallback (it is slow)
|
|
25
|
+
*
|
|
26
|
+
* Installed version is resolved in cost order: the installer's manifest stamp
|
|
27
|
+
* (a file read), then our own package.json when run from a checkout, then
|
|
28
|
+
* `npm ls -g` as a last resort. The last one costs ~500ms on EVERY run, which
|
|
29
|
+
* is why the installer stamps the manifest at all.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const os = require('os');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
|
|
36
|
+
const PKG = 'liteagents';
|
|
37
|
+
// Per-kit config dir. This is the ONE line that differs across packages.
|
|
38
|
+
const CONFIG_DIR = '.config/opencode';
|
|
39
|
+
const TTL_MS = 24 * 60 * 60 * 1000;
|
|
40
|
+
const DEADLINE_MS = 2000;
|
|
41
|
+
const NPM_LOOKUP_MS = 3000;
|
|
42
|
+
|
|
43
|
+
// --- version comparison --------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Is `b` a newer release than `a`? Numeric per component, so 2.9.0 < 2.10.0 —
|
|
47
|
+
* a string compare gets that backwards. A prerelease suffix is stripped, so
|
|
48
|
+
* 2.24.2-beta.1 counts as newer than 2.24.1: it is still a later release, and
|
|
49
|
+
* a user on it does not need advice about 2.24.1.
|
|
50
|
+
*/
|
|
51
|
+
function isNewer(a, b) {
|
|
52
|
+
const parse = (v) => String(v).trim().replace(/^v/, '').split('-')[0]
|
|
53
|
+
.split('.').map((n) => parseInt(n, 10) || 0);
|
|
54
|
+
const [x, y] = [parse(a), parse(b)];
|
|
55
|
+
for (let i = 0; i < 3; i++) {
|
|
56
|
+
const d = (y[i] || 0) - (x[i] || 0);
|
|
57
|
+
if (d !== 0) return d > 0;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// --- installed version ---------------------------------------------------
|
|
63
|
+
|
|
64
|
+
// Walk up from this file looking for our own package.json. Finds it when
|
|
65
|
+
// running from a checkout; finds nothing when installed into ~/.config/opencode, which
|
|
66
|
+
// is why the npm fallback exists.
|
|
67
|
+
function versionFromPackageJson() {
|
|
68
|
+
let dir = __dirname;
|
|
69
|
+
for (let i = 0; i < 8; i++) {
|
|
70
|
+
try {
|
|
71
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
|
|
72
|
+
if (pkg && pkg.name === PKG && pkg.version) return String(pkg.version);
|
|
73
|
+
} catch (e) { /* keep walking */ }
|
|
74
|
+
const up = path.dirname(dir);
|
|
75
|
+
if (up === dir) break;
|
|
76
|
+
dir = up;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// The installer stamps the release it wrote into <install root>/manifest.json.
|
|
82
|
+
// version-check.cjs sits at <root>/<commands|command>/remember/, so the root is
|
|
83
|
+
// two levels up. This is the fast path: reading a file beats spawning npm.
|
|
84
|
+
function versionFromManifest() {
|
|
85
|
+
try {
|
|
86
|
+
const m = JSON.parse(fs.readFileSync(
|
|
87
|
+
path.join(__dirname, '..', '..', 'manifest.json'), 'utf8'));
|
|
88
|
+
// NOT m.version -- that is the manifest schema version, a different thing.
|
|
89
|
+
return m && m.liteagents_version ? String(m.liteagents_version) : null;
|
|
90
|
+
} catch (e) { return null; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function versionFromNpm() {
|
|
94
|
+
if (process.env.LITEAGENTS_SKIP_NPM_LOOKUP) return null;
|
|
95
|
+
try {
|
|
96
|
+
const r = require('child_process').spawnSync(
|
|
97
|
+
'npm', ['ls', '-g', PKG, '--depth=0', '--json'],
|
|
98
|
+
{ encoding: 'utf8', timeout: NPM_LOOKUP_MS }
|
|
99
|
+
);
|
|
100
|
+
if (!r.stdout) return null;
|
|
101
|
+
const deps = JSON.parse(r.stdout).dependencies || {};
|
|
102
|
+
return deps[PKG] && deps[PKG].version ? String(deps[PKG].version) : null;
|
|
103
|
+
} catch (e) { return null; }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function installedVersion() {
|
|
107
|
+
const env = (process.env.LITEAGENTS_INSTALLED_VERSION || '').trim();
|
|
108
|
+
if (env) return env;
|
|
109
|
+
return versionFromManifest() || versionFromPackageJson() || versionFromNpm();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// --- cache ---------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
// Home-scoped, not per-repo: it describes the global install, so a per-repo
|
|
115
|
+
// cache would make every repo fetch the same answer.
|
|
116
|
+
function cachePath() {
|
|
117
|
+
return path.join(os.homedir(), CONFIG_DIR, `.${PKG}-version.json`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function readCache() {
|
|
121
|
+
try {
|
|
122
|
+
const c = JSON.parse(fs.readFileSync(cachePath(), 'utf8'));
|
|
123
|
+
if (typeof c.checked_at !== 'number' || typeof c.latest !== 'string') return null;
|
|
124
|
+
if (Date.now() - c.checked_at > TTL_MS) return null;
|
|
125
|
+
return c.latest;
|
|
126
|
+
} catch (e) { return null; }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Best effort. An unwritable cache dir means we re-fetch next run, never that
|
|
130
|
+
// we withhold the advice we already have.
|
|
131
|
+
function writeCache(latest) {
|
|
132
|
+
try {
|
|
133
|
+
fs.mkdirSync(path.dirname(cachePath()), { recursive: true });
|
|
134
|
+
fs.writeFileSync(cachePath(), JSON.stringify({ checked_at: Date.now(), latest }));
|
|
135
|
+
} catch (e) { /* not worth a word */ }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// --- registry ------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
function fetchLatest(cb) {
|
|
141
|
+
let base = process.env.npm_config_registry || 'https://registry.npmjs.org/';
|
|
142
|
+
if (!/\/$/.test(base)) base += '/';
|
|
143
|
+
const url = `${base}${PKG}/latest`;
|
|
144
|
+
|
|
145
|
+
let mod;
|
|
146
|
+
try { mod = url.startsWith('http://') ? require('http') : require('https'); }
|
|
147
|
+
catch (e) { return cb(null); }
|
|
148
|
+
|
|
149
|
+
let settled = false;
|
|
150
|
+
let req = null;
|
|
151
|
+
const finish = (v) => {
|
|
152
|
+
if (settled) return;
|
|
153
|
+
settled = true;
|
|
154
|
+
clearTimeout(deadline);
|
|
155
|
+
if (v === null && req) { try { req.destroy(); } catch (e) { /* */ } }
|
|
156
|
+
cb(v);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// The hard bound. req.setTimeout below does not cover connect time.
|
|
160
|
+
const deadline = setTimeout(() => finish(null), DEADLINE_MS);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
req = mod.get(url, { headers: { accept: 'application/json' } }, (res) => {
|
|
164
|
+
if (res.statusCode !== 200) { res.resume(); return finish(null); }
|
|
165
|
+
let body = '';
|
|
166
|
+
res.setEncoding('utf8');
|
|
167
|
+
res.on('data', (c) => {
|
|
168
|
+
body += c;
|
|
169
|
+
if (body.length > 1e6) finish(null); // a packument this big is not ours
|
|
170
|
+
});
|
|
171
|
+
res.on('end', () => {
|
|
172
|
+
try {
|
|
173
|
+
const v = JSON.parse(body).version;
|
|
174
|
+
finish(typeof v === 'string' && v ? v : null);
|
|
175
|
+
} catch (e) { finish(null); }
|
|
176
|
+
});
|
|
177
|
+
res.on('error', () => finish(null));
|
|
178
|
+
});
|
|
179
|
+
req.setTimeout(DEADLINE_MS, () => finish(null));
|
|
180
|
+
req.on('error', () => finish(null));
|
|
181
|
+
} catch (e) { finish(null); }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- main ----------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
function advise(installed, latest) {
|
|
187
|
+
if (!installed || !latest) return; // never guess
|
|
188
|
+
if (!isNewer(installed, latest)) return; // current, or ahead of the registry
|
|
189
|
+
process.stdout.write(
|
|
190
|
+
`liteagents ${installed} -> ${latest} available: `
|
|
191
|
+
+ `npm i -g ${PKG}@latest && ${PKG}\n`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function main() {
|
|
196
|
+
const installed = installedVersion();
|
|
197
|
+
|
|
198
|
+
const cached = readCache();
|
|
199
|
+
if (cached) return advise(installed, cached);
|
|
200
|
+
|
|
201
|
+
fetchLatest((latest) => {
|
|
202
|
+
if (!latest) return; // offline, slow, or broken: silent
|
|
203
|
+
writeCache(latest);
|
|
204
|
+
advise(installed, latest);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (require.main === module) {
|
|
209
|
+
// Every failure is silent by contract. This command is a passenger; it does
|
|
210
|
+
// not get to fail the run it is riding in.
|
|
211
|
+
try { main(); } catch (e) { /* silent */ }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
module.exports = { isNewer };
|
|
@@ -38,6 +38,26 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
|
|
|
38
38
|
- **Locate `friction.cjs`** — it is bundled next to this command at `remember/friction.cjs`
|
|
39
39
|
(the same directory as `remember.md`, whether installed or run from the package). If it
|
|
40
40
|
exists nowhere, skip to step 1 (stash-only) and tell the user friction.cjs is missing.
|
|
41
|
+
- **Check for a newer liteagents** (best-effort, one line, never blocking) — bundled
|
|
42
|
+
beside `friction.cjs` as `remember/version-check.cjs`. Call it by its **absolute
|
|
43
|
+
path**, exactly as step 7 calls `docs-builder.cjs`: the cwd here is the target repo,
|
|
44
|
+
not this package, so a cwd-relative path fails everywhere except the liteagents repo
|
|
45
|
+
itself.
|
|
46
|
+
```bash
|
|
47
|
+
node ~/.config/opencode/command/remember/version-check.cjs
|
|
48
|
+
```
|
|
49
|
+
**If that path does not exist, use the directory you just resolved for
|
|
50
|
+
`friction.cjs`** — the two ship side by side, so that directory is correct for a
|
|
51
|
+
non-default install and when running from a checkout, where the path above would
|
|
52
|
+
point at the installed copy instead of the one under test.
|
|
53
|
+
It prints one advice line if the installed version is behind the registry, and prints
|
|
54
|
+
nothing otherwise. It exits 0 on every path, caches the registry answer for 24h, and
|
|
55
|
+
is bounded to ~2s, so it cannot stall this run. If it prints a line, relay it verbatim
|
|
56
|
+
in your final report; never act on it and never run the install yourself.
|
|
57
|
+
- **If the script is missing from both locations, say so** — one line, same rule as step
|
|
58
|
+
7's "applicable but could not run". A failed *check* (offline, registry down, timeout)
|
|
59
|
+
stays silent by design: it is a once-a-day nudge, not a result anyone is waiting on. A
|
|
60
|
+
missing *script* means the install is incomplete, which is worth a word.
|
|
41
61
|
- **Resolve the global sessions root** — probe this list top-to-bottom, use the first that
|
|
42
62
|
exists and contains `.jsonl` files directly, or one level down in per-project
|
|
43
63
|
subdirectories (friction.cjs scans exactly those two levels, not a deep recursive walk).
|
|
@@ -72,11 +92,26 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
|
|
|
72
92
|
fresh output). **Move only those pipeline files** — anything else in `.opencode/memory/`
|
|
73
93
|
(e.g. user-owned rule files) stays where it is. Remove the old dirs only if empty, update the managed MEMORY section in AGENTS.md to
|
|
74
94
|
the new reference (step 5), and tell the user exactly what moved.
|
|
75
|
-
- **
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
95
|
+
- **Sync `AGENT_RULES.md` from the installed template** — run the bundled script, which
|
|
96
|
+
does the whole decision itself. Call it by **absolute path**, for the same reason as
|
|
97
|
+
`version-check.cjs` in step 0: the cwd is the target repo, not this package.
|
|
98
|
+
```bash
|
|
99
|
+
node ~/.config/opencode/command/remember/sync-rules.cjs
|
|
100
|
+
```
|
|
101
|
+
It compares `.opencode/remember/AGENT_RULES.md` against the template shipped beside it
|
|
102
|
+
and takes one of three actions: **absent** — copies it in; **identical** — does
|
|
103
|
+
nothing at all, no write and no output; **differs** — moves the old body to
|
|
104
|
+
`AGENT_RULES.md.bak` and copies the new one in, reporting both. Relay whatever it
|
|
105
|
+
prints in the step-8 report; it is silent when nothing changed.
|
|
106
|
+
|
|
107
|
+
**This replaced a bootstrap-once rule that never refreshed**, which left a measured 35
|
|
108
|
+
repos many releases behind. The rules doc is a shipped standards document, so it is
|
|
109
|
+
kept current rather than frozen on first write — nothing is destroyed, because a
|
|
110
|
+
differing body is always preserved in the backup first.
|
|
111
|
+
|
|
112
|
+
The comparison is a byte compare done *by the script*, never by you: a model-performed
|
|
113
|
+
copy can re-wrap a line or drop a trailing newline, and the file would then differ
|
|
114
|
+
forever, backing up on every single run.
|
|
80
115
|
- Read all `.opencode/stash/*.md` files in the current project
|
|
81
116
|
- Read friction output written in step 0: `.opencode/remember/friction/antigen_clusters.json` (preferred) or `.opencode/remember/friction/antigen_review.md` (fallback). On the fallback path, step 4c does NO counting — merge quotes into
|
|
82
117
|
matching entries only; never change `sessions`, `last_seen`, or `recurred_while_hot` (the
|
|
@@ -88,7 +123,11 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
|
|
|
88
123
|
length-gate debt on `.opencode/remember/MEMORY.md`. Steps 4-5 (friction → ledger count →
|
|
89
124
|
Antigens render) are stash-independent and still run whenever friction produced output
|
|
90
125
|
(see step 4's own guard). If there is also no friction
|
|
91
|
-
output, report "nothing to consolidate" and stop after step 1
|
|
126
|
+
output, report "nothing to consolidate" and stop after step 1 — **but run
|
|
127
|
+
step 5's `stub-check.cjs` before you stop.** The stub shape does not depend on
|
|
128
|
+
there being anything to consolidate, and skipping it on quiet runs is exactly
|
|
129
|
+
how a repo with nothing to remember stays broken forever. `sync-rules.cjs`
|
|
130
|
+
already ran above, for the same reason.
|
|
92
131
|
|
|
93
132
|
2. **Extract from unprocessed stashes** (up to 5 stashes per agent, as few agents as possible — see Guardrails)
|
|
94
133
|
- Each agent reads its batch of stashes together and calls the mid-tier model (see Guardrails) to extract:
|
|
@@ -387,11 +426,32 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
|
|
|
387
426
|
Users trim this section deliberately (a pointer-only variant is common), and rewriting
|
|
388
427
|
it silently re-adds text they removed, on every single run, forever. Observed in the
|
|
389
428
|
field: a run restored the inline rules into a AGENTS.md whose owner had cut them, and
|
|
390
|
-
the edit had to be reverted by hand.
|
|
391
|
-
handled
|
|
429
|
+
the edit had to be reverted by hand. Note this no longer matches how `AGENT_RULES.md`
|
|
430
|
+
itself is handled: `sync-rules.cjs` refreshes that file every run, because it is a
|
|
431
|
+
shipped standards document with a backup behind it. This section is prose the user
|
|
432
|
+
owns, with nothing behind it — so it stays bootstrap-once.
|
|
392
433
|
- If an existing pair is present but its **path pointer** is missing or wrong, that is
|
|
393
434
|
load-bearing: **report it and stop**, do not silently rewrite the section around it.
|
|
394
435
|
|
|
436
|
+
- **Then assert the stub SHAPE mechanically** — run the bundled script by **absolute
|
|
437
|
+
path**, for the same reason as steps 0 and 1:
|
|
438
|
+
```bash
|
|
439
|
+
node ~/.config/opencode/command/remember/stub-check.cjs
|
|
440
|
+
```
|
|
441
|
+
It edits only *inside* the marker pairs, and only the mechanism: a MEMORY include that
|
|
442
|
+
is not `@.opencode/remember/MEMORY.md` is repaired, and an `@`-include of
|
|
443
|
+
`AGENT_RULES.md` is demoted to a plain pointer. Prose inside the blocks is user-owned
|
|
444
|
+
and is never touched, which is why the bootstrap-once rule above still holds. It will
|
|
445
|
+
**not** repoint a MEMORY include at a file that does not exist — an un-migrated
|
|
446
|
+
`.opencode/memory/` repo has a live MEMORY.md at the old path, and breaking a working
|
|
447
|
+
include to satisfy a naming convention is worse than reporting it. Silent when the
|
|
448
|
+
shape is already current; relay whatever it prints in the step-8 report.
|
|
449
|
+
|
|
450
|
+
Measured 2026-09-03: 21 of 37 local repos still carried the pre-v2.19 `@`-include of
|
|
451
|
+
`AGENT_RULES.md`, hot-loading ~300 lines into every session. A shape rule checked by
|
|
452
|
+
asking you to look is a rule that drifts back; this one is a byte-level assertion done
|
|
453
|
+
by the script, never by you.
|
|
454
|
+
|
|
395
455
|
```markdown
|
|
396
456
|
# Project Memory
|
|
397
457
|
> Auto-generated by /remember. Do not edit manually.
|
|
@@ -535,7 +595,14 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
|
|
|
535
595
|
ledger: ag-003 "don't commit per change" RECURRED while hot (2/2) → rephrased, attempt 2
|
|
536
596
|
ledger: ag-002 "literal scoped ask" ESCALATED → Fact; 2 phrasings failed. Hook or accept?
|
|
537
597
|
```
|
|
538
|
-
-
|
|
598
|
+
- Relay verbatim whatever `version-check.cjs` (step 0), `sync-rules.cjs` (step 1), and
|
|
599
|
+
`stub-check.cjs` (step 5) printed. Never re-word or summarize them: they are the
|
|
600
|
+
record of a file that was written or a version gap, and a paraphrase of "your body
|
|
601
|
+
was backed up to AGENT_RULES.md.bak" can lose the filename the user needs.
|
|
602
|
+
- Each is silent when nothing changed, so silence is the normal case and there is
|
|
603
|
+
nothing to invent — never report an action that produced no output.
|
|
604
|
+
- Never a silent write: if any of the three wrote or moved a file and you did not
|
|
605
|
+
relay its line, that is a defect.
|
|
539
606
|
- If step 7 ran the auto re-index, say so and name the regenerated files
|
|
540
607
|
(`docs/index.md`, plus `docs/log.md` if touched) so they are staged with this run
|
|
541
608
|
- Confirm MEMORY.md and AGENTS.md updated
|
|
@@ -543,7 +610,8 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
|
|
|
543
610
|
**File locations (all project-local — two dirs: `/stash` owns `.opencode/stash/`, `/remember` owns `.opencode/remember/`)**
|
|
544
611
|
- Stash files: `.opencode/stash/*.md`
|
|
545
612
|
- Memory file: `.opencode/remember/MEMORY.md` (single source of truth, referenced as `@.opencode/remember/MEMORY.md`)
|
|
546
|
-
- Rules template: `.opencode/remember/AGENT_RULES.md` (
|
|
613
|
+
- Rules template: `.opencode/remember/AGENT_RULES.md` (refreshed from the bundled package template every `/remember` run by `sync-rules.cjs`; a differing body is backed up first, not silently overwritten — referenced by a plain path pointer, not `@`-referenced — see step 5)
|
|
614
|
+
- Rules backup: `.opencode/remember/AGENT_RULES.md.bak` (written by `sync-rules.cjs` only when the existing body differs from the template; a single file, overwritten each time it fires — not timestamped)
|
|
547
615
|
- Antigen ledger: `.opencode/remember/ledger.json` (per-rule evidence trail: class, status, attempts/rejected-buffer, recurrence-while-hot)
|
|
548
616
|
- Consolidation report: `.opencode/remember/report.md` (latest step-8 report, overwritten each run)
|
|
549
617
|
- Processed manifest: `.opencode/remember/.processed`
|
|
@@ -91,16 +91,6 @@
|
|
|
91
91
|
},
|
|
92
92
|
"description": "System design, technology selection, API design, scalability planning"
|
|
93
93
|
},
|
|
94
|
-
"context-builder": {
|
|
95
|
-
"prompt": "{file:./agent/context-builder.md}",
|
|
96
|
-
"mode": "subagent",
|
|
97
|
-
"tools": {
|
|
98
|
-
"write": true,
|
|
99
|
-
"edit": true,
|
|
100
|
-
"bash": true
|
|
101
|
-
},
|
|
102
|
-
"description": "Initialize project context, discover documentation, create knowledge bases"
|
|
103
|
-
},
|
|
104
94
|
"market-researcher": {
|
|
105
95
|
"prompt": "{file:./agent/market-researcher.md}",
|
|
106
96
|
"mode": "subagent",
|
|
@@ -31,13 +31,13 @@ Install for your platform:
|
|
|
31
31
|
|
|
32
32
|
| Platform | Installation | What's Included |
|
|
33
33
|
|----------|--------------|-----------------|
|
|
34
|
-
| **Claude Code** | `cp -r claude/* ~/.claude/` |
|
|
35
|
-
| **Droid** | `cp -r droid/* ~/.factory/` |
|
|
36
|
-
| **Ampcode** | `cp -r ampcode/* ~/.config/amp/` |
|
|
37
|
-
| **OpenCode** | `cp -r opencode/* ~/.config/opencode/` |
|
|
34
|
+
| **Claude Code** | `cp -r claude/* ~/.claude/` | 10 subagents + 8 skills + 10 commands + live-canvas-channel plugin |
|
|
35
|
+
| **Droid** | `cp -r droid/* ~/.factory/` | 10 subagents + 18 commands |
|
|
36
|
+
| **Ampcode** | `cp -r ampcode/* ~/.config/amp/` | 10 subagents + 18 commands |
|
|
37
|
+
| **OpenCode** | `cp -r opencode/* ~/.config/opencode/` | 10 subagents + 18 commands |
|
|
38
38
|
|
|
39
39
|
**Key Difference**:
|
|
40
|
-
- All four platforms ship the same
|
|
40
|
+
- All four platforms ship the same 10 subagents and the same 18 capabilities
|
|
41
41
|
- **Claude Code** splits them into 8 skills (3 auto-trigger) + 10 commands
|
|
42
42
|
- **Droid / OpenCode / Ampcode** expose all 18 as commands — no auto-triggering
|
|
43
43
|
|
|
@@ -47,7 +47,7 @@ Install for your platform:
|
|
|
47
47
|
|
|
48
48
|
### Claude Code (skills + commands)
|
|
49
49
|
|
|
50
|
-
**
|
|
50
|
+
**10 Subagents** - Expert personas with specialized knowledge
|
|
51
51
|
- 3 Workflow Agents (PRD, Tasks, Implementation)
|
|
52
52
|
- 8 Specialist Agents (UX, QA, Architecture, Product, Development, etc.)
|
|
53
53
|
|
|
@@ -65,7 +65,7 @@ Install for your platform:
|
|
|
65
65
|
|
|
66
66
|
### Droid / OpenCode / Ampcode (commands only)
|
|
67
67
|
|
|
68
|
-
**
|
|
68
|
+
**10 Subagents** - the same personas, referenced from the platform's config file
|
|
69
69
|
|
|
70
70
|
**18 Commands** - All workflow capabilities in command form
|
|
71
71
|
- Combines Claude's skills + commands into one unified command set
|
|
@@ -98,7 +98,6 @@ Install for your platform:
|
|
|
98
98
|
| **system-architect** | System design, technology selection, API design, scalability planning |
|
|
99
99
|
| **feature-planner** | Epics, user stories, prioritization, backlog management, retrospectives |
|
|
100
100
|
| **market-researcher** | Market analysis, competitive research, project discovery, brainstorming |
|
|
101
|
-
| **context-builder** | Initialize project context, discover documentation, create knowledge bases |
|
|
102
101
|
|
|
103
102
|
---
|
|
104
103
|
|
|
@@ -165,7 +164,8 @@ and wiring the memory into your agent config file.
|
|
|
165
164
|
stored; running `/remember` clears the backlog.
|
|
166
165
|
2. **`/remember`** - Runs friction analysis first (best-effort — scores sessions across *all* your
|
|
167
166
|
projects from the tool's global sessions root, clusters failures into antigens), then
|
|
168
|
-
consolidates stashes + antigens into `…/remember/MEMORY.md` and injects
|
|
167
|
+
consolidates stashes + antigens into `…/remember/MEMORY.md` and injects an explicit
|
|
168
|
+
`@<tool-dir>/remember/MEMORY.md` include into the
|
|
169
169
|
**per-tool agent config — `CLAUDE.md` (Claude Code), `AGENTS.md` (Droid / OpenCode), or
|
|
170
170
|
`AGENT.md` (Ampcode)** — so every future session loads it. Each package writes to its own
|
|
171
171
|
tool's config file; the global probe list only governs which logs friction *reads*. Per-stash
|
|
@@ -247,7 +247,7 @@ Subagent workflows require manual coordination.
|
|
|
247
247
|
## Value Proposition
|
|
248
248
|
|
|
249
249
|
### For Individual Developers
|
|
250
|
-
- **Instant Expertise** - Access
|
|
250
|
+
- **Instant Expertise** - Access 10 specialist agents without hiring
|
|
251
251
|
- **Consistent Quality** - Best practices built into every agent
|
|
252
252
|
- **Faster Iteration** - Systematic workflows reduce trial-and-error
|
|
253
253
|
- **Learning Tool** - Observe expert patterns and decision-making
|
|
@@ -255,7 +255,7 @@ Subagent workflows require manual coordination.
|
|
|
255
255
|
### For Teams
|
|
256
256
|
- **Standardized Processes** - Shared agent definitions ensure consistency
|
|
257
257
|
- **Onboarding Acceleration** - New members learn patterns through agent interactions
|
|
258
|
-
- **Documentation Culture** -
|
|
258
|
+
- **Documentation Culture** - docs-builder promotes knowledge capture
|
|
259
259
|
- **Cross-Functional Collaboration** - Product, design, and engineering agents work together
|
|
260
260
|
|
|
261
261
|
### For Technical Leaders
|
|
@@ -272,7 +272,7 @@ Subagent workflows require manual coordination.
|
|
|
272
272
|
```
|
|
273
273
|
~/.claude/
|
|
274
274
|
├── CLAUDE.md # Registry + orchestrator workflows
|
|
275
|
-
├── agents/ #
|
|
275
|
+
├── agents/ # 10 subagent implementations (*.md)
|
|
276
276
|
├── skills/ # 8 skills (subdirectories with SKILL.md)
|
|
277
277
|
└── commands/ # 10 commands (*.md)
|
|
278
278
|
```
|
|
@@ -287,7 +287,7 @@ Subagent workflows require manual coordination.
|
|
|
287
287
|
```
|
|
288
288
|
~/.config/amp/
|
|
289
289
|
├── AGENT.md # Reference doc (subagents + commands)
|
|
290
|
-
├── agents/ #
|
|
290
|
+
├── agents/ # 10 subagent implementations (*.md)
|
|
291
291
|
└── commands/ # 18 commands (*.md)
|
|
292
292
|
```
|
|
293
293
|
|
|
@@ -299,7 +299,7 @@ Subagent workflows require manual coordination.
|
|
|
299
299
|
```
|
|
300
300
|
~/.factory/
|
|
301
301
|
├── AGENTS.md # Reference doc (subagents + commands)
|
|
302
|
-
├── droids/ #
|
|
302
|
+
├── droids/ # 10 subagent implementations (*.md)
|
|
303
303
|
└── commands/ # 18 commands (*.md)
|
|
304
304
|
```
|
|
305
305
|
|
|
@@ -311,7 +311,7 @@ Subagent workflows require manual coordination.
|
|
|
311
311
|
```
|
|
312
312
|
~/.config/opencode/
|
|
313
313
|
├── AGENTS.md # Reference doc (subagents + commands)
|
|
314
|
-
├── agent/ #
|
|
314
|
+
├── agent/ # 10 subagent implementations (*.md)
|
|
315
315
|
└── command/ # 18 commands (*.md)
|
|
316
316
|
```
|
|
317
317
|
|