fapony 0.3.3 → 0.3.5
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 +7 -1
- package/fapony.ts +3 -116
- package/package.json +5 -6
- package/skill/move-to-done/SKILL.md +3 -2
- package/src/adapters/cli.ts +128 -0
- package/src/adapters/hooks/compute-hint-impact.ts +107 -0
- package/src/adapters/hooks/context-data.ts +102 -0
- package/src/adapters/hooks/edit-hint.ts +195 -0
- package/src/adapters/hooks/git-autonomy.ts +178 -0
- package/src/adapters/hooks/index.ts +80 -0
- package/src/adapters/hooks/mv-guard.ts +52 -0
- package/src/adapters/hooks/read-hint.ts +383 -0
- package/src/adapters/hooks/session-start.ts +101 -0
- package/src/adapters/hooks/stop.ts +434 -0
- package/src/{mcp → adapters/mcp}/evidence.ts +2 -2
- package/src/{mcp → adapters/mcp}/primitives.ts +2 -2
- package/src/{mcp → adapters/mcp}/tools/check.ts +1 -1
- package/src/{mcp → adapters/mcp}/tools/collect.ts +1 -1
- package/src/{mcp → adapters/mcp}/tools/mem.ts +3 -3
- package/src/{mcp → adapters/mcp}/tools/report.ts +4 -4
- package/src/adapters/mcp/types.ts +18 -0
- package/src/{mcp → adapters/mcp}/worktree.ts +1 -1
- package/src/analyze.ts +1 -1
- package/src/commands.ts +180 -0
- package/src/conventions-seed.ts +1 -1
- package/src/core/config.ts +199 -0
- package/src/core/debt-format.ts +107 -0
- package/src/core/debt-types.ts +79 -0
- package/src/core/defaults.ts +8 -0
- package/src/core/enums.ts +34 -0
- package/src/core/format.ts +33 -0
- package/src/core/hint-log.ts +68 -0
- package/src/core/hook-helpers.ts +31 -0
- package/src/core/mem-log.ts +357 -0
- package/src/core/parse.ts +71 -0
- package/src/core/pricing.ts +217 -0
- package/src/core/safety.ts +18 -0
- package/src/core/types.ts +142 -0
- package/src/core/util.ts +105 -0
- package/src/db/store.ts +2 -2
- package/src/debt/cli.ts +1 -1
- package/src/debt/format.ts +2 -107
- package/src/debt/load.ts +1 -1
- package/src/debt/promotion.ts +1 -1
- package/src/debt/types.ts +14 -79
- package/src/digest/collect.ts +4 -3
- package/src/gate.ts +2 -2
- package/src/gates.ts +1 -1
- package/src/hook.ts +70 -1424
- package/src/init-mem.ts +1 -1
- package/src/init.ts +1 -1
- package/src/install/claude.ts +17 -0
- package/src/install/opencode.ts +114 -0
- package/src/install.ts +12 -5
- package/src/lint-baseline.ts +1 -2
- package/src/map.ts +29 -8
- package/src/mem/commands/plan.ts +197 -42
- package/src/mem/commands/read.ts +53 -12
- package/src/mem/index.ts +60 -1
- package/src/mem/store.ts +5 -1
- package/src/memory.ts +26 -388
- package/src/parse.ts +9 -71
- package/src/price/fetch.ts +4 -16
- package/src/price/resolve.ts +12 -213
- package/src/report/cli.ts +3 -3
- package/src/safety.ts +2 -18
- package/src/seed/plan-seed.ts +126 -32
- package/src/seed/primitives.ts +1 -7
- package/src/session/types.ts +14 -128
- package/src/setup.ts +1 -1
- package/src/stats/data.ts +3 -3
- package/src/telemetry.ts +2 -2
- package/src/update.ts +1 -1
- package/src/usage/cache.ts +1 -2
- package/src/usage/cli.ts +1 -1
- package/src/usage/scan.ts +2 -1
- package/src/util.ts +10 -93
- package/src/web/html.ts +2 -33
- package/src/db/defaults.ts +0 -34
- package/src/db/getters.ts +0 -35
- package/src/db/index.ts +0 -7
- package/src/db/load.ts +0 -57
- package/src/db/types.ts +0 -77
- package/src/mcp/types.ts +0 -54
- package/src/test.ts +0 -2
- /package/src/{mcp → adapters/mcp}/tools/index.ts +0 -0
- /package/src/{mcp → adapters/mcp}/transport.ts +0 -0
package/src/init-mem.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import { existsSync, readdirSync, readFileSync, rmSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { DEFAULT_MEM_DIR, FAPONY_DIR, loadConfig } from "./
|
|
9
|
+
import { DEFAULT_MEM_DIR, FAPONY_DIR, loadConfig } from "./core/config.js";
|
|
10
10
|
import { walkDir } from "./util.js";
|
|
11
11
|
|
|
12
12
|
export function cmdInitMem(args: string[]): void {
|
package/src/init.ts
CHANGED
package/src/install/claude.ts
CHANGED
|
@@ -232,6 +232,7 @@ function installClaudeHooks(dryRun: boolean, deps: InstallDeps): void {
|
|
|
232
232
|
installStopHook(dryRun, deps);
|
|
233
233
|
installReadHintHook(dryRun, deps);
|
|
234
234
|
installEditHintHook(dryRun, deps);
|
|
235
|
+
installMvGuardHook(dryRun, deps);
|
|
235
236
|
installSessionStartHook(dryRun, deps);
|
|
236
237
|
}
|
|
237
238
|
|
|
@@ -276,6 +277,22 @@ function installEditHintHook(dryRun: boolean, deps: InstallDeps): void {
|
|
|
276
277
|
});
|
|
277
278
|
}
|
|
278
279
|
|
|
280
|
+
/**
|
|
281
|
+
* PreToolUse hook on Bash: denies a raw `git mv` of a plan file into a done/
|
|
282
|
+
* directory, pointing at `fapony mem plan-sweep --apply` instead — that
|
|
283
|
+
* command does the link rewrite a plain `git mv` skips. The one fapony hook
|
|
284
|
+
* that blocks besides Stop; matcher "Bash" keeps the spawn off every other
|
|
285
|
+
* tool call.
|
|
286
|
+
*/
|
|
287
|
+
function installMvGuardHook(dryRun: boolean, deps: InstallDeps): void {
|
|
288
|
+
ensureClaudeHook(dryRun, deps, {
|
|
289
|
+
event: "PreToolUse",
|
|
290
|
+
matcher: "Bash",
|
|
291
|
+
subcommand: "hook-mv-guard",
|
|
292
|
+
label: "plan-mv guard",
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
279
296
|
/**
|
|
280
297
|
* SessionStart hook: injects `fapony mem kickoff` as context when the repo has
|
|
281
298
|
* a mem log, and stays silent when it does not. Context only — SessionStart
|
package/src/install/opencode.ts
CHANGED
|
@@ -81,9 +81,15 @@ function isConfigured(mcp: Record<string, unknown> | undefined): boolean {
|
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
export interface OpencodeInstallOpts {
|
|
85
|
+
/** Opt-in only: write the git-autonomy rewrite plugin. Never default. */
|
|
86
|
+
gitAutonomy?: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
84
89
|
export function cmdInstallOpencode(
|
|
85
90
|
dryRun: boolean,
|
|
86
91
|
deps: InstallDeps = {},
|
|
92
|
+
opts: OpencodeInstallOpts = {},
|
|
87
93
|
): void {
|
|
88
94
|
const exitFn = deps.exit ?? defaultExit;
|
|
89
95
|
const getHome = deps.homedir ?? homedir;
|
|
@@ -115,6 +121,7 @@ export function cmdInstallOpencode(
|
|
|
115
121
|
installCommitHintPlugin(dryRun, getHome);
|
|
116
122
|
installEditHintPlugin(dryRun, getHome);
|
|
117
123
|
installSessionStartPlugin(dryRun, getHome);
|
|
124
|
+
if (opts.gitAutonomy) installGitAutonomyPlugin(dryRun, getHome);
|
|
118
125
|
return;
|
|
119
126
|
}
|
|
120
127
|
|
|
@@ -140,6 +147,7 @@ export function cmdInstallOpencode(
|
|
|
140
147
|
installCommitHintPlugin(dryRun, getHome);
|
|
141
148
|
installEditHintPlugin(dryRun, getHome);
|
|
142
149
|
installSessionStartPlugin(dryRun, getHome);
|
|
150
|
+
if (opts.gitAutonomy) installGitAutonomyPlugin(dryRun, getHome);
|
|
143
151
|
return;
|
|
144
152
|
}
|
|
145
153
|
|
|
@@ -163,6 +171,7 @@ export function cmdInstallOpencode(
|
|
|
163
171
|
installCommitHintPlugin(dryRun, getHome);
|
|
164
172
|
installEditHintPlugin(dryRun, getHome);
|
|
165
173
|
installSessionStartPlugin(dryRun, getHome);
|
|
174
|
+
if (opts.gitAutonomy) installGitAutonomyPlugin(dryRun, getHome);
|
|
166
175
|
}
|
|
167
176
|
|
|
168
177
|
/**
|
|
@@ -608,3 +617,108 @@ function installSessionStartPlugin(
|
|
|
608
617
|
` session start: ${dryRun ? "would write" : "wrote"} ${pluginPath}`,
|
|
609
618
|
);
|
|
610
619
|
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* OpenCode plugin — git-autonomy rewrites, the parity of the personal
|
|
623
|
+
* `git-autonomy.ts` plugin. Two hooks because the policy lives on two
|
|
624
|
+
* surfaces the other never sees:
|
|
625
|
+
* `experimental.chat.system.transform` (system prompt) + `tool.definition`
|
|
626
|
+
* (bash tool description).
|
|
627
|
+
*
|
|
628
|
+
* OPT-IN ONLY — written only when `fapony install` gets `--git-autonomy`.
|
|
629
|
+
* Never on the default path, never via `--all` unless the flag rides along.
|
|
630
|
+
* The policy is an opinion (commit-as-you-go), not a utility, and the file
|
|
631
|
+
* name is `fapony-git-autonomy.ts`: the user's own `git-autonomy.ts` is a
|
|
632
|
+
* different file and is never touched — delete it by hand after verifying.
|
|
633
|
+
*
|
|
634
|
+
* Logic lives in src/adapters/hooks/git-autonomy.ts
|
|
635
|
+
* (rewriteGitAutonomySystem / rewriteGitAutonomyTool) — the plugin imports
|
|
636
|
+
* it from the install root, so `git pull` updates every client. No baked
|
|
637
|
+
* regex in the generated body: a baked copy would rot silently when upstream
|
|
638
|
+
* rewords the policy, the same failure this module's gitAutonomyStatus
|
|
639
|
+
* exists to name. Never throws: a rewrite must not break a session start
|
|
640
|
+
* or hide a tool.
|
|
641
|
+
*/
|
|
642
|
+
export function gitAutonomyPluginSource(root: string): string {
|
|
643
|
+
const hookModule = JSON.stringify(join(root, "src", "hook.ts"));
|
|
644
|
+
return `// fapony git-autonomy — rewrites opencode's ask-first git policy to
|
|
645
|
+
// commit-as-you-go on both surfaces that carry it (system prompt +
|
|
646
|
+
// bash tool description), and cuts the tone few-shot examples.
|
|
647
|
+
// OPT-IN: written only by \`fapony install --platform opencode --git-autonomy\`.
|
|
648
|
+
// Generated by \`fapony install\` — logic lives in src/adapters/hooks/git-autonomy.ts.
|
|
649
|
+
import { rewriteGitAutonomySystem, rewriteGitAutonomyTool } from ${hookModule};
|
|
650
|
+
|
|
651
|
+
export const FaponyGitAutonomy = async () => ({
|
|
652
|
+
"experimental.chat.system.transform": async (
|
|
653
|
+
_input: unknown,
|
|
654
|
+
output: { system: string[] },
|
|
655
|
+
) => {
|
|
656
|
+
try {
|
|
657
|
+
for (let i = 0; i < output.system.length; i++) {
|
|
658
|
+
output.system[i] = rewriteGitAutonomySystem(output.system[i]).text;
|
|
659
|
+
}
|
|
660
|
+
} catch {
|
|
661
|
+
// a rewrite must never break a session start
|
|
662
|
+
}
|
|
663
|
+
},
|
|
664
|
+
"tool.definition": async (
|
|
665
|
+
input: { toolID: string },
|
|
666
|
+
output: { description: string; parameters: unknown },
|
|
667
|
+
) => {
|
|
668
|
+
try {
|
|
669
|
+
const r = rewriteGitAutonomyTool(input.toolID, output.description);
|
|
670
|
+
output.description = r.description;
|
|
671
|
+
} catch {
|
|
672
|
+
// a rewrite must never hide a tool
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
});
|
|
676
|
+
`;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function installGitAutonomyPlugin(
|
|
680
|
+
dryRun: boolean,
|
|
681
|
+
getHome: () => string,
|
|
682
|
+
): void {
|
|
683
|
+
const pluginsDir = join(getHome(), ".config", "opencode", "plugins");
|
|
684
|
+
const pluginPath = join(pluginsDir, "fapony-git-autonomy.ts");
|
|
685
|
+
if (existsSync(pluginPath)) {
|
|
686
|
+
let current = "";
|
|
687
|
+
try {
|
|
688
|
+
current = readFileSync(pluginPath, "utf-8");
|
|
689
|
+
} catch {
|
|
690
|
+
current = "";
|
|
691
|
+
}
|
|
692
|
+
if (current.includes("FaponyGitAutonomy")) {
|
|
693
|
+
// Ours — and generated, so never overwritten. Same stale-check shape
|
|
694
|
+
// as the session-start plugin: an older generation must say so instead
|
|
695
|
+
// of "no change", or a fixed rewrite never reaches the client.
|
|
696
|
+
if (current !== gitAutonomyPluginSource(INSTALL_ROOT)) {
|
|
697
|
+
console.error(
|
|
698
|
+
` git-autonomy: stale fapony plugin — delete ${pluginPath} and re-run install --git-autonomy`,
|
|
699
|
+
);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
console.error(` git-autonomy: already installed — no change`);
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
console.error(
|
|
706
|
+
` git-autonomy: ${pluginPath} exists but isn't fapony's — not overwriting.`,
|
|
707
|
+
);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
if (!dryRun) {
|
|
711
|
+
try {
|
|
712
|
+
mkdirSync(pluginsDir, { recursive: true });
|
|
713
|
+
writeFileSync(pluginPath, gitAutonomyPluginSource(INSTALL_ROOT), "utf-8");
|
|
714
|
+
} catch (e) {
|
|
715
|
+
console.error(
|
|
716
|
+
` git-autonomy: failed to write — ${(e as Error).message}`,
|
|
717
|
+
);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
console.error(
|
|
722
|
+
` git-autonomy: ${dryRun ? "would write" : "wrote"} ${pluginPath}`,
|
|
723
|
+
);
|
|
724
|
+
}
|
package/src/install.ts
CHANGED
|
@@ -37,6 +37,8 @@ export {
|
|
|
37
37
|
cmdInstallOpencode,
|
|
38
38
|
commitHintPluginSource,
|
|
39
39
|
editHintPluginSource,
|
|
40
|
+
gitAutonomyPluginSource,
|
|
41
|
+
type OpencodeInstallOpts,
|
|
40
42
|
readHintPluginSource,
|
|
41
43
|
} from "./install/opencode.js";
|
|
42
44
|
export {
|
|
@@ -58,6 +60,10 @@ export async function cmdInstall(
|
|
|
58
60
|
const platform = args.find((a) => !a.startsWith("--"));
|
|
59
61
|
const dryRun = args.includes("--dry-run");
|
|
60
62
|
const installAll = args.includes("--all");
|
|
63
|
+
// Opt-in only: the git-autonomy rewrite is an opinion (commit-as-you-go),
|
|
64
|
+
// not a utility — never installed unless this flag rides along, including
|
|
65
|
+
// via --all. Opencode-only; other platforms ignore it.
|
|
66
|
+
const gitAutonomy = args.includes("--git-autonomy");
|
|
61
67
|
|
|
62
68
|
// --- explicit platform: original behavior, unchanged ---
|
|
63
69
|
if (platform === "antigravity") {
|
|
@@ -81,12 +87,12 @@ export async function cmdInstall(
|
|
|
81
87
|
return;
|
|
82
88
|
}
|
|
83
89
|
if (platform === "opencode") {
|
|
84
|
-
cmdInstallOpencode(dryRun, deps);
|
|
90
|
+
cmdInstallOpencode(dryRun, deps, { gitAutonomy });
|
|
85
91
|
return;
|
|
86
92
|
}
|
|
87
93
|
if (platform !== undefined) {
|
|
88
94
|
console.error(
|
|
89
|
-
`usage: fapony install --platform antigravity|opencode|claude|cursor|zcode|codex [--dry-run]`,
|
|
95
|
+
`usage: fapony install --platform antigravity|opencode|claude|cursor|zcode|codex [--dry-run] [--git-autonomy]`,
|
|
90
96
|
);
|
|
91
97
|
console.error(
|
|
92
98
|
` supported platforms: antigravity, opencode, claude, cursor, zcode, codex`,
|
|
@@ -125,7 +131,7 @@ export async function cmdInstall(
|
|
|
125
131
|
if (installAll) {
|
|
126
132
|
console.error();
|
|
127
133
|
for (const client of found) {
|
|
128
|
-
installPlatform(client.platform, dryRun, deps);
|
|
134
|
+
installPlatform(client.platform, dryRun, deps, { gitAutonomy });
|
|
129
135
|
}
|
|
130
136
|
return;
|
|
131
137
|
}
|
|
@@ -154,7 +160,7 @@ export async function cmdInstall(
|
|
|
154
160
|
for (const client of found) {
|
|
155
161
|
const answer = await askFn(`install into ${client.platform}?`, "Y");
|
|
156
162
|
if (isAffirmative(answer)) {
|
|
157
|
-
installPlatform(client.platform, dryRun, deps);
|
|
163
|
+
installPlatform(client.platform, dryRun, deps, { gitAutonomy });
|
|
158
164
|
}
|
|
159
165
|
}
|
|
160
166
|
} finally {
|
|
@@ -166,6 +172,7 @@ function installPlatform(
|
|
|
166
172
|
platform: string,
|
|
167
173
|
dryRun: boolean,
|
|
168
174
|
deps: InstallDeps,
|
|
175
|
+
opts: { gitAutonomy?: boolean } = {},
|
|
169
176
|
): void {
|
|
170
177
|
switch (platform) {
|
|
171
178
|
case "antigravity":
|
|
@@ -178,7 +185,7 @@ function installPlatform(
|
|
|
178
185
|
cmdInstallCursor(dryRun, deps);
|
|
179
186
|
break;
|
|
180
187
|
case "opencode":
|
|
181
|
-
cmdInstallOpencode(dryRun, deps);
|
|
188
|
+
cmdInstallOpencode(dryRun, deps, opts);
|
|
182
189
|
break;
|
|
183
190
|
case "zcode":
|
|
184
191
|
cmdInstallZcode(dryRun, deps);
|
package/src/lint-baseline.ts
CHANGED
|
@@ -14,8 +14,7 @@
|
|
|
14
14
|
import { execSync } from "node:child_process";
|
|
15
15
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { join, relative } from "node:path";
|
|
17
|
-
import { DEFAULT_EVIDENCE_FILE } from "./
|
|
18
|
-
import { faponyDir } from "./db/load.js";
|
|
17
|
+
import { DEFAULT_EVIDENCE_FILE, faponyDir } from "./core/config.js";
|
|
19
18
|
import { assertSafe } from "./safety.js";
|
|
20
19
|
|
|
21
20
|
const USAGE =
|
package/src/map.ts
CHANGED
|
@@ -41,19 +41,36 @@ export interface ExportScan {
|
|
|
41
41
|
error: string | null;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// --- Parse gate
|
|
44
|
+
// --- Parse gate (injectable: default is Bun.Transpiler, no Node fallback —
|
|
45
|
+
// Bun-only is a repo constraint. Pass a custom scanner to extractExports()
|
|
46
|
+
// to reuse this module outside Bun or in tests.) ---
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
export interface ExportScanner {
|
|
49
|
+
scan(source: string): { exports: string[] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createBunScanner(): ExportScanner {
|
|
53
|
+
const transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
54
|
+
return {
|
|
55
|
+
scan: (source) => transpiler.scan(source) as { exports: string[] },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let defaultScanner: ExportScanner | null = null;
|
|
60
|
+
|
|
61
|
+
function getDefaultScanner(): ExportScanner {
|
|
62
|
+
if (!defaultScanner) defaultScanner = createBunScanner();
|
|
63
|
+
return defaultScanner;
|
|
64
|
+
}
|
|
47
65
|
|
|
48
66
|
interface ScanResult {
|
|
49
67
|
error: string | null;
|
|
50
68
|
exports: string[];
|
|
51
69
|
}
|
|
52
70
|
|
|
53
|
-
function scanSource(source: string): ScanResult {
|
|
71
|
+
function scanSource(source: string, scanner: ExportScanner): ScanResult {
|
|
54
72
|
try {
|
|
55
|
-
|
|
56
|
-
const scanned = transpiler.scan(source) as { exports: string[] };
|
|
73
|
+
const scanned = scanner.scan(source);
|
|
57
74
|
return { error: null, exports: scanned.exports };
|
|
58
75
|
} catch (e) {
|
|
59
76
|
return {
|
|
@@ -150,8 +167,12 @@ function braceNames(
|
|
|
150
167
|
|
|
151
168
|
const VAR_DECL_RE = /^export\s+(?:const|let|var)\b/;
|
|
152
169
|
|
|
153
|
-
export function extractExports(
|
|
154
|
-
|
|
170
|
+
export function extractExports(
|
|
171
|
+
source: string,
|
|
172
|
+
scanner?: ExportScanner,
|
|
173
|
+
): ExportScan {
|
|
174
|
+
const s = scanner ?? getDefaultScanner();
|
|
175
|
+
const scanned = scanSource(source, s);
|
|
155
176
|
if (scanned.error) return { symbols: [], error: scanned.error };
|
|
156
177
|
|
|
157
178
|
// Ambient declarations (`export declare ...`) are invisible to scan(), so
|
|
@@ -161,7 +182,7 @@ export function extractExports(source: string): ExportScan {
|
|
|
161
182
|
/^([ \t]*)export\s+declare\s+/gm,
|
|
162
183
|
"$1export ",
|
|
163
184
|
);
|
|
164
|
-
const strippedScan = scanSource(stripped);
|
|
185
|
+
const strippedScan = scanSource(stripped, s);
|
|
165
186
|
const valid = new Set([
|
|
166
187
|
...scanned.exports,
|
|
167
188
|
...(strippedScan.error ? [] : strippedScan.exports),
|