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.
- package/LICENSE +21 -0
- package/README.md +473 -0
- package/fapony.ts +78 -0
- package/package.json +42 -0
- package/skill/git-commit-conventional/SKILL.md +68 -0
- package/skill/git-ship/SKILL.md +144 -0
- package/skill/move-to-done/SKILL.md +126 -0
- package/skill/plan-with-pony/SKILL.md +263 -0
- package/skill/review-pony/SKILL.md +254 -0
- package/src/analyze.ts +517 -0
- package/src/context/index.ts +11 -0
- package/src/context/projectHealth.ts +359 -0
- package/src/conventions-seed.ts +420 -0
- package/src/db/defaults.ts +26 -0
- package/src/db/getters.ts +33 -0
- package/src/db/index.ts +7 -0
- package/src/db/load.ts +57 -0
- package/src/db/store.ts +286 -0
- package/src/db/types.ts +79 -0
- package/src/debt.ts +667 -0
- package/src/digest/cli.ts +75 -0
- package/src/digest/collect.ts +625 -0
- package/src/digest/html.ts +208 -0
- package/src/digest/text.ts +191 -0
- package/src/gate.ts +153 -0
- package/src/gates.ts +194 -0
- package/src/hook.ts +436 -0
- package/src/init-mem.ts +71 -0
- package/src/init.ts +237 -0
- package/src/install/claude.ts +361 -0
- package/src/install/codex.ts +61 -0
- package/src/install/cursor.ts +167 -0
- package/src/install/detect.ts +78 -0
- package/src/install/opencode.ts +234 -0
- package/src/install/skills.ts +106 -0
- package/src/install/types.ts +69 -0
- package/src/install/utils.ts +29 -0
- package/src/install/zcode.ts +120 -0
- package/src/install.ts +176 -0
- package/src/lint-baseline.ts +260 -0
- package/src/map.ts +320 -0
- package/src/math.ts +13 -0
- package/src/mcp/evidence.ts +332 -0
- package/src/mcp/primitives.ts +316 -0
- package/src/mcp/tools/check.ts +243 -0
- package/src/mcp/tools/collect.ts +157 -0
- package/src/mcp/tools/context.ts +66 -0
- package/src/mcp/tools/index.ts +309 -0
- package/src/mcp/tools/mem.ts +95 -0
- package/src/mcp/tools/plans.ts +255 -0
- package/src/mcp/tools/report.ts +285 -0
- package/src/mcp/tools/stats.ts +96 -0
- package/src/mcp/tools/usage.ts +211 -0
- package/src/mcp/tools/verdict.ts +148 -0
- package/src/mcp/transport.ts +241 -0
- package/src/mcp/types.ts +54 -0
- package/src/mcp/worktree.ts +27 -0
- package/src/memory.ts +264 -0
- package/src/parse.ts +71 -0
- package/src/plan-seed.ts +599 -0
- package/src/price/fetch.ts +146 -0
- package/src/price/index.ts +8 -0
- package/src/price/resolve.ts +213 -0
- package/src/report/cli.ts +92 -0
- package/src/report/format.ts +37 -0
- package/src/report/index.ts +4 -0
- package/src/report/render.ts +206 -0
- package/src/review-seed.ts +932 -0
- package/src/safety.ts +18 -0
- package/src/session/activeSession.ts +153 -0
- package/src/session/claude-code.ts +412 -0
- package/src/session/codex.ts +347 -0
- package/src/session/findModel.ts +376 -0
- package/src/session/helpers.ts +640 -0
- package/src/session/index.ts +31 -0
- package/src/session/opencode.ts +167 -0
- package/src/session/registry.ts +45 -0
- package/src/session/types.ts +128 -0
- package/src/session/zcode.ts +151 -0
- package/src/setup.ts +242 -0
- package/src/stats/cli.ts +44 -0
- package/src/stats/data.ts +1019 -0
- package/src/stats/format.ts +584 -0
- package/src/stats/index.ts +19 -0
- package/src/telemetry.ts +364 -0
- package/src/test.ts +2 -0
- package/src/update.ts +212 -0
- package/src/usage/cache.ts +125 -0
- package/src/usage/cli.ts +120 -0
- package/src/usage/format.ts +29 -0
- package/src/usage/index.ts +4 -0
- package/src/usage/render.ts +523 -0
- package/src/usage/scan.ts +161 -0
- package/src/util.ts +32 -0
- package/src/web/html.ts +33 -0
- package/templates/PLAN.md +90 -0
- package/templates/SPEC.md +30 -0
- package/templates/mem/commands/plan.ts +360 -0
- package/templates/mem/commands/read.ts +194 -0
- package/templates/mem/commands/rotate.ts +59 -0
- package/templates/mem/commands/selftest.ts +450 -0
- package/templates/mem/commands/write.ts +214 -0
- package/templates/mem/mem.ts +68 -0
- package/templates/mem/render.ts +63 -0
- package/templates/mem/selectors.ts +144 -0
- 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
|
+
}
|