fapony 0.1.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 (106) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +473 -0
  3. package/fapony.ts +78 -0
  4. package/package.json +42 -0
  5. package/skill/git-commit-conventional/SKILL.md +68 -0
  6. package/skill/git-ship/SKILL.md +144 -0
  7. package/skill/move-to-done/SKILL.md +126 -0
  8. package/skill/plan-with-pony/SKILL.md +263 -0
  9. package/skill/review-pony/SKILL.md +254 -0
  10. package/src/analyze.ts +517 -0
  11. package/src/context/index.ts +11 -0
  12. package/src/context/projectHealth.ts +359 -0
  13. package/src/conventions-seed.ts +420 -0
  14. package/src/db/defaults.ts +26 -0
  15. package/src/db/getters.ts +33 -0
  16. package/src/db/index.ts +7 -0
  17. package/src/db/load.ts +57 -0
  18. package/src/db/store.ts +286 -0
  19. package/src/db/types.ts +79 -0
  20. package/src/debt.ts +667 -0
  21. package/src/digest/cli.ts +75 -0
  22. package/src/digest/collect.ts +625 -0
  23. package/src/digest/html.ts +208 -0
  24. package/src/digest/text.ts +191 -0
  25. package/src/gate.ts +153 -0
  26. package/src/gates.ts +194 -0
  27. package/src/hook.ts +436 -0
  28. package/src/init-mem.ts +71 -0
  29. package/src/init.ts +237 -0
  30. package/src/install/claude.ts +361 -0
  31. package/src/install/codex.ts +61 -0
  32. package/src/install/cursor.ts +167 -0
  33. package/src/install/detect.ts +78 -0
  34. package/src/install/opencode.ts +234 -0
  35. package/src/install/skills.ts +106 -0
  36. package/src/install/types.ts +69 -0
  37. package/src/install/utils.ts +29 -0
  38. package/src/install/zcode.ts +120 -0
  39. package/src/install.ts +176 -0
  40. package/src/lint-baseline.ts +260 -0
  41. package/src/map.ts +320 -0
  42. package/src/math.ts +13 -0
  43. package/src/mcp/evidence.ts +332 -0
  44. package/src/mcp/primitives.ts +316 -0
  45. package/src/mcp/tools/check.ts +243 -0
  46. package/src/mcp/tools/collect.ts +157 -0
  47. package/src/mcp/tools/context.ts +66 -0
  48. package/src/mcp/tools/index.ts +309 -0
  49. package/src/mcp/tools/mem.ts +95 -0
  50. package/src/mcp/tools/plans.ts +255 -0
  51. package/src/mcp/tools/report.ts +285 -0
  52. package/src/mcp/tools/stats.ts +96 -0
  53. package/src/mcp/tools/usage.ts +211 -0
  54. package/src/mcp/tools/verdict.ts +148 -0
  55. package/src/mcp/transport.ts +241 -0
  56. package/src/mcp/types.ts +54 -0
  57. package/src/mcp/worktree.ts +27 -0
  58. package/src/memory.ts +264 -0
  59. package/src/parse.ts +71 -0
  60. package/src/plan-seed.ts +599 -0
  61. package/src/price/fetch.ts +146 -0
  62. package/src/price/index.ts +8 -0
  63. package/src/price/resolve.ts +213 -0
  64. package/src/report/cli.ts +92 -0
  65. package/src/report/format.ts +37 -0
  66. package/src/report/index.ts +4 -0
  67. package/src/report/render.ts +206 -0
  68. package/src/review-seed.ts +932 -0
  69. package/src/safety.ts +18 -0
  70. package/src/session/activeSession.ts +153 -0
  71. package/src/session/claude-code.ts +412 -0
  72. package/src/session/codex.ts +347 -0
  73. package/src/session/findModel.ts +376 -0
  74. package/src/session/helpers.ts +640 -0
  75. package/src/session/index.ts +31 -0
  76. package/src/session/opencode.ts +167 -0
  77. package/src/session/registry.ts +45 -0
  78. package/src/session/types.ts +128 -0
  79. package/src/session/zcode.ts +151 -0
  80. package/src/setup.ts +242 -0
  81. package/src/stats/cli.ts +44 -0
  82. package/src/stats/data.ts +1019 -0
  83. package/src/stats/format.ts +584 -0
  84. package/src/stats/index.ts +19 -0
  85. package/src/telemetry.ts +364 -0
  86. package/src/test.ts +2 -0
  87. package/src/update.ts +212 -0
  88. package/src/usage/cache.ts +125 -0
  89. package/src/usage/cli.ts +120 -0
  90. package/src/usage/format.ts +29 -0
  91. package/src/usage/index.ts +4 -0
  92. package/src/usage/render.ts +523 -0
  93. package/src/usage/scan.ts +161 -0
  94. package/src/util.ts +32 -0
  95. package/src/web/html.ts +33 -0
  96. package/templates/PLAN.md +90 -0
  97. package/templates/SPEC.md +30 -0
  98. package/templates/mem/commands/plan.ts +360 -0
  99. package/templates/mem/commands/read.ts +194 -0
  100. package/templates/mem/commands/rotate.ts +59 -0
  101. package/templates/mem/commands/selftest.ts +450 -0
  102. package/templates/mem/commands/write.ts +214 -0
  103. package/templates/mem/mem.ts +68 -0
  104. package/templates/mem/render.ts +63 -0
  105. package/templates/mem/selectors.ts +144 -0
  106. package/templates/mem/store.ts +285 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 delamind
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,473 @@
1
+ <p align="center">
2
+ <img src="images/logo@400.webp" width="400" alt="fapony logo">
3
+ </p>
4
+
5
+ # fapony
6
+
7
+ **Where did your tokens go?** fapony reads the session logs Claude Code, Codex, OpenCode and ZCode
8
+ already write, and puts them all on one yardstick — tokens, cost and time per model, per client,
9
+ per workflow. Nothing to instrument, no per-project setup, no waiting for data to accumulate: it
10
+ runs on the history already sitting on your disk.
11
+
12
+ <p align="center">
13
+ <img src="images/summary.webp" width="800" alt="fapony usage-web summary cards">
14
+ </p>
15
+
16
+ ```bash
17
+ fapony usage-scan # read the session logs already on your disk
18
+ fapony price-scan # fetch the price table (needed once, for cost)
19
+ fapony usage-web # every session you already have, all clients, one page
20
+ ```
21
+
22
+ **Cost is the part your client probably isn't logging.** Of the four, only OpenCode writes a real
23
+ dollar figure into its session log — Claude Code, ZCode and Codex record `0`. fapony prices those
24
+ sessions at published list rates and labels the number `imputed`, so a figure you can compare
25
+ across clients exists at all. A model it can't find a rate for stays `unpriced`: nothing is
26
+ quietly counted as free.
27
+
28
+ <details>
29
+ <summary>full usage-web dashboard preview</summary>
30
+
31
+ <p align="center">
32
+ <img src="images/sample.webp" width="800" alt="fapony usage-web dashboard">
33
+ </p>
34
+
35
+ </details>
36
+
37
+ That is day one. Past that, fapony measures what coding agents actually do — rounds, pass/fail,
38
+ cost per grade — through 6 MCP tools any agent can call. If you juggle more than one agent, this is
39
+ the point: the numbers come from the same yardstick everywhere, so "which model earns its keep on
40
+ which kind of task" becomes a data question instead of a vibe. On top of measurement it checks
41
+ claims against git facts: handoff conformance, allowlisted evidence, a 6-grade verdict — with
42
+ everything the agent claimed but couldn't prove marked as such.
43
+
44
+ **What that question looks like answered, from one project's own ledger (52 graded `code`-regime
45
+ runs, `fapony stats --mode verdict --regime code`):**
46
+
47
+ | model | tokens/pass | quality | n |
48
+ |---|---|---|---|
49
+ | `claude-opus-5` | 22.5M | 3.8 | 10 |
50
+ | `claude-sonnet-5` | 5.6M | 3.5 | 11 |
51
+ | `muse-spark-1.3-contributor-free` | 4.3M | 4.0 | 5 |
52
+
53
+ Same quality band, an 8× token spread — the kind of answer a session log can't give (it has tokens,
54
+ no grades) and a benchmark can't give either (it has grades, not your codebase). One caveat that's
55
+ on you to hold: work isn't randomly assigned to models, so a gap this size is a strong prior, not a
56
+ controlled trial — you likely route easy tasks to the cheap model already. `n≥5` is fapony's own
57
+ floor before a model counts toward the frontier at all; below that it's a data point, not a pick.
58
+
59
+ **The reason to keep it running is the third layer: knowledge accumulation.** Any single client already logs its own session — timing, tokens, tool calls. What none of them see is *across* runs, clients and task shapes: which model earns its keep on which kind of work **in this project**, at what token cost, graded by whoever reviewed it. Every verdict carries a `regime` (`code` / `fix` / `review` / `plan` / `inquiry` / `test`), and runs split by whether there was a plan at all — so "does planning beat diving in, and for which model" is a table, not an argument. Session logs have the tokens but no grades; benchmarks have grades but not your codebase. fapony is the one layer that holds both, because it's the one every client reports into.
60
+
61
+ Three tiers, deliberately: **measurement ships today** and needs no per-project setup — raw facts nobody can call unfair. **Verification is the sharper edge** but stays beta until its evidence layer is hardened; fapony doesn't control your agent's flow, so it never promises "verified" as a headline. **Knowledge accumulation is the compounding one** — it's worthless on run 1 and gets more useful every run after, which is exactly why it's the layer competitors can't clone by copying a feature list.
62
+
63
+ Adopting it doesn't change your workflow. There is no loop to join and no framework to learn: install the MCP server, point your agent at it, and read the reports. fapony also ships plans, skills and read-only seed commands from its own dogfooding — those are conveniences, kept in their own section below, and deleting all of them costs you nothing the ledger can measure.
64
+
65
+ ## What fapony is not
66
+
67
+ Stated up front, because the gap between these two things is where most tooling oversells:
68
+
69
+ - **It does not run your test suite.** The evidence collector runs an allowlist *you* write in
70
+ `.fapony/evidence.json`, and never a command an agent proposes. No allowlist, no evidence — and
71
+ the report says `not_run` rather than staying quiet.
72
+ - **It does not judge your code.** `verdict_submit` *stores* a verdict; a human or a reviewing
73
+ agent supplies it. fapony is the ledger, not the judge.
74
+ - **`handoff_check` checks conformance, not correctness.** It verifies that what the agent claimed
75
+ lines up with git facts and that it declared its uncertainty — not that the code works. Those are
76
+ different guarantees and fapony only offers the first.
77
+ - **Nothing blocks.** There is no gate, no hook, no CI failure. Forget to call it and you are back
78
+ to exactly the workflow you had.
79
+ - **Model attribution is inferred, not declared.** A gate is attributed to whichever client
80
+ session was live in that worktree at that moment. When one model writes the code and another
81
+ reviews and files the verdict, the grade lands on the reviewer. Reports label it `inferred`;
82
+ read it as such.
83
+ - **The knowledge layer is empty on run 1.** It is worth something around run 5 and more every run
84
+ after. That is the trade for it being the layer nobody can clone from a feature list.
85
+
86
+ ## Quick start (MCP)
87
+
88
+ ```bash
89
+ # 1. Install (Bun is the only runtime dependency — fapony itself has zero packages)
90
+ git clone https://github.com/kire21b/fapony.git && cd fapony
91
+ bun install
92
+ bun link # puts `fapony` on your PATH; or run via `bun fapony.ts`
93
+ # note: `bun link` claims the global `fapony` bin by package name, not path — running it
94
+ # from a second checkout silently repoints the command there. Re-run it in the one you want.
95
+
96
+ # 2. Wire it into your MCP client
97
+ fapony install # detects installed clients, asks which to wire
98
+ fapony install --all # skip the prompt, wire everything detected
99
+ # use --platform <name> to force a specific client (bypasses detection)
100
+ # zcode/codex need their config to exist first — open the app once if you never have
101
+ # claude/opencode also symlink skill/<name>/ into ~/.claude/skills — an existing
102
+ # skill of the same name is reported, never overwritten
103
+ # …or add it manually to any MCP client (e.g. Claude Desktop):
104
+ # { "mcpServers": { "fapony": { "command": "fapony", "args": ["mcp"] } } }
105
+
106
+ # 3. Measure — zero per-project setup
107
+ fapony usage-scan # scan the session logs already on disk → cache
108
+ fapony price-scan # fetch the OpenRouter price table → ~/.config/fapony/prices.json
109
+ fapony usage-web # dashboard; re-run the scans to refresh
110
+ # both scans are manual by design — nothing fetches or re-reads session logs behind your back
111
+ # ask your agent: "Run fapony_stats and fapony_usage — what has it cost me, per model?"
112
+
113
+ # 4. Verify (optional, per project) — scaffold the evidence allowlist
114
+ fapony init /path/to/your-worktree
115
+ # .fapony/evidence.json lists the commands the evidence collector may run —
116
+ # edit the placeholder cmds to your real test/typecheck commands
117
+ # commit it: the allowlist is a security boundary your whole team shares.
118
+ # If your .gitignore ignores .fapony/ wholesale, re-include it (dir before file):
119
+ # **/.fapony/*
120
+ # !**/.fapony/evidence.json
121
+ # Monorepo: give an app its own apps/<app>/.fapony/evidence.json and reports whose
122
+ # changed files all sit under that app use it; anything else uses the root one.
123
+ ```
124
+
125
+ With `.fapony/evidence.json` in place, any graded run can be replayed as a report. This one is
126
+ a CLI command, not an MCP tool — the schemas cost every session of every client and no skill
127
+ called them (see [The 6 tools](#the-6-tools) below). Grade something first;
128
+ `verdict_submit` is what creates the run:
129
+
130
+ ```bash
131
+ fapony report <run-id> # run ids come from `fapony stats`
132
+ ```
133
+
134
+ You get one report: git facts (files, commits, branch), handoff conformance (claims vs. reality), evidence from the allowlisted commands (pass/fail/timeout/unverified), a 6-grade verdict, and cost — with anything the agent claimed but couldn't prove marked as such.
135
+
136
+ Sections that have nothing to report say so (`not_run`, `unavailable`) rather than disappearing — a report with no evidence must not read like a report that passed.
137
+
138
+ Two things worth knowing about the report header and budget:
139
+
140
+ - **`server_sha`** — every report is stamped with the git SHA of the fapony code that produced it, read once at server start. MCP servers are long-lived: after you edit fapony and don't restart the client, reports keep coming from the old build. Compare the stamp against `git log -1` in the fapony repo; if they differ, reconnect the server before trusting the result.
141
+ - **Evidence budget** — each allowlisted command gets `timeout_ms` (default 30s), and the whole report is capped at 180s total. A command that doesn't fit is reported as `timeout`, never as a pass. Time your real suite and set `timeout_ms` accordingly.
142
+
143
+ ## How it fits
144
+
145
+ ```mermaid
146
+ flowchart LR
147
+ A[Claude Code] --> F[fapony MCP]
148
+ B[OpenCode] --> F
149
+ C[ZCode] --> F
150
+ D[Codex] --> F
151
+ F --> G[git facts + session logs]
152
+ G --> S[stats / usage]
153
+ G --> V[verification report]
154
+ G --> P[project_health - optional]
155
+ ```
156
+
157
+ fapony never drives the agent. It sits on two sides of your work that never touch each
158
+ other, and **the rest of this README is organised along that line**: the ledger below is the
159
+ product, and everything under "The work side" after it is a convenience you can delete without
160
+ losing a single number.
161
+
162
+ | | The ledger | The work side |
163
+ |---|---|---|
164
+ | What it is | 6 MCP tools + a SQLite ledger | plans, skills, read-only seed commands |
165
+ | Needs | an MCP client | nothing — or your own tooling instead |
166
+ | Writes | one graded row per unit of work | nothing |
167
+ | Skip it and | there is no fapony | fapony still answers every question |
168
+
169
+ ## The ledger — this is the product
170
+
171
+ One habit feeds it: grade a unit of work when it ends. Everything else on this page is
172
+ optional around that. `verdict_submit` needs no plan file, no skill and no `.fapony/`
173
+ directory — any agent that speaks MCP can call it, and calling it is what turns a pile of
174
+ session logs into an answer.
175
+
176
+ ### One turn, end to end
177
+
178
+ ```mermaid
179
+ sequenceDiagram
180
+ autonumber
181
+ participant A as Any MCP client
182
+ participant F as fapony MCP
183
+ participant L as ~/.config/fapony/state.db
184
+
185
+ Note over A,F: end a turn with a commit and no grade → the Stop hook blocks it once
186
+ A->>F: verdict_submit (grade + regime + reason_code + note)
187
+ F->>L: one graded unit of work, stamped with the model that did it
188
+ opt proof, not just a claim — CLI, once the run exists
189
+ A->>F: fapony report <run-id>
190
+ F-->>A: git facts + evidence from .fapony/evidence.json, stamped with server_sha
191
+ end
192
+ A->>F: fapony_stats
193
+ F->>L: read across every run, client and project
194
+ L-->>A: model x regime x quality — which model to pay for this shape
195
+ ```
196
+
197
+ The Stop hook is the only thing fapony does *to* you — once per turn, when a commit ends
198
+ ungraded. It never picks the grade; it cannot see whether the work held up.
199
+
200
+ ### The 6 tools
201
+
202
+ ```
203
+ discover: plan_list (plans grouped by state, joined with their run history)
204
+ measure: fapony_stats ── fapony_usage
205
+ verify: verdict_submit
206
+ recall: project_health_context (what failed in these files before — optional, never required)
207
+ mem_find (what was ever decided about these files — reads the project's mem log)
208
+ ```
209
+
210
+ | Tool | Tier | Purpose |
211
+ |------|------|---------|
212
+ | `plan_list` | discover | Plan files grouped by state — active / blocked / untouched / superseded / trackers — with a progress tally and each one's run history. Not a raw `ls`; see [Plans your agent can answer questions about](#plans-your-agent-can-answer-questions-about) |
213
+ | `fapony_stats` | measure | KPIs across runs: by-model (gates, fail rate, quality, tokens), by-grade, planned vs dove-in, regime x model, per-file risk; `group_by: reason_code\|plan\|file` for top-N slices; `mode: verdict` ranks models by quality vs tokens/pass instead of listing raw counts |
214
+ | `fapony_usage` | measure | Passive usage from OpenCode, ZCode, Claude Code, and Codex sessions (tokens, cost, by-model; `detail:true` adds per-step timing) |
215
+ | `verdict_submit` | verify | Store a 6-grade verdict (pass-excellent → uncertain) with a required `regime` — the task shape the grade applies to |
216
+ | `project_health_context` | recall | Known-patterns block for the files you are about to touch. Useful when a file does have history; measured across real repos, most do not (1-9% of shipped files come back under a `fix:` within two weeks), so it is optional — never a precondition for editing |
217
+ | `mem_find` | recall | Search the project's mem log read-only — decisions/bugs/notes keyed by `files[]`, `text`, `kind` (no default filter), `since`. "What was ever decided about this file?" in one call before editing |
218
+
219
+ The handoff/report family is CLI-only — the schemas cost every session of every client and no skill called them. `fapony report <run-id>` prints the full report for a run (facts + handoff conformance + evidence + verdict); `fapony report-web [file]` renders it as a static HTML page (overwrites `file` on every call — safe to reuse the same path). Run `bun run overview` for a one-shot shortcut that writes it to `/tmp/fapony-overview.html` and opens it. `fapony usage-scan` scans session logs and writes a cache file; `fapony usage-web [port]` serves a static HTML dashboard from that cache (no live scanning). Run `fapony usage-scan` periodically to keep data fresh.
220
+
221
+ Full protocol, adapter examples (bash, Python), and safety rules: [docs/mcp-handcheck.md](docs/mcp-handcheck.md).
222
+
223
+ ### Verdict grades
224
+
225
+ Verification produces a quality grade, not just pass/fail:
226
+
227
+ | Grade | Meaning |
228
+ |-------|---------|
229
+ | `pass-excellent` | Ship-quality, no issues |
230
+ | `pass-good` | Minor nits, safe to ship |
231
+ | `pass-adequate` | Works, but could be better |
232
+ | `pass` | Meets minimum bar |
233
+ | `fail` | Needs fixes |
234
+ | `uncertain` | Reviewer can't judge — plan may have a problem |
235
+
236
+ ### Why measure from the outside
237
+
238
+ - **Raw facts are hard to argue with.** Cost, rounds, diff sizes, pass rates — collected from git and session logs, not self-reported. A vendor can dispute a verdict as unfair; they can't dispute their own token count.
239
+ - **Agent platforms grading their own homework is a conflict of interest.** fapony is a separate layer that measures any agent the same way, which is what makes "model X vs. model Y" or "workflow A vs. workflow B" answerable with real data instead of vibes.
240
+ - **Verification stays honest about its limits.** The collector runs only commands listed in `.fapony/evidence.json`; commands proposed by the agent outside the allowlist are reported as *proposed — not executed*, never run. And because fapony doesn't control your agent's flow, verdicts are labeled as one signal — not promised as truth.
241
+
242
+ ## The work side — conveniences, not the contract
243
+
244
+ Read-only, deterministic, and none of it writes to the ledger. These exist because they were
245
+ useful in this project's own dogfooding; use them, use your client's own search, or use
246
+ neither. **Nothing here is a precondition for anything in the section above.**
247
+
248
+ ### A lookup instead of a file read
249
+
250
+ ```mermaid
251
+ sequenceDiagram
252
+ autonumber
253
+ participant A as You + your agent
254
+ participant F as fapony CLI (read-only)
255
+ participant W as your worktree
256
+
257
+ A->>F: review-seed --files src/thing/
258
+ F->>W: static scan — exports, importers, untested
259
+ W-->>F: facts, no LLM in the middle
260
+ F-->>A: the lines worth reading, instead of the whole files
261
+ A->>W: build, then commit
262
+ Note over A,F: nothing is stored — skip this side entirely and fapony still works
263
+ ```
264
+
265
+ `review-seed --files` takes file names or a directory and answers "what is in here, who
266
+ imports it, what is untested" for roughly a thirtieth of the tokens reading those files costs.
267
+ That is the whole trick; there is no model in the middle.
268
+
269
+ ### Skills
270
+
271
+ fapony ships five portable skills, each as `skill/<name>/SKILL.md` — the layout Claude
272
+ Code expects, so a client can symlink the directory rather than copy the file:
273
+
274
+ | Skill | Purpose | Trigger |
275
+ |-------|---------|---------|
276
+ | `skill/plan-with-pony/` | Draft plan + spec from "what's in your head" via conversation | `/plan-with-pony` |
277
+ | `skill/review-pony/` | Review as verification, wired to fapony: scope facts before (`review-seed`), verdict after | `/review-pony` |
278
+ | `skill/move-to-done/` | Archive a shipped PLAN into .fapony/done/ | `/move-to-done` |
279
+ | `skill/git-commit-conventional/` | Commit split by concern + conventional message | `/git-commit` |
280
+ | `skill/git-ship/` | Push branch, open PR with drafted title/body, merge, reset branch onto base | `/ship`, `/pr` |
281
+
282
+ ### When to call what
283
+
284
+ ```mermaid
285
+ flowchart TD
286
+ I([idea]) --> Q{does it outlive<br/>this session?}
287
+ Q -->|"feature, several days"| P["/plan-with-pony<br/>PLAN.md + SPEC.md"]
288
+ Q -->|"wire · refactor · fix"| Z["fapony analyze DIR<br/>fapony review-seed --files"]
289
+ P --> W[you and your agent build]
290
+ Z --> W
291
+ W --> C["/git-commit"]
292
+ C --> R["/review-pony"]
293
+ R -->|findings| W
294
+ R -->|clean| S["/git-ship"]
295
+ S -->|"there was a PLAN.md"| D["/move-to-done"]
296
+ D -.-> H[(fapony ledger)]
297
+ R -.-> H
298
+ C -.->|"Stop hook: a commit needs a verdict"| H
299
+ H -.->|"which model for this shape"| Q
300
+
301
+ style H fill:#2d333b,stroke:#768390,color:#adbac7
302
+ ```
303
+
304
+ **The fork at the top is load-bearing.** A plan file is an artifact for work the next session has
305
+ to pick up. Wiring, refactors and UI passes finish in one sitting and the PLAN.md gets archived
306
+ unread — so `/plan-with-pony` declines those itself and hands over the two seed commands instead.
307
+ `fapony review-seed --files` takes a directory as well as file names, and answers "what is in
308
+ here, who imports it, what is untested" for about a thirtieth of the tokens reading those files
309
+ costs. Both arms meet at the same review and the same ledger.
310
+
311
+ **The dotted edges are the whole point.** Verdicts carry `regime` and `reason_code`, so the
312
+ ledger can answer the one question no single client can: *in this project, which model is worth
313
+ paying for this shape of work.* That is what flows back to the fork — not "this file broke once",
314
+ which fapony measured at a 1–9% base rate and demoted.
315
+
316
+ | Moment | Call | What fapony gets out of it |
317
+ |---|---|---|
318
+ | Starting anything | `/plan-with-pony` | decides plan-vs-seed, then reads back how this shape has gone |
319
+ | Before editing an unfamiliar file | `fapony review-seed --files` | nothing; it saves you reading the file |
320
+ | Before committing | `/git-commit` | nothing; it just keeps commits reviewable |
321
+ | Before merging | `/review-pony` | writes a verdict + `reason_code` + `regime` + note |
322
+ | Merging | `/git-ship` (`pr` / `land` on a team) | nothing; pure git plumbing |
323
+ | After it ships | `/move-to-done` | writes the ship verdict, closes the loop |
324
+ | Proving a finished run | `fapony report <run-id>` (CLI, not MCP) | git facts + allowlisted evidence, one page |
325
+
326
+ **Team flow.** `/git-ship pr` stops once the PR is open and hands you the URL; the reviewer does
327
+ their pass; `/git-ship land` merges it after approval. If the default branch requires reviews,
328
+ plain `/git-ship` detects that and behaves like `pr` on its own.
329
+
330
+ **What this is not.** It doesn't reduce your token bill — an agent that plans against known
331
+ failure patterns tends to spend fewer rounds getting there, but fapony measures that, it doesn't
332
+ cause it. Use `fapony_usage` to find out whether it actually happened for you rather than taking
333
+ the claim on faith.
334
+
335
+ `fapony install --platform claude` (or `opencode`) symlinks these directories into
336
+ `~/.claude/skills` rather than copying them, so `fapony update` refreshes every client
337
+ at once. A destination that already exists and isn't a fapony link is reported and left
338
+ alone — replace it by hand if you want fapony's version.
339
+
340
+ `plan-with-pony` is vendor-neutral — the SKILL.md *is* the prompt, so pipe it to any agent:
341
+
342
+ ```bash
343
+ cat skill/plan-with-pony/SKILL.md | claude -p # Claude Code
344
+ cat skill/plan-with-pony/SKILL.md | opencode run # OpenCode
345
+ cat skill/plan-with-pony/SKILL.md | <your-agent> # anything that reads stdin
346
+ ```
347
+
348
+ Example plans produced by it live in [examples/](examples/).
349
+
350
+ ### Plans your agent can answer questions about
351
+
352
+ Plans stay markdown files in your repo — nothing moves into a database. Four optional
353
+ frontmatter keys are enough to make a folder of them queryable:
354
+
355
+ ```yaml
356
+ ---
357
+ kind: unit # tracker = a checklist that never finishes
358
+ status: blocked # active | blocked | superseded
359
+ blocked_by: PLAN-documents.md # a plan, or a sentence
360
+ blocks: PLAN-export.md # ordering, stated once instead of buried in prose
361
+ ---
362
+
363
+ # PLAN — month view in /quick
364
+
365
+ ## TL;DR # 15 lines; the only part that changes mid-flight
366
+ - **Why:** two menu entries for the same data at different granularity
367
+ - [x] chunk 1 — month grid `a1b2c3` 2026-09-13
368
+ - [ ] chunk 2 — move overdue out
369
+ ```
370
+
371
+ Then ask your agent *"what's left, and what's blocked?"* — `plan_list` answers from the
372
+ frontmatter and from fapony's own run history, without reading a single 100KB plan body into
373
+ context (`format: "markdown"`):
374
+
375
+ ```
376
+ ## active — in order (2)
377
+ - [ ] PLAN-calendar — 1/3 · unblocks PLAN-export
378
+ - [ ] PLAN-export — never attempted
379
+ ## blocked (1)
380
+ - [ ] PLAN-attendance — waiting: PLAN-documents.md
381
+ ## untouched (14) · trackers (3)
382
+ done: 63 archived
383
+ ```
384
+
385
+ **Plans with no frontmatter still work** — they are grouped by run history alone (attempted =
386
+ active, never attempted = untouched), so an existing folder of plans is queryable before anyone
387
+ annotates anything. Two details that keep it honest over years:
388
+
389
+ - The progress tally counts checkboxes in the **first `##` section only**, anchored by position
390
+ rather than by the word "TL;DR" — so it works in any language, and a step list deeper in the
391
+ file stays detail instead of becoming status.
392
+ - **There is no `MASTER.md`.** Every line of the list above is derived from frontmatter and
393
+ checkboxes, so it cannot drift; a hand-kept master file always does.
394
+
395
+ The layout, and why archiving is a plain `git mv`:
396
+
397
+ ```
398
+ .fapony/plan/PLAN-calendar.md live
399
+ .fapony/done/PLAN-calendar.md shipped — same name, same depth, so every relative
400
+ link inside the file survives the move untouched
401
+ .fapony/spec/SPEC-calendar.md specs are a reference library; they are never archived
402
+ ```
403
+
404
+ Ship dates live in the plan's own header (`> ✅ **shipped 2026-09-13** (a1b2c3)`), not in the
405
+ filename — `grep -h shipped .fapony/done/*.md | sort` answers "what landed when" without paying
406
+ to rewrite every inbound link on every ship. Example plans, including an un-annotated one and an
407
+ archived one: [examples/](examples/).
408
+
409
+ ## CLI
410
+
411
+ ```bash
412
+ # Verification & reporting
413
+ fapony mcp # MCP server (stdio JSON-RPC — 6 tools)
414
+ fapony report <run-id> # verification report for a run
415
+ fapony report-web [file] # static HTML report page
416
+ fapony usage-scan # scan session logs → cache (incremental, progress bar)
417
+ fapony price-scan # fetch model price table → prices.json (cache; query never fetches)
418
+ fapony usage-web [port] # live usage comparison dashboard from cache
419
+ fapony stats [--mode verdict [--regime code|fix|review|plan|inquiry|test]] # KPIs: pass/stall rate, by-model, by-grade — --mode verdict ranks by quality/tokens instead
420
+ fapony digest [--since 7d|YYYY-MM-DD] [--format text|html] [--json] [--out FILE] # single-page summary: decisions, open bugs, in-flight plans, cost, pass/fail — from what's already on disk
421
+ fapony plan-seed <name> [--spec] [--scope <path>]... # write PLAN (+SPEC): frontmatter, 8 empty sections, prior-art list, ledger context; SPEC chunks carry signatures, every section capped — the agent fills the judgment
422
+ fapony review-seed [--staged|--commit <sha>|--range <a...b>|--files f1,f2,dir|--plan <PLAN.md>] # read-only scope facts for a review (changed files, importers, untested, signatures, plan cross-check)
423
+
424
+ # Setup & maintenance
425
+ fapony init <path> # scaffold .fapony/ (plan/spec/memory/evidence)
426
+ fapony init-mem [--update] # refresh the memory scaffold from the template
427
+ fapony install # detect installed clients, prompt to wire each
428
+ fapony install --all # wire all detected clients without prompting
429
+ fapony install --platform <name> # force a specific client (bypasses detection)
430
+ fapony install --dry-run # show what would happen without writing files
431
+ fapony setup # interactive wizard: config + scaffold in one step
432
+ fapony update # self-update via git pull
433
+ fapony telemetry show|send # opt-in only, default off — see TELEMETRY.md
434
+ fapony test # self-check
435
+ ```
436
+
437
+ ## Config
438
+
439
+ `fapony.config.json` lives in the fapony checkout and is gitignored (it's per-machine). Copy [fapony.config.example.json](fapony.config.example.json) for a complete working reference; every section is optional with sane defaults. Key fields:
440
+
441
+ - `worktrees` — name → absolute path mapping
442
+ - `review.maxRounds` — round cap enforced by the gate
443
+ - `memory` — shell commands for claim/close/add/kickoff, or `null` to default-wire when `.fapony/.memory/mem.ts` exists
444
+ - `paths` (`planDir`/`doneDir`/`specDir`/`memoryEntry`/`stateDir`) / `safety` — directory layout and the dangerous-command deny-list
445
+ - `usageWeb` — optional `{ port, hostname }` for `fapony usage-web` server defaults. Run `fapony usage-scan` first to populate the cache.
446
+
447
+ Env overrides: `FAPONY_CONFIG` (config file), `FAPONY_STATE_DIR` (state DB location; default `~/.config/fapony/`). Full schema, design decisions, and edge cases are documented in [CLAUDE.md](CLAUDE.md) — this README intentionally doesn't duplicate them.
448
+
449
+ ## Scope
450
+
451
+ **Supported:**
452
+ - MCP server — 6 tools via stdio JSON-RPC, works with any MCP client
453
+ - Measurement: cross-run KPIs by model/grade/value, per-file risk (graded touches vs. fails) + passive usage (tokens, cost)
454
+ - Model attribution across clients — resolved from the session log that was live when the verdict landed, so a verdict carries a model without the caller declaring one
455
+ - Zero setup beyond install: the two habits fapony depends on ship in the MCP `initialize` response, not in your rules file
456
+ - Verification (beta): handoff conformance, 6-grade verdicts, allowlisted evidence collector (`.fapony/evidence.json` — agent-proposed commands are never executed); reports stamped with the producing build's `server_sha`
457
+ - Vendor-neutral executor/reviewer roles — anything that reads stdin
458
+ - Memory integration via shell adapter, per project (configurable or default-wired)
459
+ - Opt-in telemetry, off by default ([TELEMETRY.md](TELEMETRY.md) lists exactly what leaves the machine)
460
+ - Bun-only; run state in SQLite via `bun:sqlite` (WAL mode)
461
+
462
+ **Not supported (yet):**
463
+ - A hosted or shared ledger for a team — `runs.worktree` is the only sharing key today, and it's a
464
+ path, not an identity. If you want to try pointing two machines at the same ledger anyway,
465
+ `FAPONY_STATE_DIR` can be set to a synced folder (Syncthing, a shared drive) — but SQLite's WAL
466
+ mode does not tolerate concurrent writers over most network filesystems (NFS, Dropbox, iCloud
467
+ Drive) and can corrupt the db under real contention. Treat this as an experiment you're accepting
468
+ the risk on, not a supported path; nothing here is a substitute for a real shared-ledger server.
469
+ - Memory migration from `.fapony/.memory/log.jsonl`
470
+
471
+ ## License
472
+
473
+ MIT
package/fapony.ts ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // fapony — measure/verify MCP server for coding agents
4
+ // CLI dispatch: all logic lives in src/
5
+
6
+ import { cmdAnalyze } from "./src/analyze.js";
7
+ import { cmdDebt } from "./src/debt.js";
8
+ import { cmdDigest } from "./src/digest/cli.js";
9
+ import { cmdHookReadHint, cmdHookStop } from "./src/hook.js";
10
+ import { cmdInit } from "./src/init.js";
11
+ import { cmdInitMem } from "./src/init-mem.js";
12
+ import { cmdInstall } from "./src/install.js";
13
+ import { cmdLintBaseline } from "./src/lint-baseline.js";
14
+ import { cmdMcp } from "./src/mcp/transport.js";
15
+ import { cmdPlanSeed } from "./src/plan-seed.js";
16
+ import { cmdPriceScan } from "./src/price/index.js";
17
+ import { cmdReport, cmdReportWeb } from "./src/report/index.js";
18
+ import { cmdReviewSeed } from "./src/review-seed.js";
19
+ import { cmdSetup } from "./src/setup.js";
20
+ import { cmdStats } from "./src/stats/index.js";
21
+ import { cmdTelemetry } from "./src/telemetry.js";
22
+ import { cmdTest } from "./src/test.js";
23
+ import { cmdUpdate } from "./src/update.js";
24
+ import { cmdUsageScan, cmdUsageWeb } from "./src/usage/index.js";
25
+
26
+ const [cmd, ...a] = process.argv.slice(2);
27
+
28
+ if (cmd === "analyze") {
29
+ cmdAnalyze(a);
30
+ } else if (cmd === "debt") {
31
+ cmdDebt(a);
32
+ } else if (cmd === "lint-baseline") {
33
+ cmdLintBaseline(a);
34
+ } else if (cmd === "plan-seed") {
35
+ cmdPlanSeed(a);
36
+ } else if (cmd === "review-seed") {
37
+ cmdReviewSeed(a);
38
+ } else if (cmd === "digest") {
39
+ await cmdDigest(a);
40
+ } else if (cmd === "stats") {
41
+ cmdStats(a);
42
+ } else if (cmd === "telemetry") {
43
+ await cmdTelemetry(a);
44
+ } else if (cmd === "init-mem") {
45
+ cmdInitMem(a);
46
+ } else if (cmd === "init") {
47
+ await cmdInit(a);
48
+ } else if (cmd === "install") {
49
+ await cmdInstall(a);
50
+ } else if (cmd === "setup") {
51
+ await cmdSetup();
52
+ } else if (cmd === "update") {
53
+ await cmdUpdate();
54
+ } else if (cmd === "hook-stop") {
55
+ await cmdHookStop();
56
+ } else if (cmd === "hook-read-hint") {
57
+ await cmdHookReadHint();
58
+ } else if (cmd === "mcp") {
59
+ cmdMcp();
60
+ } else if (cmd === "report") {
61
+ cmdReport(a);
62
+ } else if (cmd === "report-web") {
63
+ cmdReportWeb(a);
64
+ } else if (cmd === "usage-scan") {
65
+ cmdUsageScan(a);
66
+ } else if (cmd === "price-scan") {
67
+ await cmdPriceScan(a);
68
+ } else if (cmd === "usage-web") {
69
+ cmdUsageWeb(a);
70
+ } else if (cmd === "test") {
71
+ await cmdTest();
72
+ } else {
73
+ console.error(`fapony: unknown command "${cmd ?? ""}"`);
74
+ console.error(
75
+ "usage: fapony <setup|update|stats|telemetry|init|init-mem|install|report|report-web|usage-scan|usage-web|price-scan|analyze|debt|lint-baseline|plan-seed|review-seed|digest|mcp|hook-stop|hook-read-hint|test> [args]",
76
+ );
77
+ process.exit(1);
78
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "fapony",
3
+ "version": "0.1.0",
4
+ "description": "Measurement layer for coding agents — measure what agents do, verify what they claim. 6 MCP tools, any agent, no loop required",
5
+ "license": "MIT",
6
+ "author": "delamind (https://github.com/kire21b)",
7
+ "homepage": "https://github.com/kire21b/fapony#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kire21b/fapony.git"
11
+ },
12
+ "keywords": [
13
+ "mcp",
14
+ "mcp-server",
15
+ "agent",
16
+ "claude-code",
17
+ "opencode",
18
+ "coding-agent"
19
+ ],
20
+ "bin": {
21
+ "fapony": "./fapony.ts"
22
+ },
23
+ "files": [
24
+ "fapony.ts",
25
+ "src/",
26
+ "templates/",
27
+ "skill/"
28
+ ],
29
+ "devDependencies": {
30
+ "@biomejs/biome": "^2.5.12",
31
+ "bun-types": "^1.4.0",
32
+ "typescript": "^5.9.3"
33
+ },
34
+ "scripts": {
35
+ "lint": "biome check .",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "bun fapony.ts test",
38
+ "test:fast": "SKIP_SLOW=1 bun fapony.ts test",
39
+ "check": "bun run lint && bun run typecheck && bun fapony.ts test",
40
+ "overview": "bun fapony.ts report-web /tmp/fapony-overview.html && open /tmp/fapony-overview.html"
41
+ }
42
+ }