fapony 0.6.2 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +144 -156
  2. package/package.json +5 -6
  3. package/skill/define-convention/SKILL.md +3 -3
  4. package/skill/lookup-before-edit/SKILL.md +2 -1
  5. package/skill/move-to-done/SKILL.md +18 -22
  6. package/skill/plan-with-pony/SKILL.md +4 -4
  7. package/skill/review-pony/SKILL.md +10 -12
  8. package/src/adapters/cli.ts +11 -52
  9. package/src/adapters/hooks/index.ts +0 -67
  10. package/src/adapters/hooks/mv-guard.ts +2 -2
  11. package/src/analyze/discover.ts +1 -1
  12. package/src/analyze/index.ts +0 -1
  13. package/src/commands.ts +8 -39
  14. package/src/core/config.ts +0 -6
  15. package/src/core/fapony-dir.ts +71 -0
  16. package/src/core/hook-helpers.ts +1 -2
  17. package/src/debt/load.ts +3 -5
  18. package/src/debt/promotion.ts +2 -2
  19. package/src/debt/scan.ts +1 -1
  20. package/src/digest/collect.ts +6 -7
  21. package/src/fael.ts +139 -0
  22. package/src/hook.ts +1 -81
  23. package/src/init.ts +30 -90
  24. package/src/install/antigravity.ts +8 -99
  25. package/src/install/claude.ts +66 -200
  26. package/src/install/codex.ts +6 -170
  27. package/src/install/detect.ts +4 -14
  28. package/src/install/opencode.ts +50 -486
  29. package/src/install/types.ts +1 -39
  30. package/src/install/zcode.ts +5 -91
  31. package/src/install.ts +9 -35
  32. package/src/memory.ts +10 -69
  33. package/src/plan/index.ts +34 -0
  34. package/src/plan/next.ts +181 -0
  35. package/src/plan/store.ts +41 -0
  36. package/src/{mem/commands/plan.ts → plan/sweep.ts} +66 -46
  37. package/src/seed/plan-seed.ts +10 -9
  38. package/src/setup.ts +4 -22
  39. package/src/update.ts +1 -1
  40. package/templates/PLAN.md +1 -1
  41. package/src/adapters/hooks/bug-markers.ts +0 -60
  42. package/src/adapters/hooks/context-data.ts +0 -139
  43. package/src/adapters/hooks/edit-hint.ts +0 -215
  44. package/src/adapters/hooks/read-hint.ts +0 -410
  45. package/src/adapters/hooks/session-start.ts +0 -112
  46. package/src/adapters/hooks/stop.ts +0 -768
  47. package/src/adapters/mcp/tools/index.ts +0 -149
  48. package/src/adapters/mcp/tools/mem.ts +0 -252
  49. package/src/adapters/mcp/transport.ts +0 -147
  50. package/src/analyze/cache.ts +0 -162
  51. package/src/core/mem-log.ts +0 -375
  52. package/src/init-mem.ts +0 -143
  53. package/src/install/cursor.ts +0 -167
  54. package/src/install/utils.ts +0 -29
  55. package/src/mem/commands/read.ts +0 -738
  56. package/src/mem/commands/rotate.ts +0 -59
  57. package/src/mem/commands/where.ts +0 -56
  58. package/src/mem/commands/write.ts +0 -273
  59. package/src/mem/engine.ts +0 -315
  60. package/src/mem/index.ts +0 -201
  61. package/src/mem/render.ts +0 -66
  62. package/src/mem/selectors.ts +0 -160
  63. package/src/mem/store.ts +0 -356
@@ -1,167 +0,0 @@
1
- // src/install/cursor.ts — Cursor install provider
2
- //
3
- // Writes mcpServers.fapony to ~/.cursor/mcp.json directly (Cursor has no CLI
4
- // for MCP config) and appends the fapony stop hook to the stop array in
5
- // ~/.cursor/hooks.json — append, never replace: other tools (e.g.
6
- // code-review-graph) register hooks in the same file and Cursor runs every
7
- // entry in the array.
8
-
9
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
- import { homedir } from "node:os";
11
- import { join } from "node:path";
12
- import {
13
- CURSOR_MCP_ENTRY,
14
- defaultExit,
15
- INSTALL_ROOT,
16
- type InstallDeps,
17
- MCP_KEY,
18
- } from "./types.js";
19
-
20
- /** ~/.cursor itself — the app creates it on first run, often before the user
21
- * ever adds an MCP server or hook, so dir-exists is the detect signal. */
22
- export function findCursorDir(getHome: () => string): string | null {
23
- const dir = join(getHome(), ".cursor");
24
- return existsSync(dir) ? dir : null;
25
- }
26
-
27
- function isFaponyEntry(entry: unknown): boolean {
28
- if (!entry || typeof entry !== "object") return false;
29
- const cfg = entry as Record<string, unknown>;
30
- return (
31
- cfg.command === CURSOR_MCP_ENTRY.command &&
32
- JSON.stringify(cfg.args) === JSON.stringify(CURSOR_MCP_ENTRY.args)
33
- );
34
- }
35
-
36
- function readJsonObject(path: string): Record<string, unknown> | null {
37
- try {
38
- const parsed: unknown = JSON.parse(readFileSync(path, "utf-8"));
39
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
40
- return null;
41
- }
42
- return parsed as Record<string, unknown>;
43
- } catch {
44
- return null;
45
- }
46
- }
47
-
48
- export function cmdInstallCursor(
49
- dryRun: boolean,
50
- deps: InstallDeps = {},
51
- ): void {
52
- const exitFn = deps.exit ?? defaultExit;
53
- const getHome = deps.homedir ?? homedir;
54
- const cursorDir = findCursorDir(getHome);
55
-
56
- if (!cursorDir) {
57
- console.error(
58
- `Cursor not found — open Cursor at least once to create ~/.cursor`,
59
- );
60
- exitFn(1);
61
- return;
62
- }
63
-
64
- // --- 1. MCP server (mcpServers.fapony in ~/.cursor/mcp.json) ---
65
- const mcpPath = join(cursorDir, "mcp.json");
66
- let mcp: Record<string, unknown> = {};
67
- let mcpIsNew = true;
68
- if (existsSync(mcpPath)) {
69
- const parsed = readJsonObject(mcpPath);
70
- if (!parsed) {
71
- console.error(`failed to parse ${mcpPath} — fix or remove it first`);
72
- exitFn(1);
73
- return;
74
- }
75
- mcp = parsed;
76
- mcpIsNew = false;
77
- }
78
- const servers = (mcp.mcpServers ?? {}) as Record<string, unknown>;
79
- const existing = servers[MCP_KEY];
80
- if (existing !== undefined && !isFaponyEntry(existing)) {
81
- console.error(
82
- `an MCP server named "${MCP_KEY}" exists but points elsewhere — not overwriting.`,
83
- );
84
- console.error(` inspect ${mcpPath} and remove it first`);
85
- exitFn(1);
86
- return;
87
- }
88
- if (existing !== undefined) {
89
- console.error(
90
- `✓ mcpServers.${MCP_KEY} already configured — no change needed`,
91
- );
92
- console.error(` (${mcpPath})`);
93
- } else {
94
- const after = {
95
- ...mcp,
96
- mcpServers: { ...servers, [MCP_KEY]: CURSOR_MCP_ENTRY },
97
- };
98
- if (dryRun) {
99
- console.error(
100
- `── dry-run: would ${mcpIsNew ? "create" : "write"} ${mcpPath}${mcpIsNew ? "" : ` (mcpServers.${MCP_KEY})`} ──`,
101
- );
102
- } else {
103
- writeFileSync(mcpPath, `${JSON.stringify(after, null, 2)}\n`, "utf-8");
104
- console.error(`✓ added mcpServers.${MCP_KEY} to ${mcpPath}`);
105
- console.error(` restart Cursor to load the MCP server`);
106
- }
107
- }
108
-
109
- // --- 2. Stop hook (hooks.stop array in ~/.cursor/hooks.json) ---
110
- installStopHook(dryRun, cursorDir);
111
- }
112
-
113
- /**
114
- * Append `fapony hook-stop` to the stop array in ~/.cursor/hooks.json.
115
- * Same policy as Claude's installStopHook: never touch a hook someone else
116
- * registered, never fail the install over it. User hooks run from ~/.cursor/,
117
- * so the command must be an absolute path.
118
- */
119
- function installStopHook(dryRun: boolean, cursorDir: string): void {
120
- const hooksPath = join(cursorDir, "hooks.json");
121
- let config: Record<string, unknown> = {};
122
- if (existsSync(hooksPath)) {
123
- const parsed = readJsonObject(hooksPath);
124
- if (!parsed) {
125
- console.error(
126
- ` stop hook: ${hooksPath} is unreadable or malformed — skipping`,
127
- );
128
- return;
129
- }
130
- config = parsed;
131
- }
132
- const hookMap = config.hooks;
133
- if (
134
- hookMap !== undefined &&
135
- (typeof hookMap !== "object" || Array.isArray(hookMap))
136
- ) {
137
- console.error(
138
- ` stop hook: ${hooksPath} has an unexpected "hooks" shape — skipping`,
139
- );
140
- return;
141
- }
142
- const map = (hookMap ?? {}) as Record<string, unknown>;
143
- const stop = Array.isArray(map.stop) ? (map.stop as unknown[]) : [];
144
- if (JSON.stringify(stop).includes("hook-stop")) {
145
- console.error(` stop hook: already configured in hooks.json — no change`);
146
- return;
147
- }
148
-
149
- const command = `bun ${join(INSTALL_ROOT, "fapony.ts")} hook-stop`;
150
- const after = {
151
- ...config,
152
- version: typeof config.version === "number" ? config.version : 1,
153
- hooks: { ...map, stop: [...stop, { command }] },
154
- };
155
-
156
- if (!dryRun) {
157
- try {
158
- writeFileSync(hooksPath, `${JSON.stringify(after, null, 2)}\n`, "utf-8");
159
- } catch (e) {
160
- console.error(` stop hook: failed to write — ${(e as Error).message}`);
161
- return;
162
- }
163
- }
164
- console.error(
165
- ` stop hook: ${dryRun ? "would write" : "wrote"} hooks.stop → ${hooksPath}`,
166
- );
167
- }
@@ -1,29 +0,0 @@
1
- // src/install/utils.ts — shared utilities for install providers
2
-
3
- /**
4
- * Compute a line-by-line JSON diff between two objects.
5
- * Used by OpenCode and ZCode providers for dry-run output.
6
- */
7
- export function computeDiff(
8
- before: Record<string, unknown>,
9
- after: Record<string, unknown>,
10
- ): string {
11
- const beforeStr = JSON.stringify(before, null, 2);
12
- const afterStr = JSON.stringify(after, null, 2);
13
- const beforeLines = beforeStr.split("\n");
14
- const afterLines = afterStr.split("\n");
15
-
16
- const diff: string[] = [];
17
- const max = Math.max(beforeLines.length, afterLines.length);
18
- for (let i = 0; i < max; i++) {
19
- const b = beforeLines[i];
20
- const a = afterLines[i];
21
- if (b !== a) {
22
- if (b !== undefined) diff.push(`- ${b}`);
23
- if (a !== undefined) diff.push(`+ ${a}`);
24
- } else {
25
- diff.push(` ${b}`);
26
- }
27
- }
28
- return diff.join("\n");
29
- }