fapony 0.3.0 → 0.3.4
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 +87 -65
- package/fapony.ts +3 -116
- package/package.json +6 -5
- package/skill/move-to-done/SKILL.md +22 -32
- package/skill/review-pony/SKILL.md +27 -58
- package/src/adapters/cli.ts +123 -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 +79 -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 +302 -0
- package/src/{mcp → adapters/mcp}/evidence.ts +2 -2
- package/src/{mcp → adapters/mcp}/primitives.ts +3 -3
- 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/index.ts +0 -74
- package/src/{mcp → adapters/mcp}/tools/mem.ts +3 -3
- package/src/{mcp → adapters/mcp}/tools/report.ts +4 -4
- package/src/{mcp → adapters/mcp}/transport.ts +4 -39
- package/src/adapters/mcp/types.ts +18 -0
- package/src/{mcp → adapters/mcp}/worktree.ts +2 -2
- package/src/analyze.ts +2 -2
- 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 +5 -5
- package/src/gates.ts +1 -1
- package/src/hook.ts +69 -1337
- package/src/init-mem.ts +58 -72
- package/src/init.ts +13 -17
- package/src/install/antigravity.ts +112 -0
- package/src/install/claude.ts +19 -123
- package/src/install/detect.ts +17 -7
- package/src/install/opencode.ts +167 -26
- package/src/install.ts +23 -7
- package/src/lint-baseline.ts +1 -2
- package/src/map.ts +29 -8
- package/src/mem/commands/plan.ts +70 -42
- package/src/mem/commands/read.ts +219 -146
- package/src/mem/index.ts +4 -13
- package/src/mem/store.ts +5 -1
- package/src/memory.ts +24 -387
- 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/{plan-seed.ts → seed/plan-seed.ts} +18 -32
- package/src/seed/primitives.ts +60 -0
- package/src/{review-seed.ts → seed/review-seed.ts} +7 -54
- package/src/session/types.ts +14 -128
- package/src/setup.ts +1 -1
- package/src/stats/data.ts +4 -4
- package/src/telemetry.ts +3 -3
- 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 -32
- package/src/web/html.ts +2 -33
- package/templates/SPEC.md +8 -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/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/math.ts +0 -13
- package/src/mcp/tools/verdict.ts +0 -161
- package/src/mcp/types.ts +0 -54
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
|
/**
|
|
@@ -335,10 +344,10 @@ export const FaponyCommitHint = async ({ directory }) => {
|
|
|
335
344
|
export function editHintPluginSource(root: string): string {
|
|
336
345
|
const hookModule = JSON.stringify(join(root, "src", "hook.ts"));
|
|
337
346
|
return `// fapony edit hint — annotates edits to source files that have importers
|
|
338
|
-
// with the importer count plus
|
|
347
|
+
// with the importer count, plus mem/debt context rows about the file.
|
|
339
348
|
// Annotate only: never blocks, dedupe per (session, file) inside editHintFor.
|
|
340
349
|
// Generated by \`fapony install\` — edit src/hook.ts in the fapony checkout.
|
|
341
|
-
import { editHintFor, recordHintFire } from ${hookModule};
|
|
350
|
+
import { editHintFor, readContextData, recordHintFire } from ${hookModule};
|
|
342
351
|
|
|
343
352
|
export const FaponyEditHint = async ({ directory }) => {
|
|
344
353
|
return {
|
|
@@ -350,16 +359,25 @@ export const FaponyEditHint = async ({ directory }) => {
|
|
|
350
359
|
// spends the per-session dedupe row or logs a fire that never showed.
|
|
351
360
|
if (typeof output.output !== "string") return;
|
|
352
361
|
const filePath = input.args?.filePath;
|
|
362
|
+
const parts = [];
|
|
353
363
|
const hint = editHintFor({
|
|
354
364
|
filePath,
|
|
355
365
|
cwd: directory,
|
|
356
366
|
session: input.sessionID,
|
|
357
367
|
});
|
|
368
|
+
if (hint) parts.push(hint);
|
|
369
|
+
// Attach mem/debt context (same as read hint — annotate only, cap 5 lines)
|
|
370
|
+
const ctx = readContextData(filePath, directory);
|
|
371
|
+
if (ctx) {
|
|
372
|
+
for (const line of [...ctx.debtLines, ...ctx.memLines]) {
|
|
373
|
+
parts.push(line);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (parts.length > 0) {
|
|
377
|
+
output.output = output.output + "\\n" + parts.join("\\n");
|
|
358
378
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (hint) {
|
|
362
|
-
output.output = output.output + "\\n" + hint;
|
|
379
|
+
// --- hint-fire log (PLAN-edit-importer-hint, opencode parity) ---
|
|
380
|
+
// After output — best-effort, never block the hint.
|
|
363
381
|
try {
|
|
364
382
|
const { spawnSync } = require("node:child_process");
|
|
365
383
|
const git = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd: directory, encoding: "utf-8" });
|
|
@@ -374,6 +392,12 @@ export const FaponyEditHint = async ({ directory }) => {
|
|
|
374
392
|
} catch { return null; }
|
|
375
393
|
})() : null;
|
|
376
394
|
recordHintFire({ ts: new Date().toISOString(), worktree, surface: "edit", file: rel, count: 1 });
|
|
395
|
+
if (ctx?.debtIds?.length) {
|
|
396
|
+
recordHintFire({ ts: new Date().toISOString(), worktree, surface: "debt", file: rel, count: ctx.debtIds.length, ids: ctx.debtIds });
|
|
397
|
+
}
|
|
398
|
+
if (ctx?.memLines?.length) {
|
|
399
|
+
recordHintFire({ ts: new Date().toISOString(), worktree, surface: "mem", file: rel, count: ctx.memLines.length });
|
|
400
|
+
}
|
|
377
401
|
}
|
|
378
402
|
} catch {
|
|
379
403
|
// best-effort
|
|
@@ -488,8 +512,8 @@ function installEditHintPlugin(dryRun: boolean, getHome: () => string): void {
|
|
|
488
512
|
}
|
|
489
513
|
|
|
490
514
|
/**
|
|
491
|
-
* OpenCode plugin — session-start context
|
|
492
|
-
*
|
|
515
|
+
* OpenCode plugin — session-start context, the parity of Claude's SessionStart
|
|
516
|
+
* hook.
|
|
493
517
|
*
|
|
494
518
|
* Why `experimental.chat.system.transform` and not the `event` hook on
|
|
495
519
|
* `session.created`: the event hook is observer-only (returns void, no
|
|
@@ -500,19 +524,29 @@ function installEditHintPlugin(dryRun: boolean, getHome: () => string): void {
|
|
|
500
524
|
* the same shape as every other OpenCode hint (annotate-after), and arguably
|
|
501
525
|
* better: sessions that never dispatch never pay the spawn.
|
|
502
526
|
*
|
|
527
|
+
* The guard (no mem log = silence), the kickoff spawn and the cap all live in
|
|
528
|
+
* `sessionStartContext` (src/hook.ts), imported at runtime like the read /
|
|
529
|
+
* commit / edit hint plugins — so `git pull` updates this client too. It used
|
|
530
|
+
* to bake `mem kickoff` into the generated body and push stdout, which injected
|
|
531
|
+
* kickoff's "# <path> — 0 entries" header into every session of every repo
|
|
532
|
+
* with no mem log (bug mub2ezhi) and could not be fixed by a pull. One import,
|
|
533
|
+
* one implementation, shared with the Claude/Codex hooks.
|
|
534
|
+
*
|
|
503
535
|
* Once per session (closure Set — the factory runs once per server), silent
|
|
504
|
-
* when
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
536
|
+
* when `sessionStartContext` returns null (no mem log in scope), never throws.
|
|
537
|
+
* `process.execPath` is the bun running opencode itself — no PATH dependency.
|
|
538
|
+
* Verified against OpenCode 1.18.29; `experimental.chat.system.transform` is an
|
|
539
|
+
* experimental hook, so a rename makes this plugin quiet with no error — check
|
|
540
|
+
* on OpenCode upgrades.
|
|
508
541
|
*/
|
|
509
542
|
export function sessionStartPluginSource(root: string): string {
|
|
510
543
|
const hookModule = JSON.stringify(join(root, "src", "hook.ts"));
|
|
511
544
|
const faponyTs = JSON.stringify(join(root, "fapony.ts"));
|
|
512
|
-
return `// fapony session start — injects \`fapony mem kickoff\` as context
|
|
513
|
-
// session, silent when the repo has no mem log.
|
|
514
|
-
//
|
|
515
|
-
|
|
545
|
+
return `// fapony session start — injects the \`fapony mem kickoff\` digest as context
|
|
546
|
+
// once per session via sessionStartContext, silent when the repo has no mem log.
|
|
547
|
+
// Annotate only: never blocks.
|
|
548
|
+
// Generated by \`fapony install\` — logic lives in src/hook.ts (sessionStartContext).
|
|
549
|
+
import { sessionStartContext } from ${hookModule};
|
|
516
550
|
|
|
517
551
|
export const FaponySessionStart = async ({ directory }) => {
|
|
518
552
|
const seen = new Set();
|
|
@@ -522,15 +556,8 @@ export const FaponySessionStart = async ({ directory }) => {
|
|
|
522
556
|
const sessionID = input?.sessionID;
|
|
523
557
|
if (!sessionID || seen.has(sessionID)) return;
|
|
524
558
|
seen.add(sessionID);
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
cwd: directory,
|
|
528
|
-
encoding: "utf-8",
|
|
529
|
-
});
|
|
530
|
-
if (kick.status !== 0) return;
|
|
531
|
-
const out = (kick.stdout ?? "").toString().trim();
|
|
532
|
-
if (!out) return;
|
|
533
|
-
output.system.push(capContext(out));
|
|
559
|
+
const ctx = sessionStartContext(directory, ${faponyTs});
|
|
560
|
+
if (ctx) output.system.push(ctx);
|
|
534
561
|
} catch {
|
|
535
562
|
// context must never break a session start
|
|
536
563
|
}
|
|
@@ -553,7 +580,16 @@ function installSessionStartPlugin(
|
|
|
553
580
|
} catch {
|
|
554
581
|
current = "";
|
|
555
582
|
}
|
|
556
|
-
if (current.includes("
|
|
583
|
+
if (current.includes("FaponySessionStart")) {
|
|
584
|
+
// Ours — and generated, so never overwritten. A file baked by an older
|
|
585
|
+
// fapony differs from what we generate now: say so instead of "no change",
|
|
586
|
+
// which is how the pre-hook plugin (mub2ezhi) would have been kept.
|
|
587
|
+
if (current !== sessionStartPluginSource(INSTALL_ROOT)) {
|
|
588
|
+
console.error(
|
|
589
|
+
` session start: stale fapony plugin — delete ${pluginPath} and re-run install`,
|
|
590
|
+
);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
557
593
|
console.error(` session start: already installed — no change`);
|
|
558
594
|
return;
|
|
559
595
|
}
|
|
@@ -581,3 +617,108 @@ function installSessionStartPlugin(
|
|
|
581
617
|
` session start: ${dryRun ? "would write" : "wrote"} ${pluginPath}`,
|
|
582
618
|
);
|
|
583
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
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// ZCode gets skill/<name>/ symlinked into ~/.agents/skills.
|
|
13
13
|
|
|
14
14
|
import { createInterface } from "node:readline";
|
|
15
|
+
import { cmdInstallAntigravity } from "./install/antigravity.js";
|
|
15
16
|
import { cmdInstallClaude } from "./install/claude.js";
|
|
16
17
|
import { cmdInstallCodex } from "./install/codex.js";
|
|
17
18
|
import { cmdInstallCursor } from "./install/cursor.js";
|
|
@@ -22,6 +23,7 @@ import { cmdInstallZcode } from "./install/zcode.js";
|
|
|
22
23
|
import { ask } from "./setup.js";
|
|
23
24
|
import { isAffirmative } from "./util.js";
|
|
24
25
|
|
|
26
|
+
export { cmdInstallAntigravity } from "./install/antigravity.js";
|
|
25
27
|
export {
|
|
26
28
|
claudeAddArgs,
|
|
27
29
|
claudeGetArgs,
|
|
@@ -35,6 +37,8 @@ export {
|
|
|
35
37
|
cmdInstallOpencode,
|
|
36
38
|
commitHintPluginSource,
|
|
37
39
|
editHintPluginSource,
|
|
40
|
+
gitAutonomyPluginSource,
|
|
41
|
+
type OpencodeInstallOpts,
|
|
38
42
|
readHintPluginSource,
|
|
39
43
|
} from "./install/opencode.js";
|
|
40
44
|
export {
|
|
@@ -56,8 +60,16 @@ export async function cmdInstall(
|
|
|
56
60
|
const platform = args.find((a) => !a.startsWith("--"));
|
|
57
61
|
const dryRun = args.includes("--dry-run");
|
|
58
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");
|
|
59
67
|
|
|
60
68
|
// --- explicit platform: original behavior, unchanged ---
|
|
69
|
+
if (platform === "antigravity") {
|
|
70
|
+
cmdInstallAntigravity(dryRun, deps);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
61
73
|
if (platform === "claude") {
|
|
62
74
|
cmdInstallClaude(dryRun, deps);
|
|
63
75
|
return;
|
|
@@ -75,15 +87,15 @@ export async function cmdInstall(
|
|
|
75
87
|
return;
|
|
76
88
|
}
|
|
77
89
|
if (platform === "opencode") {
|
|
78
|
-
cmdInstallOpencode(dryRun, deps);
|
|
90
|
+
cmdInstallOpencode(dryRun, deps, { gitAutonomy });
|
|
79
91
|
return;
|
|
80
92
|
}
|
|
81
93
|
if (platform !== undefined) {
|
|
82
94
|
console.error(
|
|
83
|
-
`usage: fapony install --platform opencode|claude|cursor|zcode|codex [--dry-run]`,
|
|
95
|
+
`usage: fapony install --platform antigravity|opencode|claude|cursor|zcode|codex [--dry-run] [--git-autonomy]`,
|
|
84
96
|
);
|
|
85
97
|
console.error(
|
|
86
|
-
` supported platforms: opencode, claude, cursor, zcode, codex`,
|
|
98
|
+
` supported platforms: antigravity, opencode, claude, cursor, zcode, codex`,
|
|
87
99
|
);
|
|
88
100
|
(deps.exit ?? defaultExit)(1);
|
|
89
101
|
return;
|
|
@@ -107,7 +119,7 @@ export async function cmdInstall(
|
|
|
107
119
|
if (found.length === 0) {
|
|
108
120
|
console.error();
|
|
109
121
|
console.error(
|
|
110
|
-
` no MCP client found (looked for claude on PATH; config files for cursor, opencode, zcode, codex).`,
|
|
122
|
+
` no MCP client found (looked for claude on PATH; config files for antigravity, cursor, opencode, zcode, codex).`,
|
|
111
123
|
);
|
|
112
124
|
console.error(
|
|
113
125
|
` open the app once, then re-run — or force with: fapony install --platform <name>`,
|
|
@@ -119,7 +131,7 @@ export async function cmdInstall(
|
|
|
119
131
|
if (installAll) {
|
|
120
132
|
console.error();
|
|
121
133
|
for (const client of found) {
|
|
122
|
-
installPlatform(client.platform, dryRun, deps);
|
|
134
|
+
installPlatform(client.platform, dryRun, deps, { gitAutonomy });
|
|
123
135
|
}
|
|
124
136
|
return;
|
|
125
137
|
}
|
|
@@ -148,7 +160,7 @@ export async function cmdInstall(
|
|
|
148
160
|
for (const client of found) {
|
|
149
161
|
const answer = await askFn(`install into ${client.platform}?`, "Y");
|
|
150
162
|
if (isAffirmative(answer)) {
|
|
151
|
-
installPlatform(client.platform, dryRun, deps);
|
|
163
|
+
installPlatform(client.platform, dryRun, deps, { gitAutonomy });
|
|
152
164
|
}
|
|
153
165
|
}
|
|
154
166
|
} finally {
|
|
@@ -160,8 +172,12 @@ function installPlatform(
|
|
|
160
172
|
platform: string,
|
|
161
173
|
dryRun: boolean,
|
|
162
174
|
deps: InstallDeps,
|
|
175
|
+
opts: { gitAutonomy?: boolean } = {},
|
|
163
176
|
): void {
|
|
164
177
|
switch (platform) {
|
|
178
|
+
case "antigravity":
|
|
179
|
+
cmdInstallAntigravity(dryRun, deps);
|
|
180
|
+
break;
|
|
165
181
|
case "claude":
|
|
166
182
|
cmdInstallClaude(dryRun, deps);
|
|
167
183
|
break;
|
|
@@ -169,7 +185,7 @@ function installPlatform(
|
|
|
169
185
|
cmdInstallCursor(dryRun, deps);
|
|
170
186
|
break;
|
|
171
187
|
case "opencode":
|
|
172
|
-
cmdInstallOpencode(dryRun, deps);
|
|
188
|
+
cmdInstallOpencode(dryRun, deps, opts);
|
|
173
189
|
break;
|
|
174
190
|
case "zcode":
|
|
175
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),
|