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
@@ -1,11 +1,8 @@
1
1
  ---
2
- name: refactor
3
- description: Refactor [code]
4
- usage: /refactor <code-section> | /refactor (no args = fix-ledger mode)
5
- argument-hint: [file-or-function, or empty for the fix ledger]
6
- allowed-tools: Read, Edit, Grep, Glob, Bash(npm test *), Bash(npx jest *), Bash(npx vitest *), Bash(pnpm test *), Bash(yarn test *), Bash(pytest *), Bash(python *), Bash(go test *), Bash(cargo test *), Bash(make test *), Bash(git diff *), Bash(git grep *), Bash(git status *), Bash(git rev-parse *), Bash(git switch *)
2
+ description: Refactor and optimize [code]
7
3
  ---
8
- Refactor $ARGUMENTS.
4
+ Refactor $ARGUMENTS. A targeted refactor includes the performance pass
5
+ below — it is on by default, not a separate command.
9
6
 
10
7
  ## Guardrails
11
8
  - **Spawn a worker and explicitly select your tool's mid tier.** State the
@@ -44,15 +41,15 @@ Refactor $ARGUMENTS.
44
41
  other does.** `git status --porcelain` at exit must list only files a
45
42
  surviving bullet named — that is this command's scope guarantee, and unlike
46
43
  `/branch-review` it is not expected to be empty. It cannot police the
47
- memory directory: `.claude/` is normally gitignored, so porcelain stays
44
+ memory directory: `.opencode/` is normally gitignored, so porcelain stays
48
45
  empty whether you deleted a fixed bullet, wrote nothing, or overwrote
49
- `MEMORY.md`. So also take `md5sum .claude/remember/*` before you start and
46
+ `MEMORY.md`. So also take `md5sum .opencode/remember/*` before you start and
50
47
  again before you report, and show the comparison: only `fix-ledger.md` may
51
48
  differ. `last-review.md` in particular is `/branch-review`'s to write —
52
49
  a fixer that touches it forges the gate that judges its own work.
53
50
 
54
51
  ## Ledger mode — `$ARGUMENTS` empty
55
- Work through `.claude/remember/fix-ledger.md`, the non-blocking findings
52
+ Work through `.opencode/remember/fix-ledger.md`, the non-blocking findings
56
53
  `/branch-review` has accumulated. Everything below (goals, constraints,
57
54
  verification, HITL gates) still applies; this section only says what to
58
55
  refactor and how to close each item.
@@ -72,8 +69,14 @@ refactor and how to close each item.
72
69
  behaviour change is not a refactor — leave the bullet, note it in the report.
73
70
  5. Run the tests as described below. Then report: **fixed / dropped / left**
74
71
  with the reason per left item, and the remaining bullet count.
75
- 6. Say plainly: **commit, then run `/branch-review`** on this branch — ledger
76
- mode is a fixer, not a review, and its diff gets the ordinary gate.
72
+ 6. **Hand it back; do not chain it.** Say plainly: **commit, then run
73
+ `/branch-review`** on this branch — ledger mode is a fixer, not a review,
74
+ and its diff gets the ordinary gate. That is a sentence you *say*, not a
75
+ sequence you *run*. They are two separate calls and both are the user's:
76
+ an answer of "commit", "yes" or "go" authorizes the commit and nothing
77
+ after it. Never start `/branch-review` off the back of it. Observed in the
78
+ field: a run chained the review onto the owner's "commit" and the owner
79
+ objected.
77
80
 
78
81
  ## Goals
79
82
  - Reduce complexity
@@ -81,6 +84,35 @@ refactor and how to close each item.
81
84
  - Apply DRY
82
85
  - Better naming
83
86
  - Smaller functions (single responsibility)
87
+ - Remove needless work — the performance pass below
88
+
89
+ ## Performance — part of every targeted refactor
90
+ When `$ARGUMENTS` names a target, look for wasted work as well as messy
91
+ work: time and space complexity, N+1 queries, I/O inside a loop, needless
92
+ allocations, the same value recomputed repeatedly.
93
+
94
+ **Ground every finding before you touch it.** Performance claims are easy
95
+ to invent. A finding counts as **confirmed** only with at least one of:
96
+ - a profile, benchmark or log line showing call frequency or duration,
97
+ - the path sits on an obvious hot loop or per-request handler with real
98
+ volume,
99
+ - the user supplied evidence in the request.
100
+
101
+ Without one of those it is **uncertain — report it, do not optimise it.**
102
+ Speculative optimisation is scope creep with a stopwatch.
103
+
104
+ Fix confirmed findings under the same constraints as any other refactor:
105
+ minimal change, one obvious shape, no behaviour change, no API change.
106
+ After each such edit, re-read the changed region and confirm it still
107
+ computes the same answer — a perf change that quietly alters semantics is
108
+ the worst kind. Report per finding: **location** (`file:line`), **cost**
109
+ (concrete — "N+1 over ~1k rows on every page load", not "could be
110
+ faster"), **change**, **expected improvement**, **trade-off**
111
+ (readability / memory / consistency).
112
+
113
+ In ledger mode the surviving bullets are the whole scope — do not add
114
+ perf findings of your own. One you notice goes back to the orchestrator
115
+ as a new bullet, like any other side finding.
84
116
 
85
117
  ## Constraints
86
118
  - **NO behavior changes**
@@ -114,8 +146,20 @@ honest way to know is to run them.
114
146
  - the change is **bigger than the user asked for** (scope creep —
115
147
  unrelated cleanups, formatting, comment edits). Confirm before
116
148
  applying.
149
+ - a perf fix has **multiple reasonable shapes** (cache vs precompute vs
150
+ batch vs paginate vs index) — present the options with trade-offs, not
151
+ a chosen path.
152
+ - a perf fix trades **correctness for speed** (lossy approximation,
153
+ weaker or eventual consistency) — even when it is "obviously" faster.
154
+ - a perf fix touches **concurrency primitives** (locks, atomics,
155
+ ordering) — easy to introduce a race.
156
+ - a perf fix changes a **DB schema, response shape or caller contract**.
117
157
 
118
158
  Final report:
119
159
  - **refactor done, tests N pass / 0 fail** — ready, OR
120
160
  - **refactor done, but K tests fail** — awaiting direction (revert /
121
161
  patch / update test).
162
+
163
+ Plus the performance pass: **confirmed-and-fixed** · **confirmed-but-asking**
164
+ (why + options) · **uncertain** (what profiling or data would settle it) ·
165
+ **none found**.
@@ -1,8 +1,5 @@
1
1
  ---
2
- name: release
3
2
  description: Verify, sweep docs, cut a version — then hand the release sequence back
4
- usage: /release
5
- allowed-tools: Read, Grep, Glob, Edit, Write, Agent, Bash(git status *), Bash(git diff *), Bash(git log *), Bash(git show *), Bash(git fetch *), Bash(git add *), Bash(git commit *), Bash(git rev-parse *), Bash(git merge-base *), Bash(npm *), Bash(pnpm *), Bash(yarn *), Bash(pytest *), Bash(python *), Bash(go *), Bash(cargo *), Bash(make *)
6
3
  ---
7
4
  Release **preparation** orchestrator for the **current branch**. It runs your
8
5
  existing pre-deploy gate, sweeps the docs, bumps the version and commits —
@@ -57,7 +54,7 @@ A review must have run on this branch **at the current HEAD SHA**.
57
54
 
58
55
  **Compare the SHAs yourself; do not settle for an answer.** Run `git rev-parse
59
56
  HEAD` and compare it against the `sha:` line in
60
- `.claude/remember/last-review.md`, which `/branch-review` writes. Asking the
57
+ `.opencode/remember/last-review.md`, which `/branch-review` writes. Asking the
61
58
  orchestrator "did a review run?" puts the question to the one party with an
62
59
  incentive to say yes, so its word is not evidence — and neither is a SHA
63
60
  quoted from a chat message, which is the same claim in another costume and is
@@ -72,7 +69,7 @@ that predates this file's introduction has no record, so it does not count.
72
69
  is what makes "all findings fixed" checkable instead of promised.
73
70
  **No exceptions — including the fix ledger.** It is normally gitignored, so
74
71
  appending to it moves nothing and this never comes up. A repo that tracks
75
- `.claude/` instead will see a ledger commit land after the review and make
72
+ `.opencode/` instead will see a ledger commit land after the review and make
76
73
  it stale. That is the rule working, not a case to carve out: re-review, or
77
74
  leave the ledger uncommitted until after the release.
78
75
  - **`coverage:` naming any stage `NOT RUN`** → **stop**. A `ready` from a run
@@ -9,7 +9,7 @@
9
9
  6. [Environment](#environment)
10
10
  7. [Development Workflow](#development-workflow)
11
11
  8. [Twelve-Factor Checklist](#twelve-factor-checklist)
12
- 9. [AGENTS.md Stub](#agentsmd-stub)
12
+ 9. [CLAUDE.md Stub](#claudemd-stub)
13
13
 
14
14
  ---
15
15
 
@@ -59,7 +59,7 @@ Every task runs through three layers. Do not skip ahead to code.
59
59
  Not courtesies. These bind you as written, whether or not your tool enforces them.
60
60
 
61
61
  - **Always** identify affected files before making changes, and explain what will change and why
62
- - **Ask first** — stop and get explicit sign-off — before modifying authentication systems, database schema or migrations, CI workflows, or `.opencode/settings.json`
62
+ - **Ask first** — stop and get explicit sign-off — before modifying authentication systems, database schema or migrations, CI workflows, or `.claude/settings.json`
63
63
  - **Never** write secrets into the tree (`.env`/`*.env`, keys, credentials). They load from the environment at runtime; only a value-less `.env.example` is committed
64
64
  - **Never** commit to `main`. Commit to a new branch (name doesn't matter), then propose `/branch-review` followed by `/release`; merging and releasing are my call, made by name — "approve", "good", or "go" on a draft is not that call
65
65
 
@@ -130,90 +130,50 @@ A problem you see and don't fix goes in the report, never in a comment. Comments
130
130
 
131
131
  ## Testing Standards
132
132
 
133
- ### Rules
133
+ Principles, not a framework. Whatever the language, follow its ecosystem's conventions for
134
+ runner, layout and fixtures — these rules govern what a test must *do*, never how a
135
+ particular toolchain spells it.
134
136
 
135
- **Test behavior, not implementation.** A test suite must give you confidence to refactor freely. If changing internal code (without changing behavior) breaks tests, those tests are liabilities, not assets.
137
+ ### What a test is for
136
138
 
137
- **Follow the Testing Trophy** (not the Testing Pyramid):
138
- - Few unit tests — only for pure logic, algorithms, and complex calculations
139
- - Many integration tests — the sweet spot; test real components working together
140
- - Some E2E tests — cover critical user journeys end-to-end
141
- - Static analysis — types and linters catch bugs cheaper than tests
139
+ **Test behavior, not implementation.** A suite must give you confidence to refactor freely. If changing internal code without changing behavior breaks tests, those tests are liabilities, not assets.
142
140
 
143
- ### When to Write Tests
141
+ **Shape the Testing Trophy, not the Pyramid:** static analysis catches the cheapest bugs; few unit tests, for pure logic and algorithms; many integration tests, the sweet spot, real components working together; some end-to-end tests over the critical journeys. Target roughly 20% unit, 60% integration, 15% E2E, 5% manual.
144
142
 
145
- - **After the design stabilizes, not during exploration.** Do not TDD a prototype — you'll write 500 tests for code you delete tomorrow. First make it work (POC), then make it right (refactor + tests), then make it fast
146
- - **Write tests when the code has users.** If a function is called by other modules or exposed to users, it needs tests. Internal helpers that only serve one caller don't need their own test file
147
- - **Write tests for bugs.** Every bug fix must include a regression test that fails before the fix and passes after. This is the highest-value test you can write
148
- - **Write tests before refactoring.** Before changing working code, write characterization tests first to lock in current behavior, then refactor with confidence
149
- - **Do not write tests for glue code.** Code that just wires components together (calls A then B then C) is tested at the integration level, not unit level
143
+ ### When to write them
150
144
 
151
- ### TDD: When It Works and When It Doesn't
145
+ - **After the design stabilizes, not during exploration.** Do not test a prototype — you will write tests for code you delete tomorrow. First make it work (POC), then make it right (tests), then make it fast
146
+ - **Tests first when you already know the contract.** Pure functions, algorithms, parsers, validators, data transformations — write the test, watch it fail, then implement. When you are still discovering the interface, that same discipline produces churn and false confidence
147
+ - **Write tests for bugs.** Every fix ships a regression test that fails before the fix and passes after — the highest-value test there is
148
+ - **Write tests before refactoring.** Characterization tests lock in current behavior first, then change the code
149
+ - **Write tests when the code has users.** Called by other modules or exposed externally means it needs tests; a helper serving one caller does not need its own file
150
+ - **Do not test glue code.** Something that only wires A to B to C is covered at the integration level
152
151
 
153
- - **TDD works for:** Pure functions, algorithms, parsers, validators, data transformations — anything with clear inputs and outputs
154
- - **TDD does not work for:** Exploring a design, building a POC, or unstable interfaces. Writing tests for unstable APIs creates churn and false confidence
155
- - **The rule:** You must understand what you're building before you TDD it. TDD is a design tool for known problems, not a discovery tool for unknown ones
156
- - **Red-green-refactor discipline:** If you do TDD, follow the cycle strictly. Write a failing test, write minimal code to pass, refactor. Do not write 20 tests then implement — that's front-loading waste
152
+ ### What makes a good test
157
153
 
158
- ### What Makes a Good Test
154
+ - **Tests real behavior.** Call the public interface, assert on observable output. Never reach into internals
155
+ - **Fails for the right reason.** It breaks when the feature breaks, not when the implementation moves
156
+ - **Reads like a spec.** Someone new to the code should learn what the feature does by reading it
157
+ - **Self-contained.** Sets up its own state, runs, cleans up. No ordering dependencies, and no reliance on project directories, user config, or ambient environment
158
+ - **Deterministic.** Flaky tests erode trust. A dependency on timing, network, or global state is a defect in the test
159
+ - **Never sleep for a condition — poll for it.** Sleeping then asserting is wrong at every value: too short and it flakes under load, too long and the suite drags, and a real async bug looks identical to a guess that was too short. Wait on the condition itself, re-reading the state *inside* the loop, with a timeout that names what it was waiting for. A fixed delay is only correct once you have waited for the triggering condition, the delay comes from a documented interval rather than a guess, and a comment says why
159
160
 
160
- - **Tests real behavior.** Call the public API, assert on observable output. Do not reach into internals
161
- - **Fails for the right reason.** A good test fails when the feature is broken, not when the implementation changes
162
- - **Reads like a spec.** Someone unfamiliar with the code must understand what the feature does by reading the test
163
- - **Self-contained.** Each test sets up its own state, runs, and cleans up. No ordering dependencies between tests
164
- - **Fast and deterministic.** Flaky tests erode trust. If a test depends on timing, network, or global state, fix that dependency
161
+ ### Anti-patterns
165
162
 
166
- ### Anti-Patterns Do Not Do These
163
+ - **Mocking most of the test.** If mock setup outweighs the logic, you are testing mocks. Prefer the real thing against a temporary directory, an in-memory store, or a disposable container
164
+ - **Partial mocks.** Mirror the complete structure the real thing returns, not only the fields this test reads. A mock missing a field downstream code consumes passes here and fails in production
165
+ - **Smoke tests.** Asserting a result merely exists proves nothing. Assert on specific values, structure, or side effects
166
+ - **Testing private internals.** If it needs its own test, it should be part of the public interface; otherwise the public tests should reach it
167
+ - **Mirroring implementation.** A test that restates the source line by line breaks on every refactor and catches nothing
168
+ - **Test-only production code.** Never add a method, flag, or branch to production solely for tests. Inject the dependency instead
169
+ - **Chasing a coverage number.** 80% of meaningless tests is worse than 40% of behavioural ones. Coverage tells you what is *not* tested, never that what is covered is correct. Cover the critical path first — data, auth, money, core logic — before helpers
167
170
 
168
- - **Mocking more than 60% of the test.** If most of the test is mock setup, you're testing mocks, not code. Use real implementations with `tmp_path`, `:memory:` SQLite, or test containers
169
- - **Smoke tests.** `assert result is not None` proves nothing. Assert on specific values, structure, or side effects
170
- - **Testing private methods.** If you need to test a private method, either it should be public or the public method's tests should cover it
171
- - **Mirroring implementation.** Tests that replicate the source code line-by-line break on every refactor and catch zero bugs
172
- - **Test-only production code.** Never add methods, flags, or branches to production code solely for testing. Use dependency injection instead
171
+ ### Organization
173
172
 
174
- ### Test Organization
175
-
176
- - **Co-locate tests with packages:** `packages/<pkg>/tests/` not a root `tests/` directory. Each package owns its tests
177
- - **Separate by type:**
178
- ```
179
- packages/<pkg>/tests/
180
- unit/ # Fast, isolated, mocked deps, <1s each
181
- integration/ # Real DB, filesystem, multi-component, <10s each
182
- e2e/ # Full workflows, subprocess calls, <60s each
183
- conftest.py # Shared fixtures for this package
184
- ```
185
- - **One test file per module** (not per function). `test_auth.py` tests the auth module, not `test_login.py` + `test_logout.py` + `test_session.py`
186
- - **No duplicate test files.** Before creating a new test file, check if one already exists for that module
187
-
188
- ### Markers and Signals
189
-
190
- | Marker | Purpose | CI Behavior |
191
- |--------|---------|-------------|
192
- | `@pytest.mark.slow` | Runtime > 5s | Run in full suite, skip in quick checks |
193
- | `@pytest.mark.ml` | Requires ML deps (torch, etc.) | Skip if deps not installed |
194
- | `@pytest.mark.real_api` | Calls external APIs | Skip in CI — run manually before release |
195
-
196
- **CI runs for fast signals:**
197
- - `pytest -m "not slow and not ml and not real_api"` — fast gate on every push (~30s)
198
- - `pytest` — full suite on PR merge or nightly
199
- - Package-level runs for targeted debugging: `pytest packages/core/tests/`
200
-
201
- ### Coverage and Ratios
202
-
203
- - **Do not chase a coverage number.** 80% coverage with meaningless tests is worse than 40% with behavior-testing integration tests
204
- - **Cover the critical path first.** Data layer, auth, payment, core business logic — before helper utilities
205
- - **Coverage tells you what's NOT tested, not what IS tested.** High coverage with bad assertions is false confidence
206
- - **Delete tests that don't catch bugs.** If a test has never failed (or only fails on refactors), it's not providing value
207
-
208
- **Target ratio:** ~20% unit, ~60% integration, ~15% E2E, ~5% manual/exploratory
209
-
210
- ### Test Tooling Standards
211
-
212
- - Use `tmp_path` for filesystem tests, `:memory:` or `tmp_path` SQLite for DB tests
213
- - Use dependency injection over `@patch` — it's more readable and survives refactors
214
- - Tests must be self-sufficient — no dependency on project directories, user config, or environment state
215
- - Use factories or builders for test data, not raw constructors with 15 arguments
216
- - Keep test fixtures close to where they're used. Shared fixtures in `conftest.py`, not a global test utilities package
173
+ - **Mirror the source structure**, at whatever level the ecosystem puts tests. One test file per module, not per function, and never a second file covering a module that already has one
174
+ - **Separate by cost so CI gets a fast signal.** Keep quick isolated tests apart from ones needing real IO or a full workflow, and let the slow ones — long runtimes, heavy optional dependencies, live external APIs — be excluded from the gate that runs on every push and included in the full run
175
+ - **Fixtures live near what uses them**, shared upward only when genuinely shared. Build test data with factories or builders, never a constructor taking fifteen positional arguments
176
+ - **Delete tests that never catch anything.** A test that has only ever failed during refactors is a maintenance cost, not a safety net
217
177
 
218
178
  ---
219
179
 
@@ -281,9 +241,9 @@ The [Twelve-Factor App](https://12factor.net) methodology for modern, scalable a
281
241
 
282
242
  ---
283
243
 
284
- ## AGENTS.md Stub
244
+ ## CLAUDE.md Stub
285
245
 
286
- Copy this to any project's AGENTS.md. These are mandatory rules, not suggestions.
246
+ Copy this to any project's CLAUDE.md. These are mandatory rules, not suggestions.
287
247
 
288
248
  ```markdown
289
249
  ## Dev Rules
@@ -304,5 +264,5 @@ Copy this to any project's AGENTS.md. These are mandatory rules, not suggestions
304
264
 
305
265
  **Responsive web UI is mandatory.** Any web UI must work on mobile by default — fluid layouts, viewport meta, breakpoints, no horizontal scroll. Verify in DevTools device emulation before claiming a UI task is done. POCs exempt; real projects are not.
306
266
 
307
- For full development and testing standards, see `.opencode/remember/AGENT_RULES.md`.
267
+ For full development and testing standards, see `.claude/remember/AGENT_RULES.md`.
308
268
  ```
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * stub-check.cjs — asserts the two managed blocks in the tool config carry the
6
+ * CURRENT stub shape, and repairs the shape when it is wrong.
7
+ *
8
+ * Shape, not content. The blocks' prose is user-owned — step 5 writes it once
9
+ * and never re-imposes it, because users trim it deliberately. What this script
10
+ * touches is only the mechanism:
11
+ *
12
+ * MEMORY block @<PROJECT_DIR>/remember/MEMORY.md — an @-include.
13
+ * A bare `@MEMORY.md` resolves relative to the file that
14
+ * contains it, so in a repo root it names a file that does
15
+ * not exist and hot memory silently never loads.
16
+ *
17
+ * AGENT_RULES block <PROJECT_DIR>/remember/AGENT_RULES.md — a PLAIN pointer.
18
+ * v2.19 demoted it from an @-include on purpose: it is a
19
+ * standards guide to consult when building something new,
20
+ * not hot context, and @-including it loads ~300 lines into
21
+ * every session. Measured 2026-09-03: 21 of 37 local repos
22
+ * still carried the pre-v2.19 @-include.
23
+ *
24
+ * Two deliberate limits:
25
+ *
26
+ * - It only edits INSIDE a marker pair. A pointer elsewhere in the config is
27
+ * the user's prose and is left alone.
28
+ * - It never repoints the MEMORY include at a file that does not exist. An
29
+ * un-migrated `.opencode/memory/` repo has a live MEMORY.md at the old path;
30
+ * rewriting it to the new one would break a working include to satisfy a
31
+ * naming convention. That case is reported, not repaired.
32
+ *
33
+ * Missing marker pairs are not this script's business — step 5 creates them.
34
+ */
35
+
36
+ const fs = require('fs');
37
+ const path = require('path');
38
+
39
+ // The ONE pair of lines that differs across packages.
40
+ const PROJECT_DIR = '.opencode';
41
+ const CONFIG_FILE = 'AGENTS.md';
42
+
43
+ const MEM = { start: '<!-- MEMORY:START -->', end: '<!-- MEMORY:END -->' };
44
+ const RULES = { start: '<!-- AGENT_RULES:START -->', end: '<!-- AGENT_RULES:END -->' };
45
+
46
+ const MEMORY_REL = `${PROJECT_DIR}/remember/MEMORY.md`;
47
+ const RULES_REL = `${PROJECT_DIR}/remember/AGENT_RULES.md`;
48
+
49
+ /** lstat, not existsSync: existsSync follows links, so a DANGLING link reads
50
+ * as absent and gets walked straight past. */
51
+ function lexists(p) {
52
+ try { fs.lstatSync(p); return true; } catch (e) { return false; }
53
+ }
54
+
55
+ /**
56
+ * True when writing to `target` would land outside `repo`.
57
+ *
58
+ * There are two ways out and a guard on only one of them is false safety:
59
+ * `target` may itself be a symlink — including a dangling one, which reads as
60
+ * "the file is absent" and is still followed on write — or any parent
61
+ * directory may be a link pointing elsewhere. This runs across a whole fleet
62
+ * of repos, so a relative link only has to reach a sibling checkout.
63
+ *
64
+ * A link that stays INSIDE the repo is not an escape: a repo that keeps its
65
+ * rules or its config behind an in-repo symlink is an ordinary setup, and
66
+ * refusing it would strand that repo forever. So the leaf is followed by hand
67
+ * with readlink — which works on a dangling link, where realpath cannot — and
68
+ * each hop re-resolves the parents, because the file a link points at can sit
69
+ * behind a linked directory of its own.
70
+ */
71
+ function escapesRepo(repo, target) {
72
+ let root;
73
+ try { root = fs.realpathSync(repo); } catch (e) { return true; }
74
+
75
+ let p = path.resolve(target);
76
+ for (let hop = 0; hop < 40; hop++) {
77
+ // Resolve the existing part of the path. Walk up to the deepest ancestor
78
+ // that exists; anything below it cannot be a link yet.
79
+ const tail = [];
80
+ let dir = path.dirname(p);
81
+ while (!lexists(dir)) {
82
+ tail.unshift(path.basename(dir));
83
+ const up = path.dirname(dir);
84
+ if (up === dir) return true; // walked off the filesystem root
85
+ dir = up;
86
+ }
87
+ try { p = path.join(fs.realpathSync(dir), ...tail, path.basename(p)); }
88
+ catch (e) { return true; } // an ancestor is a dangling link
89
+
90
+ let to;
91
+ try { to = fs.readlinkSync(p); } catch (e) {
92
+ return p !== root && !p.startsWith(root + path.sep); // not a link: decide here
93
+ }
94
+ p = path.resolve(path.dirname(p), to);
95
+ }
96
+ return true; // a link cycle: refuse
97
+ }
98
+
99
+ /** Index range of the lines strictly between a marker pair, or null. */
100
+ function blockRange(lines, markers) {
101
+ const s = lines.findIndex((l) => l.trim() === markers.start);
102
+ if (s === -1) return null;
103
+ const e = lines.findIndex((l, i) => i > s && l.trim() === markers.end);
104
+ if (e === -1) return null;
105
+ return { from: s + 1, to: e }; // [from, to)
106
+ }
107
+
108
+ /**
109
+ * @returns {{fixes:string[], notes:string[], changed:boolean}}
110
+ * fixes — repairs written to disk
111
+ * notes — wrong shapes deliberately left alone, with the reason
112
+ */
113
+ function check(repo) {
114
+ const config = path.join(repo, CONFIG_FILE);
115
+ const fixes = [];
116
+ const notes = [];
117
+
118
+ // Checked before the read, not just before the write: a repair decided
119
+ // from a followed link is already the wrong decision.
120
+ if (escapesRepo(repo, config)) {
121
+ return { fixes, notes: [`${CONFIG_FILE} not checked: it leaves the repo via a symlink`], changed: false };
122
+ }
123
+
124
+ let text;
125
+ try { text = fs.readFileSync(config, 'utf8'); } catch (e) {
126
+ return { fixes, notes, changed: false }; // no config: step 5 will create one
127
+ }
128
+
129
+ const lines = text.split('\n');
130
+ let changed = false;
131
+
132
+ // ── MEMORY block: must be an @-include naming the explicit path ────────────
133
+ const mem = blockRange(lines, MEM);
134
+ if (mem) {
135
+ const i = lines.findIndex(
136
+ (l, n) => n >= mem.from && n < mem.to && /^@\S*MEMORY\.md\s*$/.test(l.trim()));
137
+ if (i === -1) {
138
+ notes.push(`${CONFIG_FILE}: MEMORY block has no @-include — hot memory does not load`);
139
+ } else {
140
+ const want = `@${MEMORY_REL}`;
141
+ const have = lines[i].trim();
142
+ if (have !== want) {
143
+ if (fs.existsSync(path.join(repo, MEMORY_REL))) {
144
+ lines[i] = want;
145
+ changed = true;
146
+ fixes.push(`${CONFIG_FILE}: MEMORY include repaired, ${have} → ${want}`);
147
+ } else {
148
+ // The old path may be the only one with a file behind it.
149
+ notes.push(
150
+ `${CONFIG_FILE}: MEMORY include is ${have}, not ${want} — left as is, `
151
+ + `${MEMORY_REL} does not exist yet`);
152
+ }
153
+ }
154
+ }
155
+ }
156
+
157
+ // ── AGENT_RULES block: must be a PLAIN pointer, never an @-include ─────────
158
+ const rules = blockRange(lines, RULES);
159
+ if (rules) {
160
+ const i = lines.findIndex(
161
+ (l, n) => n >= rules.from && n < rules.to && /^@\S*AGENT_RULES\.md\s*$/.test(l.trim()));
162
+ if (i !== -1) {
163
+ // Demote in place. The path is kept as written — only the @ is dropped,
164
+ // because the @ is the defect and the path may be a deliberate variant.
165
+ const had = lines[i].trim();
166
+ lines[i] = had.slice(1);
167
+ changed = true;
168
+ fixes.push(
169
+ `${CONFIG_FILE}: AGENT_RULES pointer demoted from an @-include (${had} → ${had.slice(1)}) `
170
+ + `— it is a standards guide, not hot context`);
171
+ } else {
172
+ const hasPointer = lines
173
+ .slice(rules.from, rules.to)
174
+ .some((l) => /AGENT_RULES\.md/.test(l));
175
+ if (!hasPointer) {
176
+ notes.push(`${CONFIG_FILE}: AGENT_RULES block has no path pointer — nothing points at the rules`);
177
+ }
178
+ }
179
+ }
180
+
181
+ if (changed) {
182
+ try {
183
+ fs.writeFileSync(config, lines.join('\n'));
184
+ } catch (e) {
185
+ return { fixes: [], notes: [`${CONFIG_FILE} not repaired: ${e.message}`], changed: false };
186
+ }
187
+ }
188
+
189
+ return { fixes, notes, changed };
190
+ }
191
+
192
+ function main() {
193
+ const repo = process.argv[2] || process.cwd();
194
+ const r = check(repo);
195
+ for (const line of r.fixes) process.stdout.write(`${line}\n`);
196
+ for (const line of r.notes) process.stdout.write(`${line}\n`);
197
+ // Silent when the shape is already current — the common case.
198
+ }
199
+
200
+ if (require.main === module) {
201
+ // A passenger on /remember, like version-check.cjs and sync-rules.cjs: it
202
+ // never gets to fail the run it rides in.
203
+ try { main(); } catch (e) { /* silent */ }
204
+ }
205
+
206
+ module.exports = { check, PROJECT_DIR, CONFIG_FILE, MEMORY_REL, RULES_REL };