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 = '.amp';
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 = '.claude';
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 ~/.claude, 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 };
@@ -0,0 +1,220 @@
1
+ ---
2
+ name: root-cause
3
+ description: Use when any test fails, bug appears, or behaviour surprises you, before proposing a fix - find the cause and prove it, by reading real evidence, tracing bad values back to their origin, comparing against a working case, and testing one hypothesis at a time
4
+ allowed-tools: Read, Grep, Glob
5
+ ---
6
+
7
+ # Root Cause
8
+
9
+ **Find the cause before you change any code.**
10
+
11
+ ## The Law
12
+
13
+ ```
14
+ NO FIX WITHOUT A CAUSE YOU CAN POINT AT
15
+ ```
16
+
17
+ A fix at the place the error *appeared* is a symptom fix. It is a failure even when
18
+ the symptom goes away, because the real cause is still there and will surface
19
+ somewhere else, later, with less context.
20
+
21
+ You cannot propose a fix until Phase 1 is done.
22
+
23
+ ## When to Use
24
+
25
+ Any technical issue: a failing test, a production bug, unexpected behaviour, a
26
+ performance problem, a broken build, an integration that will not talk.
27
+
28
+ **Especially when it feels like overkill:**
29
+ - Under time pressure — emergencies are exactly when guessing is most tempting and most expensive
30
+ - "Just one quick fix" looks obvious
31
+ - You have already tried a fix and it did not work
32
+ - You do not fully understand the issue
33
+
34
+ Simple bugs have root causes too, and finding one takes minutes. Guess-and-check
35
+ takes hours and leaves damage behind.
36
+
37
+ ---
38
+
39
+ ## Phase 1 — Gather Evidence
40
+
41
+ Do all of this **before** forming any opinion about the fix.
42
+
43
+ ### 1. Read the error completely
44
+
45
+ Do not skim past it. Read the whole message, the whole stack trace, every warning
46
+ above it. Note line numbers, file paths, error codes. The answer is often written
47
+ there in full.
48
+
49
+ ### 2. Reproduce it consistently
50
+
51
+ Can you trigger it on demand? What are the exact steps? Does it happen every time?
52
+
53
+ If it is not reproducible, gather more data. Do not start guessing — an
54
+ intermittent bug you cannot trigger is a bug you cannot prove you fixed.
55
+
56
+ ### 3. Check what changed
57
+
58
+ Recent commits, the working diff, new dependencies, config edits, environment
59
+ differences between the place it works and the place it does not.
60
+
61
+ Be careful here: the most recent change is the most *available* suspect, not the
62
+ most likely one. Recency is a lead to test, never a conclusion.
63
+
64
+ ### 4. Instrument the boundaries (multi-component systems)
65
+
66
+ When the path crosses components — CI → build → sign, API → service → database,
67
+ workflow → script → tool — do not reason about where it breaks. Measure it.
68
+
69
+ For each boundary, log what goes **in** and what comes **out**, and confirm
70
+ configuration and environment actually propagated across it.
71
+
72
+ ```bash
73
+ echo "=== layer 1: is the secret present in the workflow? ==="
74
+ echo "IDENTITY: ${IDENTITY:+SET}${IDENTITY:-UNSET}"
75
+
76
+ echo "=== layer 2: did it survive into the build script? ==="
77
+ env | grep IDENTITY || echo "IDENTITY not in environment"
78
+
79
+ echo "=== layer 3: what does the tool actually see? ==="
80
+ security find-identity -v
81
+ ```
82
+
83
+ Run it **once** to find which boundary fails, then investigate only that
84
+ component. This turns "somewhere in the pipeline" into a named layer.
85
+
86
+ ### 5. Trace the bad value back to where it was born
87
+
88
+ When the error surfaces deep in the call stack, the place it exploded is almost
89
+ never the place it went wrong. Walk backwards.
90
+
91
+ **The chain:**
92
+
93
+ 1. **Observe the symptom** — `git init` ran in the source directory
94
+ 2. **Find the immediate cause** — the code that directly did it:
95
+ `execFileAsync('git', ['init'], { cwd: projectDir })`
96
+ 3. **Ask what called this, and with what value** — `projectDir` was `''`, and an
97
+ empty `cwd` silently resolves to the process's own directory
98
+ 4. **Keep going up** — who passed the empty string? and who gave it to *them*?
99
+ 5. **Stop at the origin** — the point where a correct value first became wrong
100
+
101
+ Fix it **there**. Then, if the value is dangerous, validate it at each layer on
102
+ the way down as well, so the same mistake cannot recur through a different path.
103
+
104
+ **When you cannot trace it by reading, instrument it:**
105
+
106
+ ```typescript
107
+ async function gitInit(directory: string) {
108
+ console.error('DEBUG git init:', {
109
+ directory,
110
+ cwd: process.cwd(),
111
+ stack: new Error().stack,
112
+ });
113
+ await execFileAsync('git', ['init'], { cwd: directory });
114
+ }
115
+ ```
116
+
117
+ - Capture the **stack**, not just the value — it names the caller you are looking for
118
+ - Log **before** the dangerous operation, not in its failure handler
119
+ - Include surrounding context: the directory, the working directory, relevant environment
120
+ - In tests, write to standard error directly; a project logger may be suppressed
121
+
122
+ **When something pollutes a test run but you cannot tell which test:** bisect.
123
+ Run the tests one at a time and stop at the first one that leaves the mess behind.
124
+ `find-polluter.sh` in this skill's directory does exactly that.
125
+
126
+ ---
127
+
128
+ ## Phase 2 — Compare Against Something That Works
129
+
130
+ You are looking for a difference, and the fastest way to see one is a side-by-side.
131
+
132
+ - **Find a working example** — similar code in the same codebase that behaves correctly
133
+ - **Read the reference completely** if you are following a pattern. Every line. Skimming a
134
+ reference and adapting "the idea" is how half-understood patterns ship
135
+ - **List every difference**, however small. Do not filter by "that can't matter" — that
136
+ judgement is exactly what you do not have yet
137
+ - **Check the dependencies**: what config, what environment, what other components does
138
+ the working one have that the broken one does not?
139
+
140
+ ---
141
+
142
+ ## Phase 3 — One Hypothesis, One Variable
143
+
144
+ - **State it in writing:** "I think X is the cause, because Y." Specific, not vague.
145
+ - **Test it with the smallest possible change.** One variable. A controlled test that
146
+ isolates your suspect beats a plausible story about the most recent commit.
147
+ - **Read the result honestly.** Confirmed → Phase 4. Not confirmed → form a *new*
148
+ hypothesis. Never stack a second fix on top of an unconfirmed first one.
149
+ - **Say when you do not know.** "I don't understand why X happens" is a real state and a
150
+ useful thing to report. Pretending to know produces confident wrong fixes.
151
+
152
+ ---
153
+
154
+ ## Phase 4 — Fix at the Source
155
+
156
+ ### 1. Write the failing test first
157
+
158
+ The simplest reproduction you can manage — an automated test if there is a suite, a
159
+ throwaway script if there is not.
160
+
161
+ **Run it against the unfixed code and watch it fail, for the reason you expect.** A
162
+ test written after the fix, or one that passes both before and after, proves nothing.
163
+ This is the step that converts your hypothesis into evidence.
164
+
165
+ ### 2. Make one change
166
+
167
+ Fix the cause you identified. One change. No "while I'm here" improvements, no bundled
168
+ refactoring — those make it impossible to tell what actually worked.
169
+
170
+ ### 3. Verify
171
+
172
+ Does the new test pass? Does the rest of the suite still pass? Is the original symptom
173
+ actually gone — checked, not assumed?
174
+
175
+ ### 4. If the fix did not work, stop and count
176
+
177
+ Under three attempts: return to Phase 1 with what you just learned. The failed attempt
178
+ is evidence.
179
+
180
+ **Three or more failed fixes means you have the wrong model of the problem.** Do not
181
+ attempt a fourth. The pattern to watch for: each fix uncovers a new problem somewhere
182
+ else, or each one needs "just a bit of refactoring" to land.
183
+
184
+ That is an architecture question, not a hypothesis question. Stop and raise it.
185
+
186
+ ---
187
+
188
+ ## Red Flags — Stop and Return to Phase 1
189
+
190
+ - "Quick fix now, investigate later"
191
+ - "Just try changing X and see"
192
+ - Several changes at once, then run the tests
193
+ - "Skip the test, I'll check it by hand"
194
+ - "It's probably X" — probably is not a cause
195
+ - "I don't fully understand this, but this might work"
196
+ - Listing fixes before tracing where the bad value came from
197
+ - "One more attempt" when two have already failed
198
+ - Each fix revealing a new problem somewhere else
199
+
200
+ ## Quick Reference
201
+
202
+ | Phase | You do | Done when |
203
+ |---|---|---|
204
+ | **1. Evidence** | Read the error, reproduce, check changes, instrument boundaries, trace the value back | You can say what happened and where it started |
205
+ | **2. Compare** | Find a working case, read it fully, list every difference | You know what is different |
206
+ | **3. Hypothesis** | State one cause, test one variable | Confirmed, or you have a new hypothesis |
207
+ | **4. Fix** | Failing test first, one change, verify | The test that failed now passes, and nothing else broke |
208
+
209
+ ## When There Really Is No Root Cause
210
+
211
+ It happens — a genuine race, an upstream bug, a hardware fault. But roughly nineteen
212
+ times in twenty, "no root cause" means the investigation stopped early.
213
+
214
+ Before you conclude it: can you reproduce it? Did you instrument every boundary? Did
215
+ you trace the value to its origin, or only to the last function you recognised?
216
+
217
+ ## Related
218
+
219
+ - **AGENT_RULES.md → Testing Standards** — what makes the Phase 4 test a real one
220
+ - **`/test-generate`** — build out the suite once the cause is fixed
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: security
3
3
  description: Security audit — recurring six, injection, auth, trust boundaries
4
- usage: /security
5
4
  argument-hint: [file, directory, or leave empty for full scan]
6
5
  allowed-tools: Read, Grep, Glob, Bash(git log:*), Bash(git grep:*), Bash(rg:*)
6
+ disable-model-invocation: true
7
7
  ---
8
8
  Audit $ARGUMENTS for security vulnerabilities. **Reports, never edits** — it
9
9
  verifies every claim, then hands the findings to whoever asked.
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  name: ship
3
3
  description: Mechanical pre-deploy gate — tests, build, tree state
4
- usage: /ship
5
4
  allowed-tools: Read, Grep, Glob, Bash(git:*), Bash(npm:*), Bash(pnpm:*), Bash(yarn:*), Bash(pytest:*), Bash(python:*), Bash(go:*), Bash(cargo:*), Bash(make:*)
5
+ disable-model-invocation: true
6
6
  ---
7
7
  Mechanical pre-deploy / pre-merge gate. Every item here is answerable by
8
8
  **running a command** and reading its exit code — no code judgment. Code