liteagents 2.24.1 → 3.5.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.
Files changed (160) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/README.md +120 -160
  3. package/installer/cli.js +40 -5
  4. package/installer/installation-engine.js +8 -0
  5. package/package.json +4 -3
  6. package/packages/ampcode/AGENT.md +11 -20
  7. package/packages/ampcode/agents/code-developer.md +11 -17
  8. package/packages/ampcode/agents/orchestrator.md +3 -3
  9. package/packages/ampcode/agents/quality-assurance.md +3 -1
  10. package/packages/ampcode/{commands/brainstorming.md → skills/brainstorming/SKILL.md} +2 -3
  11. package/packages/ampcode/{commands/branch-review.md → skills/branch-review/SKILL.md} +13 -13
  12. package/packages/{claude/commands/docs-builder.md → ampcode/skills/docs-builder/SKILL.md} +1 -1
  13. package/packages/ampcode/{commands/live-canvas.md → skills/live-canvas/SKILL.md} +8 -4
  14. package/packages/ampcode/{commands/refactor.md → skills/refactor/SKILL.md} +57 -9
  15. package/packages/ampcode/{commands/release.md → skills/release/SKILL.md} +4 -4
  16. package/packages/{claude/commands → ampcode/skills}/remember/AGENT_RULES.md +44 -84
  17. package/packages/ampcode/{commands/remember.md → skills/remember/SKILL.md} +86 -13
  18. package/packages/ampcode/{commands → skills}/remember/friction.cjs +0 -0
  19. package/packages/ampcode/skills/remember/stub-check.cjs +206 -0
  20. package/packages/ampcode/skills/remember/sync-rules.cjs +178 -0
  21. package/packages/ampcode/skills/remember/version-check.cjs +214 -0
  22. package/packages/ampcode/skills/root-cause/SKILL.md +220 -0
  23. package/packages/ampcode/{commands/trace-back → skills/root-cause}/find-polluter.sh +0 -0
  24. package/packages/{claude/commands/security.md → ampcode/skills/security/SKILL.md} +1 -1
  25. package/packages/{claude/commands/ship.md → ampcode/skills/ship/SKILL.md} +1 -1
  26. package/packages/ampcode/skills/skill-creator/LICENSE.txt +202 -0
  27. package/packages/ampcode/{commands/skill-creator.md → skills/skill-creator/SKILL.md} +1 -2
  28. package/packages/ampcode/{commands → skills}/skill-creator/scripts/init_skill.py +0 -0
  29. package/packages/ampcode/{commands → skills}/skill-creator/scripts/package_skill.py +0 -0
  30. package/packages/ampcode/{commands → skills}/skill-creator/scripts/quick_validate.py +0 -0
  31. package/packages/ampcode/{commands/stash.md → skills/stash/SKILL.md} +2 -1
  32. package/packages/{claude/commands/test-generate.md → ampcode/skills/test-generate/SKILL.md} +2 -2
  33. package/packages/ampcode/variants.json +2 -2
  34. package/packages/claude/CLAUDE.md +11 -19
  35. package/packages/claude/agents/code-developer.md +11 -17
  36. package/packages/claude/agents/orchestrator.md +4 -5
  37. package/packages/claude/agents/quality-assurance.md +3 -1
  38. package/packages/claude/skills/brainstorming/SKILL.md +1 -2
  39. package/packages/claude/{commands/branch-review.md → skills/branch-review/SKILL.md} +1 -1
  40. package/packages/{ampcode/commands/docs-builder.md → claude/skills/docs-builder/SKILL.md} +7 -7
  41. package/packages/claude/skills/live-canvas/SKILL.md +5 -1
  42. package/packages/claude/{commands/refactor.md → skills/refactor/SKILL.md} +53 -5
  43. package/packages/claude/{commands/release.md → skills/release/SKILL.md} +1 -1
  44. package/packages/{ampcode/commands → claude/skills}/remember/AGENT_RULES.md +38 -78
  45. package/packages/claude/{commands/remember.md → skills/remember/SKILL.md} +86 -13
  46. package/packages/claude/{commands → skills}/remember/friction.cjs +0 -0
  47. package/packages/claude/skills/remember/stub-check.cjs +206 -0
  48. package/packages/claude/skills/remember/sync-rules.cjs +178 -0
  49. package/packages/claude/skills/remember/version-check.cjs +214 -0
  50. package/packages/claude/skills/root-cause/SKILL.md +220 -0
  51. package/packages/{ampcode/commands/security.md → claude/skills/security/SKILL.md} +2 -2
  52. package/packages/{ampcode/commands/ship.md → claude/skills/ship/SKILL.md} +2 -2
  53. package/packages/claude/skills/skill-creator/SKILL.md +1 -2
  54. package/packages/claude/{commands/stash.md → skills/stash/SKILL.md} +2 -1
  55. package/packages/{ampcode/commands/test-generate.md → claude/skills/test-generate/SKILL.md} +3 -3
  56. package/packages/claude/variants.json +1 -2
  57. package/packages/droid/AGENTS.md +10 -16
  58. package/packages/droid/commands/brainstorming.md +1 -4
  59. package/packages/droid/commands/branch-review.md +11 -14
  60. package/packages/droid/commands/docs-builder.md +0 -3
  61. package/packages/droid/commands/live-canvas.md +7 -5
  62. package/packages/droid/commands/refactor.md +55 -10
  63. package/packages/droid/commands/release.md +2 -5
  64. package/packages/droid/commands/remember/AGENT_RULES.md +38 -78
  65. package/packages/droid/commands/remember/stub-check.cjs +206 -0
  66. package/packages/droid/commands/remember/sync-rules.cjs +178 -0
  67. package/packages/droid/commands/remember/version-check.cjs +214 -0
  68. package/packages/droid/commands/remember.md +84 -14
  69. package/packages/droid/commands/root-cause.md +218 -0
  70. package/packages/droid/commands/security.md +0 -3
  71. package/packages/droid/commands/ship.md +0 -3
  72. package/packages/droid/commands/skill-creator/LICENSE.txt +202 -0
  73. package/packages/droid/commands/skill-creator.md +0 -4
  74. package/packages/droid/commands/stash.md +0 -2
  75. package/packages/droid/commands/test-generate.md +1 -4
  76. package/packages/droid/droids/1-create-prd.md +6 -2
  77. package/packages/droid/droids/2-generate-tasks.md +1 -2
  78. package/packages/droid/droids/3-process-task-list.md +1 -2
  79. package/packages/droid/droids/code-developer.md +12 -19
  80. package/packages/droid/droids/feature-planner.md +1 -2
  81. package/packages/droid/droids/market-researcher.md +1 -2
  82. package/packages/droid/droids/orchestrator.md +3 -4
  83. package/packages/droid/droids/quality-assurance.md +4 -3
  84. package/packages/droid/droids/system-architect.md +1 -2
  85. package/packages/droid/droids/ui-designer.md +1 -2
  86. package/packages/opencode/AGENTS.md +10 -16
  87. package/packages/opencode/agent/code-developer.md +11 -17
  88. package/packages/opencode/agent/orchestrator.md +2 -2
  89. package/packages/opencode/agent/quality-assurance.md +3 -1
  90. package/packages/opencode/command/brainstorming.md +1 -4
  91. package/packages/opencode/command/branch-review.md +11 -15
  92. package/packages/opencode/command/docs-builder.md +0 -4
  93. package/packages/opencode/command/live-canvas.md +7 -5
  94. package/packages/opencode/command/refactor.md +55 -11
  95. package/packages/opencode/command/release.md +2 -5
  96. package/packages/opencode/command/remember/AGENT_RULES.md +38 -78
  97. package/packages/opencode/command/remember/stub-check.cjs +206 -0
  98. package/packages/opencode/command/remember/sync-rules.cjs +178 -0
  99. package/packages/opencode/command/remember/version-check.cjs +214 -0
  100. package/packages/opencode/command/remember.md +84 -14
  101. package/packages/opencode/command/root-cause.md +218 -0
  102. package/packages/opencode/command/security.md +0 -4
  103. package/packages/opencode/command/ship.md +0 -3
  104. package/packages/opencode/command/skill-creator/LICENSE.txt +202 -0
  105. package/packages/opencode/command/skill-creator.md +0 -4
  106. package/packages/opencode/command/stash.md +0 -3
  107. package/packages/opencode/command/test-generate.md +1 -5
  108. package/packages/opencode/opencode.jsonc +4 -34
  109. package/packages/subagentic-manual.md +147 -314
  110. package/packages/ampcode/agents/context-builder.md +0 -144
  111. package/packages/ampcode/commands/debug-method.md +0 -297
  112. package/packages/ampcode/commands/live-canvas/README.md +0 -264
  113. package/packages/ampcode/commands/optimize.md +0 -61
  114. package/packages/ampcode/commands/tdd-flow.md +0 -390
  115. package/packages/ampcode/commands/test-traps/example.ts +0 -158
  116. package/packages/ampcode/commands/test-traps.md +0 -378
  117. package/packages/ampcode/commands/trace-back.md +0 -176
  118. package/packages/ampcode/commands/verify-done.md +0 -152
  119. package/packages/claude/agents/context-builder.md +0 -145
  120. package/packages/claude/commands/optimize.md +0 -61
  121. package/packages/claude/plugins/live-canvas-marketplace/plugins/live-canvas-channel/README.md +0 -89
  122. package/packages/claude/skills/debug-method/CREATION-LOG.md +0 -119
  123. package/packages/claude/skills/debug-method/SKILL.md +0 -296
  124. package/packages/claude/skills/debug-method/test-academic.md +0 -14
  125. package/packages/claude/skills/debug-method/test-pressure-1.md +0 -58
  126. package/packages/claude/skills/debug-method/test-pressure-2.md +0 -68
  127. package/packages/claude/skills/debug-method/test-pressure-3.md +0 -69
  128. package/packages/claude/skills/live-canvas/README.md +0 -269
  129. package/packages/claude/skills/tdd-flow/SKILL.md +0 -392
  130. package/packages/claude/skills/test-traps/SKILL.md +0 -378
  131. package/packages/claude/skills/test-traps/example.ts +0 -158
  132. package/packages/claude/skills/trace-back/SKILL.md +0 -176
  133. package/packages/claude/skills/verify-done/SKILL.md +0 -152
  134. package/packages/droid/commands/debug-method.md +0 -297
  135. package/packages/droid/commands/live-canvas/README.md +0 -264
  136. package/packages/droid/commands/optimize.md +0 -61
  137. package/packages/droid/commands/tdd-flow.md +0 -390
  138. package/packages/droid/commands/test-traps/example.ts +0 -158
  139. package/packages/droid/commands/test-traps.md +0 -378
  140. package/packages/droid/commands/trace-back.md +0 -176
  141. package/packages/droid/commands/verify-done.md +0 -152
  142. package/packages/droid/droids/context-builder.md +0 -144
  143. package/packages/opencode/agent/context-builder.md +0 -148
  144. package/packages/opencode/command/debug-method.md +0 -297
  145. package/packages/opencode/command/live-canvas/README.md +0 -264
  146. package/packages/opencode/command/optimize.md +0 -61
  147. package/packages/opencode/command/tdd-flow.md +0 -390
  148. package/packages/opencode/command/test-traps/example.ts +0 -158
  149. package/packages/opencode/command/test-traps.md +0 -378
  150. package/packages/opencode/command/trace-back.md +0 -176
  151. package/packages/opencode/command/verify-done.md +0 -152
  152. /package/packages/ampcode/{commands → skills}/docs-builder/docs-builder.cjs +0 -0
  153. /package/packages/ampcode/{commands → skills}/live-canvas/DESIGN_PRINCIPLES.md +0 -0
  154. /package/packages/ampcode/{commands → skills}/live-canvas/dev/post-variants.html +0 -0
  155. /package/packages/ampcode/{commands → skills}/live-canvas/templates/lab-banner.html +0 -0
  156. /package/packages/ampcode/{commands → skills}/live-canvas/templates/overlay-vanilla.js +0 -0
  157. /package/packages/claude/{commands → skills}/docs-builder/docs-builder.cjs +0 -0
  158. /package/packages/claude/skills/{trace-back → root-cause}/find-polluter.sh +0 -0
  159. /package/packages/droid/commands/{trace-back → root-cause}/find-polluter.sh +0 -0
  160. /package/packages/opencode/command/{trace-back → root-cause}/find-polluter.sh +0 -0
@@ -0,0 +1,178 @@
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
+ * A link that stays INSIDE the repo is not an escape: a repo that keeps its
55
+ * rules or its config behind an in-repo symlink is an ordinary setup, and
56
+ * refusing it would strand that repo forever. So the leaf is followed by hand
57
+ * with readlink — which works on a dangling link, where realpath cannot — and
58
+ * each hop re-resolves the parents, because the file a link points at can sit
59
+ * behind a linked directory of its own.
60
+ */
61
+ function escapesRepo(repo, target) {
62
+ let root;
63
+ try { root = fs.realpathSync(repo); } catch (e) { return true; }
64
+
65
+ let p = path.resolve(target);
66
+ for (let hop = 0; hop < 40; hop++) {
67
+ // Resolve the existing part of the path. Walk up to the deepest ancestor
68
+ // that exists; anything below it cannot be a link yet.
69
+ const tail = [];
70
+ let dir = path.dirname(p);
71
+ while (!lexists(dir)) {
72
+ tail.unshift(path.basename(dir));
73
+ const up = path.dirname(dir);
74
+ if (up === dir) return true; // walked off the filesystem root
75
+ dir = up;
76
+ }
77
+ try { p = path.join(fs.realpathSync(dir), ...tail, path.basename(p)); }
78
+ catch (e) { return true; } // an ancestor is a dangling link
79
+
80
+ let to;
81
+ try { to = fs.readlinkSync(p); } catch (e) {
82
+ return p !== root && !p.startsWith(root + path.sep); // not a link: decide here
83
+ }
84
+ p = path.resolve(path.dirname(p), to);
85
+ }
86
+ return true; // a link cycle: refuse
87
+ }
88
+
89
+ function templatePath() {
90
+ // Ships beside this script, so no path guessing and no dependence on where
91
+ // the kit was installed.
92
+ return path.join(__dirname, RULES);
93
+ }
94
+
95
+ function targetPath(repo) {
96
+ return path.join(repo, PROJECT_DIR, 'remember', RULES);
97
+ }
98
+
99
+ /**
100
+ * @returns {{action:string, detail?:string}} action is one of:
101
+ * 'no-template' | 'bootstrapped' | 'unchanged' | 'updated' | 'failed'
102
+ */
103
+ function sync(repo) {
104
+ const tpl = templatePath();
105
+ let template;
106
+ try {
107
+ template = fs.readFileSync(tpl);
108
+ } catch (e) {
109
+ return { action: 'no-template', detail: tpl };
110
+ }
111
+
112
+ const target = targetPath(repo);
113
+ if (escapesRepo(repo, target)) return { action: 'escapes', detail: target };
114
+
115
+ let current = null;
116
+ try { current = fs.readFileSync(target); } catch (e) { /* absent */ }
117
+
118
+ // Byte compare. No normalisation on either side: the copy below is a plain
119
+ // byte write, so a mismatch here means the content really differs.
120
+ if (current && current.equals(template)) return { action: 'unchanged' };
121
+
122
+ try {
123
+ fs.mkdirSync(path.dirname(target), { recursive: true });
124
+ if (current) {
125
+ // Single backup, overwritten. rename() is atomic on the same filesystem
126
+ // and cannot leave a half-written backup the way copy+truncate could.
127
+ fs.renameSync(target, path.join(path.dirname(target), BACKUP));
128
+ }
129
+ fs.writeFileSync(target, template);
130
+ } catch (e) {
131
+ return { action: 'failed', detail: e.message };
132
+ }
133
+
134
+ return current ? { action: 'updated' } : { action: 'bootstrapped' };
135
+ }
136
+
137
+ function main() {
138
+ const repo = process.argv[2] || process.cwd();
139
+ const r = sync(repo);
140
+ const rel = path.join(PROJECT_DIR, 'remember', RULES);
141
+
142
+ switch (r.action) {
143
+ case 'unchanged':
144
+ break; // silent: nothing happened
145
+ case 'bootstrapped':
146
+ process.stdout.write(`${rel} created from the installed template\n`);
147
+ break;
148
+ case 'updated':
149
+ process.stdout.write(
150
+ `${rel} updated from the installed template `
151
+ + `(previous body kept as ${BACKUP} — fold your changes in before the `
152
+ + `next release, it is a single file and the next update replaces it)\n`);
153
+ break;
154
+ case 'escapes':
155
+ // Loud, never repaired: the path is under the repo but does not stay
156
+ // there, so any write lands somewhere the run was not invited.
157
+ process.stdout.write(
158
+ `${rel} not synced: it leaves the repo via a symlink — refusing to `
159
+ + `write through it\n`);
160
+ break;
161
+ case 'no-template':
162
+ // Loud: this means the install is incomplete, not that nothing changed.
163
+ process.stdout.write(
164
+ `AGENT_RULES.md not synced: no template beside this script (${r.detail})\n`);
165
+ break;
166
+ case 'failed':
167
+ process.stdout.write(`AGENT_RULES.md not synced: ${r.detail}\n`);
168
+ break;
169
+ }
170
+ }
171
+
172
+ if (require.main === module) {
173
+ // A passenger on /remember, like version-check.cjs: it never gets to fail
174
+ // the run it rides in.
175
+ try { main(); } catch (e) { /* silent */ }
176
+ }
177
+
178
+ 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>/<skills|commands|command>/remember/, so the root
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 };
@@ -1,7 +1,5 @@
1
1
  ---
2
- name: remember
3
2
  description: Consolidate stashes + friction into project memory
4
- usage: /remember
5
3
  ---
6
4
 
7
5
  Run friction analysis, then consolidate session stashes + friction antigens into a single project-local MEMORY.md, and inject into AGENTS.md. Friction runs automatically (best-effort) — there is no separate `/friction` command. A docs reconcile check runs at the end, detect-only.
@@ -38,10 +36,31 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
38
36
  - **Locate `friction.cjs`** — it is bundled next to this command at `remember/friction.cjs`
39
37
  (the same directory as `remember.md`, whether installed or run from the package). If it
40
38
  exists nowhere, skip to step 1 (stash-only) and tell the user friction.cjs is missing.
39
+ - **Check for a newer liteagents** (best-effort, one line, never blocking) — bundled
40
+ beside `friction.cjs` as `remember/version-check.cjs`. Call it by its **absolute
41
+ path**, exactly as step 7 calls `docs-builder.cjs`: the cwd here is the target repo,
42
+ not this package, so a cwd-relative path fails everywhere except the liteagents repo
43
+ itself.
44
+ ```bash
45
+ node ~/.config/opencode/command/remember/version-check.cjs
46
+ ```
47
+ **If that path does not exist, use the directory you just resolved for
48
+ `friction.cjs`** — the two ship side by side, so that directory is correct for a
49
+ non-default install and when running from a checkout, where the path above would
50
+ point at the installed copy instead of the one under test.
51
+ It prints one advice line if the installed version is behind the registry, and prints
52
+ nothing otherwise. It exits 0 on every path, caches the registry answer for 24h, and
53
+ is bounded to ~2s, so it cannot stall this run. If it prints a line, relay it verbatim
54
+ in your final report; never act on it and never run the install yourself.
55
+ - **If the script is missing from both locations, say so** — one line, same rule as step
56
+ 7's "applicable but could not run". A failed *check* (offline, registry down, timeout)
57
+ stays silent by design: it is a once-a-day nudge, not a result anyone is waiting on. A
58
+ missing *script* means the install is incomplete, which is worth a word.
41
59
  - **Resolve the global sessions root** — probe this list top-to-bottom, use the first that
42
60
  exists and contains `.jsonl` files directly, or one level down in per-project
43
61
  subdirectories (friction.cjs scans exactly those two levels, not a deep recursive walk).
44
62
  **Never prompt the user.**
63
+ <!-- mirror:literal:start — every tool's real path, identical in all four kits -->
45
64
  ```
46
65
  # ── Add your own global sessions root at the TOP so it is checked first ──
47
66
  ~/.claude/projects/ # Claude Code
@@ -51,6 +70,7 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
51
70
  ~/.codex/sessions/ # Codex CLI (use $CODEX_HOME/sessions/ if set)
52
71
  ~/.gemini/antigravity-cli/brain/ # Antigravity
53
72
  ```
73
+ <!-- mirror:literal:end -->
54
74
  > Note: `friction.cjs` parses Claude Code's session schema. The Codex/Antigravity roots
55
75
  > will resolve but yield no signals until friction learns their formats — open an issue
56
76
  > to request one: https://github.com/hamr0/liteagents/issues
@@ -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
- - **Bootstrap `AGENT_RULES.md` (one-time, silent-if-present).** If
76
- `.opencode/remember/AGENT_RULES.md` does not exist, copy it from the bundled template next
77
- to this command (`remember/AGENT_RULES.md`, same directory as `friction.cjs`). If it
78
- already exists, leave it untouched — never overwrite, even if the bundled template
79
- changes in a later version; it becomes user-owned the moment it lands in the project.
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. This matches how `AGENT_RULES.md` itself is
391
- handled bootstrapped once, never overwritten after.
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.
@@ -452,8 +512,10 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
452
512
  `index-flat` script `/docs-builder` already uses, never a model call. Wrapped so any
453
513
  failure here can never block the memory write that already happened in steps 3-6.
454
514
 
455
- - **Locate `docs-builder.cjs`** — bundled next to this command at
456
- `docs-builder/docs-builder.cjs` (same convention as `remember/friction.cjs`). Call it by
515
+ - **Locate `docs-builder.cjs`** — it ships in the `docs-builder/` directory
516
+ that sits beside this one (under `skills/` on Claude Code and Amp, under
517
+ `commands/` on Droid and opencode), exactly as `friction.cjs` ships beside
518
+ this file. Call it by
457
519
  its **absolute path** in the command below — the cwd here is the target repo, not this
458
520
  package, so a cwd-relative path fails everywhere except the liteagents repo itself.
459
521
  - **Not applicable, stay silent:** if the project has no `docs/` directory, skip without
@@ -535,7 +597,14 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
535
597
  ledger: ag-003 "don't commit per change" RECURRED while hot (2/2) → rephrased, attempt 2
536
598
  ledger: ag-002 "literal scoped ask" ESCALATED → Fact; 2 phrasings failed. Hook or accept?
537
599
  ```
538
- - If AGENT_RULES.md was bootstrapped this run, say so (one line)
600
+ - Relay verbatim whatever `version-check.cjs` (step 0), `sync-rules.cjs` (step 1), and
601
+ `stub-check.cjs` (step 5) printed. Never re-word or summarize them: they are the
602
+ record of a file that was written or a version gap, and a paraphrase of "your body
603
+ was backed up to AGENT_RULES.md.bak" can lose the filename the user needs.
604
+ - Each is silent when nothing changed, so silence is the normal case and there is
605
+ nothing to invent — never report an action that produced no output.
606
+ - Never a silent write: if any of the three wrote or moved a file and you did not
607
+ relay its line, that is a defect.
539
608
  - If step 7 ran the auto re-index, say so and name the regenerated files
540
609
  (`docs/index.md`, plus `docs/log.md` if touched) so they are staged with this run
541
610
  - Confirm MEMORY.md and AGENTS.md updated
@@ -543,7 +612,8 @@ Reads all raw material (`.opencode/stash/*.md` + `.opencode/remember/friction/an
543
612
  **File locations (all project-local — two dirs: `/stash` owns `.opencode/stash/`, `/remember` owns `.opencode/remember/`)**
544
613
  - Stash files: `.opencode/stash/*.md`
545
614
  - Memory file: `.opencode/remember/MEMORY.md` (single source of truth, referenced as `@.opencode/remember/MEMORY.md`)
546
- - Rules template: `.opencode/remember/AGENT_RULES.md` (bootstrapped once from the bundled package template on first `/remember` run, never overwritten again user-owned after that; referenced by a plain path pointer, not `@`-referenced — see step 5)
615
+ - 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)
616
+ - 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
617
  - Antigen ledger: `.opencode/remember/ledger.json` (per-rule evidence trail: class, status, attempts/rejected-buffer, recurrence-while-hot)
548
618
  - Consolidation report: `.opencode/remember/report.md` (latest step-8 report, overwritten each run)
549
619
  - Processed manifest: `.opencode/remember/.processed`