fapony 0.1.2 → 0.2.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/README.md +94 -77
- package/fapony.ts +32 -1
- package/images/logo.png +0 -0
- package/images/logo.webp +0 -0
- package/images/logo@400.webp +0 -0
- package/images/sample.webp +0 -0
- package/images/summary.webp +0 -0
- package/package.json +5 -3
- package/skill/move-to-done/SKILL.md +18 -28
- package/skill/plan-with-pony/SKILL.md +8 -3
- package/src/analyze.ts +13 -1
- package/src/conventions-seed.ts +0 -1
- package/src/db/defaults.ts +1 -1
- package/src/db/getters.ts +3 -3
- package/src/db/store.ts +0 -75
- package/src/db/types.ts +1 -1
- package/src/debt.ts +169 -34
- package/src/digest/collect.ts +16 -0
- package/src/digest/text.ts +25 -0
- package/src/hook.ts +424 -16
- package/src/init-mem.ts +123 -37
- package/src/init.ts +51 -34
- package/src/install/claude.ts +7 -5
- package/src/install/opencode.ts +59 -5
- package/src/lint-baseline.ts +1 -7
- package/src/mcp/evidence.ts +1 -1
- package/src/mcp/tools/index.ts +97 -185
- package/src/mcp/tools/mem.ts +153 -11
- package/src/mcp/transport.ts +5 -13
- package/src/mem/commands/read.ts +448 -0
- package/src/mem/commands/where.ts +56 -0
- package/{templates → src}/mem/commands/write.ts +12 -5
- package/src/mem/index.ts +144 -0
- package/src/mem/store.ts +348 -0
- package/src/memory.ts +237 -40
- package/src/plan-seed.ts +20 -1
- package/src/review-seed.ts +19 -0
- package/src/setup.ts +4 -6
- package/src/stats/data.ts +34 -86
- package/src/stats/format.ts +8 -9
- package/src/stats/index.ts +0 -1
- package/templates/PLAN.md +1 -0
- package/src/mcp/tools/context.ts +0 -66
- package/src/mcp/tools/plans.ts +0 -255
- package/src/mcp/tools/stats.ts +0 -96
- package/templates/mem/commands/read.ts +0 -194
- package/templates/mem/commands/selftest.ts +0 -450
- package/templates/mem/mem.ts +0 -68
- package/templates/mem/store.ts +0 -285
- /package/{templates → src}/mem/commands/plan.ts +0 -0
- /package/{templates → src}/mem/commands/rotate.ts +0 -0
- /package/{templates → src}/mem/render.ts +0 -0
- /package/{templates → src}/mem/selectors.ts +0 -0
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
# fapony
|
|
6
6
|
|
|
7
|
+
[](https://www.npmjs.com/package/fapony)
|
|
8
|
+
|
|
7
9
|
**Where did your tokens go?** fapony reads the session logs Claude Code, Codex, OpenCode and ZCode
|
|
8
10
|
already write, and puts them all on one yardstick — tokens, cost and time per model, per client,
|
|
9
11
|
per workflow. Nothing to instrument, no per-project setup, no waiting for data to accumulate: it
|
|
@@ -35,14 +37,14 @@ quietly counted as free.
|
|
|
35
37
|
</details>
|
|
36
38
|
|
|
37
39
|
That is day one. Past that, fapony measures what coding agents actually do — rounds, pass/fail,
|
|
38
|
-
cost per grade — through
|
|
40
|
+
cost per grade — through 4 MCP tools any agent can call. If you juggle more than one agent, this is
|
|
39
41
|
the point: the numbers come from the same yardstick everywhere, so "which model earns its keep on
|
|
40
42
|
which kind of task" becomes a data question instead of a vibe. On top of measurement it checks
|
|
41
43
|
claims against git facts: handoff conformance, allowlisted evidence, a 6-grade verdict — with
|
|
42
44
|
everything the agent claimed but couldn't prove marked as such.
|
|
43
45
|
|
|
44
|
-
**What that question looks like answered, from one project's own ledger
|
|
45
|
-
|
|
46
|
+
**What that question looks like answered, from one project's own ledger — the top of the `n≥5`
|
|
47
|
+
frontier (`fapony stats --mode verdict --regime code`):**
|
|
46
48
|
|
|
47
49
|
| model | tokens/pass | quality | n |
|
|
48
50
|
|---|---|---|---|
|
|
@@ -56,7 +58,19 @@ on you to hold: work isn't randomly assigned to models, so a gap this size is a
|
|
|
56
58
|
controlled trial — you likely route easy tasks to the cheap model already. `n≥5` is fapony's own
|
|
57
59
|
floor before a model counts toward the frontier at all; below that it's a data point, not a pick.
|
|
58
60
|
|
|
59
|
-
**The reason to keep it running is the third layer: knowledge accumulation
|
|
61
|
+
**The reason to keep it running is the third layer: knowledge accumulation — and the thing it
|
|
62
|
+
accumulates is pain.** An agent has no memory of pain across sessions: it writes the 37th
|
|
63
|
+
hand-rolled `try/catch` as cheerfully as the first, because every session starts new. Wrappers and
|
|
64
|
+
shared libraries get built by *people* who were hurt by the same thing often enough to remember.
|
|
65
|
+
That is why a codebase written with agents from day one tends not to grow a shared layer — nobody
|
|
66
|
+
in the room remembers. fapony is the part that remembers: graded verdicts and mem rows both carry
|
|
67
|
+
`files[]`, so the zones that keep coming back in failed and re-done work are a query, not a hunch.
|
|
68
|
+
Paired with `fapony debt`, which tracks how far the codebase has actually moved to a convention you
|
|
69
|
+
already decided on, that is the loop: notice the repeated cost, name the shared thing, watch the
|
|
70
|
+
migration finish. Finding dead code and duplication is *not* part of it — knip and friends already
|
|
71
|
+
do that better, and a convention with a `checker` is deliberately left to the checker.
|
|
72
|
+
|
|
73
|
+
**The measurement layer underneath it:** 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.
|
|
60
74
|
|
|
61
75
|
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
76
|
|
|
@@ -67,29 +81,29 @@ Adopting it doesn't change your workflow. There is no loop to join and no framew
|
|
|
67
81
|
Stated up front, because the gap between these two things is where most tooling oversells:
|
|
68
82
|
|
|
69
83
|
- **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
|
|
71
|
-
the report says `not_run` rather than staying quiet.
|
|
84
|
+
`.fapony/evidence.json`, and never a command an agent proposes. No allowlist, no evidence.
|
|
72
85
|
- **It does not judge your code.** `verdict_submit` *stores* a verdict; a human or a reviewing
|
|
73
86
|
agent supplies it. fapony is the ledger, not the judge.
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
- **
|
|
78
|
-
|
|
87
|
+
- **It checks conformance, not correctness.** What it can verify is that a claim lines up with git
|
|
88
|
+
facts and that uncertainty was declared — not that the code works. Those are different
|
|
89
|
+
guarantees and fapony only offers the first.
|
|
90
|
+
- **Almost nothing blocks.** No CI failure, no gate on your own commands. The one exception is the
|
|
91
|
+
Stop hook, once per turn when a commit ends ungraded; the read hint only annotates. Skip the
|
|
92
|
+
install of both and you are back to exactly the workflow you had.
|
|
79
93
|
- **Model attribution is inferred, not declared.** A gate is attributed to whichever client
|
|
80
94
|
session was live in that worktree at that moment. When one model writes the code and another
|
|
81
95
|
reviews and files the verdict, the grade lands on the reviewer. Reports label it `inferred`;
|
|
82
96
|
read it as such.
|
|
83
97
|
- **The knowledge layer is empty on run 1.** It is worth something around run 5 and more every run
|
|
84
|
-
after.
|
|
98
|
+
after.
|
|
85
99
|
|
|
86
100
|
## Quick start (MCP)
|
|
87
101
|
|
|
88
102
|
```bash
|
|
89
103
|
# 1. Install (needs Bun — https://bun.sh)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
npm install -g fapony # or: bun add -g fapony
|
|
105
|
+
# from source instead:
|
|
106
|
+
# git clone https://github.com/kire21b/fapony.git && cd fapony && bun install && bun link
|
|
93
107
|
# note: `bun link` claims the global `fapony` bin by package name, not path — running it
|
|
94
108
|
# from a second checkout silently repoints the command there. Re-run it in the one you want.
|
|
95
109
|
|
|
@@ -108,23 +122,16 @@ fapony usage-scan # scan the session logs already on dis
|
|
|
108
122
|
fapony price-scan # fetch the OpenRouter price table → ~/.config/fapony/prices.json
|
|
109
123
|
fapony usage-web # dashboard; re-run the scans to refresh
|
|
110
124
|
# both scans are manual by design — nothing fetches or re-reads session logs behind your back
|
|
111
|
-
# ask your agent: "Run
|
|
125
|
+
# ask your agent: "Run fapony_usage — what has it cost me, per model?"
|
|
112
126
|
|
|
113
127
|
# 4. Verify (optional, per project) — scaffold the evidence allowlist
|
|
114
128
|
fapony init /path/to/your-worktree
|
|
115
|
-
# .fapony/evidence.json
|
|
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.
|
|
129
|
+
# edit .fapony/evidence.json to your real test/typecheck commands, then commit it
|
|
123
130
|
```
|
|
124
131
|
|
|
125
132
|
With `.fapony/evidence.json` in place, any graded run can be replayed as a report. This one is
|
|
126
133
|
a CLI command, not an MCP tool — the schemas cost every session of every client and no skill
|
|
127
|
-
called them (see [The
|
|
134
|
+
called them (see [The 4 tools](#the-4-tools) below). Grade something first;
|
|
128
135
|
`verdict_submit` is what creates the run:
|
|
129
136
|
|
|
130
137
|
```bash
|
|
@@ -135,6 +142,8 @@ You get one report: git facts (files, commits, branch), handoff conformance (cla
|
|
|
135
142
|
|
|
136
143
|
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
144
|
|
|
145
|
+
Two details for the allowlist once it is under version control. If your `.gitignore` ignores `.fapony/` wholesale, re-include the file (dir before file): `**/.fapony/*`, then `!**/.fapony/evidence.json`. In a monorepo, give an app its own `apps/<app>/.fapony/evidence.json` — reports whose changed files all sit under that app use it; anything else uses the root one.
|
|
146
|
+
|
|
138
147
|
Two things worth knowing about the report header and budget:
|
|
139
148
|
|
|
140
149
|
- **`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.
|
|
@@ -151,7 +160,7 @@ flowchart LR
|
|
|
151
160
|
F --> G[git facts + session logs]
|
|
152
161
|
G --> S[stats / usage]
|
|
153
162
|
G --> V[verification report]
|
|
154
|
-
G -->
|
|
163
|
+
G --> M[mem log - what was decided here]
|
|
155
164
|
```
|
|
156
165
|
|
|
157
166
|
fapony never drives the agent. It sits on two sides of your work that never touch each
|
|
@@ -161,7 +170,7 @@ losing a single number.
|
|
|
161
170
|
|
|
162
171
|
| | The ledger | The work side |
|
|
163
172
|
|---|---|---|
|
|
164
|
-
| What it is |
|
|
173
|
+
| What it is | 4 MCP tools + a SQLite ledger | plans, skills, read-only seed commands |
|
|
165
174
|
| Needs | an MCP client | nothing — or your own tooling instead |
|
|
166
175
|
| Writes | one graded row per unit of work | nothing |
|
|
167
176
|
| Skip it and | there is no fapony | fapony still answers every question |
|
|
@@ -189,36 +198,34 @@ sequenceDiagram
|
|
|
189
198
|
A->>F: fapony report <run-id>
|
|
190
199
|
F-->>A: git facts + evidence from .fapony/evidence.json, stamped with server_sha
|
|
191
200
|
end
|
|
192
|
-
A
|
|
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
|
|
201
|
+
Note over A,L: `fapony stats` reads it back — CLI, because you ask it, not the agent
|
|
195
202
|
```
|
|
196
203
|
|
|
197
|
-
The Stop hook is the only thing fapony
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
204
|
+
The Stop hook is the only thing fapony *blocks* — once per turn, when a commit ends ungraded.
|
|
205
|
+
It never picks the grade; it cannot see whether the work held up. The Read hook only annotates:
|
|
206
|
+
one factual line when a read is large enough to be cheaper as `review-seed`, or when the same
|
|
207
|
+
file is read again in a session and its mtime has not moved. The read always proceeds, and
|
|
208
|
+
`FAPONY_NO_REREAD_HINT=1` turns the re-read line off.
|
|
201
209
|
|
|
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
|
-
```
|
|
210
|
+
### The 4 tools
|
|
209
211
|
|
|
210
212
|
| Tool | Tier | Purpose |
|
|
211
213
|
|------|------|---------|
|
|
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
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
215
|
| `verdict_submit` | verify | Store a 6-grade verdict (pass-excellent → uncertain) with a required `regime` — the task shape the grade applies to |
|
|
216
|
-
| `
|
|
217
|
-
| `
|
|
216
|
+
| `mem_find` | recall | Search the project's mem log read-only — decisions/bugs/notes matched on the row's `files[]` (text substring for rows written without it), `text`, `kind` (no default filter), `since`. "What was ever decided about this file?" in one call before editing |
|
|
217
|
+
| `mem_add` | recall | Append a mem row (decision/bug/note/next/hold) with `files[]` required and rejected when empty — the write half of `mem_find`, so the row is findable when you next touch that file |
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
**A tool earns its schema by being called mid-task without being asked.** Everything you invoke
|
|
220
|
+
deliberately is a CLI command instead: the schema is paid as input tokens in every session of
|
|
221
|
+
every client whether or not it is used, while a CLI command costs nothing until it runs. That is
|
|
222
|
+
why the handoff/report family is CLI-only, and why `fapony_stats`, `project_health_context` and
|
|
223
|
+
`plan_list` left the MCP surface in 2026-09 (`fapony stats` answers the first, `fapony mem
|
|
224
|
+
kickoff` the third; the second had no caller).
|
|
225
|
+
Cutting is not the goal — spending where it pays back is: `mem_find` and `verdict_submit` keep
|
|
226
|
+
their schemas because nobody is going to type them at the right moment. `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
227
|
|
|
221
|
-
Full protocol, adapter examples (bash, Python), and safety rules: [docs/mcp-handcheck.md](docs/mcp-handcheck.md).
|
|
228
|
+
Full protocol, adapter examples (bash, Python), and safety rules: [docs/mcp-handcheck.md](https://github.com/kire21b/fapony/blob/main/docs/mcp-handcheck.md).
|
|
222
229
|
|
|
223
230
|
### Verdict grades
|
|
224
231
|
|
|
@@ -304,9 +311,7 @@ flowchart TD
|
|
|
304
311
|
**The fork at the top is load-bearing.** A plan file is an artifact for work the next session has
|
|
305
312
|
to pick up. Wiring, refactors and UI passes finish in one sitting and the PLAN.md gets archived
|
|
306
313
|
unread — so `/plan-with-pony` declines those itself and hands over the two seed commands instead.
|
|
307
|
-
|
|
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.
|
|
314
|
+
Both arms meet at the same review and the same ledger.
|
|
310
315
|
|
|
311
316
|
**The dotted edges are the whole point.** Verdicts carry `regime` and `reason_code`, so the
|
|
312
317
|
ledger can answer the one question no single client can: *in this project, which model is worth
|
|
@@ -345,7 +350,7 @@ cat skill/plan-with-pony/SKILL.md | opencode run # OpenCode
|
|
|
345
350
|
cat skill/plan-with-pony/SKILL.md | <your-agent> # anything that reads stdin
|
|
346
351
|
```
|
|
347
352
|
|
|
348
|
-
Example plans produced by it live in [examples/](examples
|
|
353
|
+
Example plans produced by it live in [examples/](https://github.com/kire21b/fapony/tree/main/examples).
|
|
349
354
|
|
|
350
355
|
### Plans your agent can answer questions about
|
|
351
356
|
|
|
@@ -368,29 +373,31 @@ blocks: PLAN-export.md # ordering, stated once instead of buried in pros
|
|
|
368
373
|
- [ ] chunk 2 — move overdue out
|
|
369
374
|
```
|
|
370
375
|
|
|
371
|
-
Then
|
|
372
|
-
|
|
373
|
-
|
|
376
|
+
Then open the next session with `fapony mem kickoff` — it reads the folder and the mem log and
|
|
377
|
+
prints what is next (priority plans, the first unchecked chunk of each, open bugs) without
|
|
378
|
+
reading a single 100KB plan body into context:
|
|
374
379
|
|
|
375
380
|
```
|
|
376
|
-
##
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
## untouched (14) · trackers (3)
|
|
382
|
-
done: 63 archived
|
|
381
|
+
## next up
|
|
382
|
+
[1] chunk 2 — move overdue out (PLAN-calendar.md)
|
|
383
|
+
[2] bug #mu8t5qve — money drifts in the month grid…
|
|
384
|
+
→ fapony mem close mu8t5qve "<msg>"
|
|
385
|
+
[3] last touched: src/quick/month.tsx, src/lib/money.ts
|
|
383
386
|
```
|
|
384
387
|
|
|
385
|
-
**Plans with no frontmatter still work** —
|
|
386
|
-
|
|
387
|
-
|
|
388
|
+
**Plans with no frontmatter still work** — the unchecked checkboxes are enough, so an existing
|
|
389
|
+
folder of plans is usable before anyone annotates anything. Two details that keep it honest over
|
|
390
|
+
years:
|
|
388
391
|
|
|
389
392
|
- The progress tally counts checkboxes in the **first `##` section only**, anchored by position
|
|
390
393
|
rather than by the word "TL;DR" — so it works in any language, and a step list deeper in the
|
|
391
394
|
file stays detail instead of becoming status.
|
|
392
|
-
- **There is no `MASTER.md`.** Every line
|
|
393
|
-
|
|
395
|
+
- **There is no `MASTER.md`.** Every line above is derived from the plan files themselves, so it
|
|
396
|
+
cannot drift; a hand-kept master file always does.
|
|
397
|
+
|
|
398
|
+
`status` / `blocked_by` / `blocks` / `superseded_by` are read by people, not by a tool — the one
|
|
399
|
+
that read them, `plan_list`, was removed in 2026-09 once `mem kickoff` answered the same
|
|
400
|
+
question from the CLI, where a schema costs nothing until it runs.
|
|
394
401
|
|
|
395
402
|
The layout, and why archiving is a plain `git mv`:
|
|
396
403
|
|
|
@@ -404,13 +411,13 @@ The layout, and why archiving is a plain `git mv`:
|
|
|
404
411
|
Ship dates live in the plan's own header (`> ✅ **shipped 2026-09-13** (a1b2c3)`), not in the
|
|
405
412
|
filename — `grep -h shipped .fapony/done/*.md | sort` answers "what landed when" without paying
|
|
406
413
|
to rewrite every inbound link on every ship. Example plans, including an un-annotated one and an
|
|
407
|
-
archived one: [examples/](examples
|
|
414
|
+
archived one: [examples/](https://github.com/kire21b/fapony/tree/main/examples).
|
|
408
415
|
|
|
409
416
|
## CLI
|
|
410
417
|
|
|
411
418
|
```bash
|
|
412
419
|
# Verification & reporting
|
|
413
|
-
fapony mcp # MCP server (stdio JSON-RPC —
|
|
420
|
+
fapony mcp # MCP server (stdio JSON-RPC — 4 tools)
|
|
414
421
|
fapony report <run-id> # verification report for a run
|
|
415
422
|
fapony report-web [file] # static HTML report page
|
|
416
423
|
fapony usage-scan # scan session logs → cache (incremental, progress bar)
|
|
@@ -421,42 +428,52 @@ fapony digest [--since 7d|YYYY-MM-DD] [--format text|html] [--json] [--out FILE]
|
|
|
421
428
|
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
429
|
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
430
|
|
|
431
|
+
# Memory & convention debt
|
|
432
|
+
fapony mem add <kind> "<text>" --files f1,f2 [spec.md] # append a mem row (decision/bug/note/next/hold)
|
|
433
|
+
fapony mem close <id> "<msg>" # close a bug
|
|
434
|
+
fapony mem find "<text>" # substring-search every row
|
|
435
|
+
fapony mem kickoff [<plan.md>] # open a session + a next-up list
|
|
436
|
+
fapony mem where # show the resolved mem dir and which step won
|
|
437
|
+
fapony mem now | done | stale # views
|
|
438
|
+
fapony debt [--id <convention>] [--where <path>] # ไฟล์ไหนยังไม่ย้ายไป convention ที่ประกาศไว้ (live, read-only)
|
|
439
|
+
fapony lint-baseline [--cmd ...] [--diff] # separate "already red" from "I made it red"
|
|
440
|
+
|
|
424
441
|
# Setup & maintenance
|
|
425
442
|
fapony init <path> # scaffold .fapony/ (plan/spec/memory/evidence)
|
|
426
|
-
fapony init-mem
|
|
443
|
+
fapony init-mem # delete .memory/ + warn call sites still referencing it
|
|
427
444
|
fapony install # detect installed clients, prompt to wire each
|
|
428
445
|
fapony install --all # wire all detected clients without prompting
|
|
429
446
|
fapony install --platform <name> # force a specific client (bypasses detection)
|
|
430
447
|
fapony install --dry-run # show what would happen without writing files
|
|
431
448
|
fapony setup # interactive wizard: config + scaffold in one step
|
|
432
449
|
fapony update # self-update via git pull
|
|
433
|
-
fapony telemetry show|send # opt-in only, default off — see TELEMETRY.md
|
|
450
|
+
fapony telemetry show|send # opt-in only, default off — see https://github.com/kire21b/fapony/blob/main/TELEMETRY.md
|
|
434
451
|
fapony test # self-check
|
|
435
452
|
```
|
|
436
453
|
|
|
437
454
|
## Config
|
|
438
455
|
|
|
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:
|
|
456
|
+
`fapony.config.json` lives in the fapony checkout and is gitignored (it's per-machine). Copy [fapony.config.example.json](https://github.com/kire21b/fapony/blob/main/fapony.config.example.json) for a complete working reference; every section is optional with sane defaults. Key fields:
|
|
440
457
|
|
|
441
458
|
- `worktrees` — name → absolute path mapping
|
|
442
459
|
- `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
|
|
444
|
-
- `paths` (`planDir`/`doneDir`/`specDir`/`
|
|
460
|
+
- `memory` — shell commands for claim/close/add/kickoff, or `null` to default-wire when a `.fapony/.memory/` dir exists
|
|
461
|
+
- `paths` (`planDir`/`doneDir`/`specDir`/`memDir`/`stateDir`) / `safety` — directory layout and the dangerous-command deny-list
|
|
445
462
|
- `usageWeb` — optional `{ port, hostname }` for `fapony usage-web` server defaults. Run `fapony usage-scan` first to populate the cache.
|
|
446
463
|
|
|
447
|
-
Env overrides: `FAPONY_CONFIG` (config file), `FAPONY_STATE_DIR` (state DB location; default `~/.config/fapony/`). Full schema, design decisions, and edge cases
|
|
464
|
+
Env overrides: `FAPONY_CONFIG` (config file), `FAPONY_STATE_DIR` (state DB location; default `~/.config/fapony/`), `FAPONY_NO_REREAD_HINT=1` (turn the re-read hint off). Full schema, design decisions, and edge cases live with the code in the repo — this README intentionally doesn't duplicate them.
|
|
448
465
|
|
|
449
466
|
## Scope
|
|
450
467
|
|
|
451
468
|
**Supported:**
|
|
452
|
-
- MCP server —
|
|
469
|
+
- MCP server — 4 tools via stdio JSON-RPC, works with any MCP client
|
|
453
470
|
- Measurement: cross-run KPIs by model/grade/value, per-file risk (graded touches vs. fails) + passive usage (tokens, cost)
|
|
454
471
|
- 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
472
|
- 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`
|
|
473
|
+
- Verification (beta): handoff conformance, 6-grade verdicts, allowlisted evidence collector (`.fapony/evidence.json`); reports stamped with the producing build's `server_sha`
|
|
457
474
|
- Vendor-neutral executor/reviewer roles — anything that reads stdin
|
|
458
475
|
- 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)
|
|
476
|
+
- Opt-in telemetry, off by default ([TELEMETRY.md](https://github.com/kire21b/fapony/blob/main/TELEMETRY.md) lists exactly what leaves the machine)
|
|
460
477
|
- Bun-only; run state in SQLite via `bun:sqlite` (WAL mode)
|
|
461
478
|
|
|
462
479
|
**Not supported (yet):**
|
package/fapony.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// fapony — measure/verify MCP server for coding agents
|
|
4
4
|
// CLI dispatch: all logic lives in src/
|
|
5
5
|
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
6
7
|
import { cmdAnalyze } from "./src/analyze.js";
|
|
7
8
|
import { cmdDebt } from "./src/debt.js";
|
|
8
9
|
import { cmdDigest } from "./src/digest/cli.js";
|
|
@@ -12,6 +13,8 @@ import { cmdInitMem } from "./src/init-mem.js";
|
|
|
12
13
|
import { cmdInstall } from "./src/install.js";
|
|
13
14
|
import { cmdLintBaseline } from "./src/lint-baseline.js";
|
|
14
15
|
import { cmdMcp } from "./src/mcp/transport.js";
|
|
16
|
+
import { cmdMem } from "./src/mem/index.js";
|
|
17
|
+
import { initStore } from "./src/mem/store.js";
|
|
15
18
|
import { cmdPlanSeed } from "./src/plan-seed.js";
|
|
16
19
|
import { cmdPriceScan } from "./src/price/index.js";
|
|
17
20
|
import { cmdReport, cmdReportWeb } from "./src/report/index.js";
|
|
@@ -42,6 +45,34 @@ if (cmd === "analyze") {
|
|
|
42
45
|
await cmdTelemetry(a);
|
|
43
46
|
} else if (cmd === "init-mem") {
|
|
44
47
|
cmdInitMem(a);
|
|
48
|
+
} else if (cmd === "mem") {
|
|
49
|
+
// `--mem-dir <path>` is global to `mem` and must reach both the writer
|
|
50
|
+
// (initStore) and the resolver behind `mem where` — parse it once and thread
|
|
51
|
+
// it through, never strip it and forget.
|
|
52
|
+
const memDirIdx = a.indexOf("--mem-dir");
|
|
53
|
+
let overrideMemDir: string | undefined;
|
|
54
|
+
let rest = a;
|
|
55
|
+
if (memDirIdx !== -1) {
|
|
56
|
+
overrideMemDir = a[memDirIdx + 1];
|
|
57
|
+
if (!overrideMemDir || overrideMemDir.startsWith("--")) {
|
|
58
|
+
console.error("fapony mem: --mem-dir needs a value");
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
if (!existsSync(overrideMemDir)) {
|
|
62
|
+
console.error(
|
|
63
|
+
`fapony mem: --mem-dir path does not exist: ${overrideMemDir}`,
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
rest = a.filter((_, i) => i !== memDirIdx && i !== memDirIdx + 1);
|
|
68
|
+
}
|
|
69
|
+
initStore(process.cwd(), overrideMemDir);
|
|
70
|
+
try {
|
|
71
|
+
await cmdMem(rest, overrideMemDir);
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error(`fapony mem: ${e instanceof Error ? e.message : String(e)}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
45
76
|
} else if (cmd === "init") {
|
|
46
77
|
await cmdInit(a);
|
|
47
78
|
} else if (cmd === "install") {
|
|
@@ -75,7 +106,7 @@ if (cmd === "analyze") {
|
|
|
75
106
|
} else {
|
|
76
107
|
console.error(`fapony: unknown command "${cmd ?? ""}"`);
|
|
77
108
|
console.error(
|
|
78
|
-
"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]",
|
|
109
|
+
"usage: fapony <setup|update|stats|telemetry|init|init-mem|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]",
|
|
79
110
|
);
|
|
80
111
|
process.exit(1);
|
|
81
112
|
}
|
package/images/logo.png
ADDED
|
Binary file
|
package/images/logo.webp
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fapony",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Measurement layer for coding agents
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Measurement layer for coding agents \u2014 measure what agents do, verify what they claim. 4 MCP tools, any agent, no loop required",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "delamind (https://github.com/kire21b)",
|
|
7
7
|
"homepage": "https://github.com/kire21b/fapony#readme",
|
|
@@ -24,13 +24,15 @@
|
|
|
24
24
|
"fapony.ts",
|
|
25
25
|
"src/",
|
|
26
26
|
"templates/",
|
|
27
|
-
"skill/"
|
|
27
|
+
"skill/",
|
|
28
|
+
"images/"
|
|
28
29
|
],
|
|
29
30
|
"scripts": {
|
|
30
31
|
"lint": "biome check .",
|
|
31
32
|
"typecheck": "tsc --noEmit",
|
|
32
33
|
"test": "bun fapony.ts test",
|
|
33
34
|
"test:fast": "SKIP_SLOW=1 bun fapony.ts test",
|
|
35
|
+
"test:one": "bun scripts/test-one.ts",
|
|
34
36
|
"check": "bun run lint && bun run typecheck && bun fapony.ts test",
|
|
35
37
|
"prepublishOnly": "bash scripts/smoke-publish.sh",
|
|
36
38
|
"overview": "bun fapony.ts report-web /tmp/fapony-overview.html && open /tmp/fapony-overview.html"
|
|
@@ -36,28 +36,19 @@ You are about to move a PLAN that has been shipped to the archive.
|
|
|
36
36
|
|
|
37
37
|
A plan that is merely *waiting* (on a person, a customer, a decision) is **not** dead and does
|
|
38
38
|
not move — mark it `status: blocked` + `blocked_by: <what you are waiting for>` and leave it in
|
|
39
|
-
`plan
|
|
39
|
+
`plan/` — the frontmatter is for the next person reading the folder, and the plan stays out
|
|
40
|
+
of `done/`, which is what `plan-sweep` and `kickoff` go by.
|
|
40
41
|
|
|
41
|
-
2. **
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this one — a sibling reference becomes a `done/` one:
|
|
42
|
+
2. **Run `plan-sweep --apply`** — this does the `git mv`, rewrites markdown links inside the
|
|
43
|
+
file and inbound links from other plan files, warns about plain-text mentions, and logs
|
|
44
|
+
a decision row — all in one call:
|
|
45
45
|
```bash
|
|
46
|
-
|
|
47
|
-
git mv .fapony/plan/PLAN-foo.md .fapony/done/PLAN-foo.md # same name, same depth
|
|
48
|
-
# in .fapony/plan/*.md: (PLAN-foo.md) -> (../done/PLAN-foo.md)
|
|
46
|
+
fapony mem plan-sweep <PLAN-foo.md> --apply
|
|
49
47
|
```
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
on every ship, forever. "What shipped on which day" is a question to derive, not to store:
|
|
55
|
-
```bash
|
|
56
|
-
grep -h 'shipped' .fapony/done/*.md | sort
|
|
57
|
-
```
|
|
58
|
-
If git refuses ("not under version control" — `.fapony/` is gitignored in
|
|
59
|
-
this repo), plain `mv` instead; there's nothing to commit for an untracked path, so skip
|
|
60
|
-
step 4 in that case.
|
|
48
|
+
It refuses if the file lacks a shipped header or has open mem rows (next/bug/hold/decision/note).
|
|
49
|
+
If git refuses ("not under version control" — `.fapony/` is gitignored in this repo), plain
|
|
50
|
+
`mv` instead; there's nothing to commit for an untracked path, so skip step 4 in that case.
|
|
51
|
+
The filename gets no date prefix — the ship date is already in the header (step 1).
|
|
61
52
|
|
|
62
53
|
3. **Leave the spec where it is** — `.fapony/spec/` is a reference library, not a queue. A spec
|
|
63
54
|
answers "how does this work", which is asked long after the plan that ordered it shipped, and
|
|
@@ -80,15 +71,14 @@ You are about to move a PLAN that has been shipped to the archive.
|
|
|
80
71
|
`missing_test` / `scope_mismatch` / `unsafe_command` / `spec_gap` / `incomplete`, and
|
|
81
72
|
`other` (with a `note`, which it requires) only when a real finding fits none of them
|
|
82
73
|
- `note`: **omit it on a clean ship.** A verdict with no note still counts toward the plan
|
|
83
|
-
history future drafts read ("passed round 1 before"), but only notes
|
|
84
|
-
|
|
85
|
-
have taught the next session something. Write one only when this plan hit something a
|
|
74
|
+
history future drafts read ("passed round 1 before"), but only notes carry prose forward — so
|
|
75
|
+
"clean ship" evicts a note that would have taught the next session something. Write one only when this plan hit something a
|
|
86
76
|
reader could not get from the diff: what the symptom looked like, where the cause actually
|
|
87
77
|
was, and the rule that follows. Standalone prose — it is read months later with no access
|
|
88
78
|
to this conversation.
|
|
89
79
|
- `worktree`: **absolute path** to this repo/worktree (`git rev-parse --show-toplevel`) —
|
|
90
|
-
every other fapony tool
|
|
91
|
-
|
|
80
|
+
every other fapony tool and query scopes by
|
|
81
|
+
absolute path too; a bare repo name won't match them
|
|
92
82
|
- `plan`: the archived plan's path (post-move, e.g. `.fapony/done/PLAN-foo.md`)
|
|
93
83
|
- `files`: repo-relative paths this plan touched (`git diff --name-only <base>..HEAD`) —
|
|
94
84
|
the only input to per-file risk history; without it the verdict says something happened
|
|
@@ -101,8 +91,8 @@ You are about to move a PLAN that has been shipped to the archive.
|
|
|
101
91
|
Input: .fapony/plan/PLAN-kickoff.md, no shipped header yet
|
|
102
92
|
Steps:
|
|
103
93
|
1. stamp header: > ✅ **shipped 2026-09-13** (a1b2c3)
|
|
104
|
-
2.
|
|
105
|
-
|
|
94
|
+
2. fapony mem plan-sweep .fapony/plan/PLAN-kickoff.md --apply
|
|
95
|
+
→ moved, links rewritten, decision logged
|
|
106
96
|
3. spec: untouched, stays in .fapony/spec/
|
|
107
97
|
4. commit
|
|
108
98
|
5. verdict_submit(verdict="pass", reason_code="none", regime="code", worktree="/Users/you/Project/fapony/wt-fapony", plan=".fapony/done/PLAN-kickoff.md", files=["src/kickoff.ts"])
|
|
@@ -122,5 +112,5 @@ A ship worth a note looks like this instead:
|
|
|
122
112
|
|
|
123
113
|
- No git repo / no commits (can't derive a shipped hash) → tell user: "Add header > ✅ **shipped** (<hash>) first"
|
|
124
114
|
- Stamped the header yourself → always say which hash you used
|
|
125
|
-
-
|
|
126
|
-
- Too many inbound links →
|
|
115
|
+
- plan-sweep refuses (open mem rows) → close them or use `MEM_FORCE=1`
|
|
116
|
+
- Too many inbound links → plan-sweep reports them; too many to fix → report the list
|
|
@@ -106,6 +106,11 @@ So the seed buys you structure; the draft budget goes on judgment:
|
|
|
106
106
|
- **Run the Phase −1 commands for facts** when the idea needs them, and put the numbers in the
|
|
107
107
|
section they answer — a number you measured beats a number the seed guessed at.
|
|
108
108
|
- **Signatures live in the SPEC chunks only.** Never paste them into plan §7 — link to the spec.
|
|
109
|
+
- **Does this zone already owe a convention?** If the feature touches a directory, run
|
|
110
|
+
`fapony debt` once and read only the entries whose files overlap it. An open migration
|
|
111
|
+
("36 files still throw raw errors") is a constraint for §4, not a side quest — a plan that
|
|
112
|
+
adds the 37th is how the debt got there. Nothing overlaps, or no `conventions.json`? Say
|
|
113
|
+
nothing and move on.
|
|
109
114
|
- If the CLI is missing, skip silently and draft from scratch (Phase 2 as written) — never block
|
|
110
115
|
on a missing tool.
|
|
111
116
|
|
|
@@ -140,8 +145,8 @@ normal — writing to the default there scatters plans into a directory nobody r
|
|
|
140
145
|
by hand, this check is yours.)
|
|
141
146
|
|
|
142
147
|
**Editing a plan someone is executing right now is a different job from drafting one.** Ask the
|
|
143
|
-
dev, or
|
|
144
|
-
|
|
148
|
+
dev, or run `fapony mem kickoff` — it reads the same plan files and names the first unchecked
|
|
149
|
+
chunk, so a plan already in flight is the one you are about to edit under someone. When that is the case:
|
|
145
150
|
|
|
146
151
|
- **Anything you add is an instruction, not a note.** A measured fact parked under "don't do"
|
|
147
152
|
still reads as a to-do to an agent mid-execution — the numbers are what make it tempting.
|
|
@@ -180,7 +185,7 @@ only place that ordering stays true.
|
|
|
180
185
|
|
|
181
186
|
**The TL;DR is 15 lines, hard cap, and is the only part that changes while the work is in flight**
|
|
182
187
|
(tick a box, stamp a short sha). Everything below it is the agreement. A TL;DR allowed to grow
|
|
183
|
-
becomes a second copy of the plan, and then neither copy can be trusted. `
|
|
188
|
+
becomes a second copy of the plan, and then neither copy can be trusted. `fapony mem kickoff` reads the
|
|
184
189
|
checkboxes in the **first `##` section only**, so section 6 stays detail rather than status.
|
|
185
190
|
|
|
186
191
|
Section 6 — every step must be verifiable. Section 8 — must link back to anything it came from.
|
package/src/analyze.ts
CHANGED
|
@@ -132,7 +132,19 @@ export function isTestedThroughBarrels(
|
|
|
132
132
|
export const SCAN_EXTS = new Set([".ts", ".tsx", ".js", ".jsx"]);
|
|
133
133
|
|
|
134
134
|
// Always skipped, hardcoded — no config (per plan: no .faponyignore in v1).
|
|
135
|
-
|
|
135
|
+
// "templates" for the same reason knip.json ignores templates/**: those files
|
|
136
|
+
// ship as a template copied into other repos by `fapony init-mem` and never
|
|
137
|
+
// have real importers here — scanning them produces false wrapper/orphan
|
|
138
|
+
// signals (measured: conventions-seed flagged 8 "wrappers" that were all
|
|
139
|
+
// src/mem/commands/*.ts helpers matched against unrelated identically-
|
|
140
|
+
// named calls elsewhere in the repo, e.g. "cmdNow() instead of now(").
|
|
141
|
+
const SKIP_DIRS = new Set([
|
|
142
|
+
"node_modules",
|
|
143
|
+
"dist",
|
|
144
|
+
"build",
|
|
145
|
+
".git",
|
|
146
|
+
"templates",
|
|
147
|
+
]);
|
|
136
148
|
|
|
137
149
|
// A nested checkout (clone or `git worktree add`) is a different project that
|
|
138
150
|
// happens to live inside this one — walking it doubles the graph and makes every
|
package/src/conventions-seed.ts
CHANGED
package/src/db/defaults.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const DEFAULT_SPEC_DIR = ".fapony/spec";
|
|
|
11
11
|
// Archive sits beside plan/, not inside it, so archiving never changes a file's
|
|
12
12
|
// depth and its relative links survive the move untouched.
|
|
13
13
|
export const DEFAULT_DONE_DIR = ".fapony/done";
|
|
14
|
-
export const
|
|
14
|
+
export const DEFAULT_MEM_DIR = ".fapony/.memory";
|
|
15
15
|
export const DEFAULT_EVIDENCE_FILE = ".fapony/evidence.json";
|
|
16
16
|
|
|
17
17
|
export const DEFAULT_CONFIG: Config = {
|