fapony 0.3.3 → 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/fapony.ts +3 -116
- package/package.json +2 -2
- package/skill/move-to-done/SKILL.md +3 -2
- 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 +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/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 +69 -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 +70 -42
- 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/seed/plan-seed.ts +8 -4
- 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/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/{mcp → adapters/mcp}/tools/index.ts +0 -0
- /package/src/{mcp → adapters/mcp}/transport.ts +0 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/core/types.ts — shared pure types + constants (no runtime deps)
|
|
2
|
+
|
|
3
|
+
export interface ModelRates {
|
|
4
|
+
input: number;
|
|
5
|
+
output: number;
|
|
6
|
+
cacheRead: number;
|
|
7
|
+
/** null = the table does not provide it → use the input rate instead (see calcCost) */
|
|
8
|
+
cacheWrite: number | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface PriceTable {
|
|
12
|
+
fetched_at: string;
|
|
13
|
+
models: Record<string, ModelRates>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ModelBreakdown {
|
|
17
|
+
provider: string;
|
|
18
|
+
model: string;
|
|
19
|
+
session_count: number;
|
|
20
|
+
tokens_input: number;
|
|
21
|
+
tokens_output: number;
|
|
22
|
+
tokens_reasoning: number;
|
|
23
|
+
tokens_cache_read: number;
|
|
24
|
+
tokens_cache_write: number;
|
|
25
|
+
cost: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SessionDetail {
|
|
29
|
+
session_id: string;
|
|
30
|
+
model: string;
|
|
31
|
+
steps: number;
|
|
32
|
+
tools: Record<string, number>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ToolLatencyStat {
|
|
36
|
+
count: number;
|
|
37
|
+
avgMs: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface StepTimingSummary {
|
|
41
|
+
/** step-finish row count (mirrors UsageDetail.steps for SQLite providers). */
|
|
42
|
+
steps: number;
|
|
43
|
+
/** Mean per-part duration from embedded data.time (row-timestamp fallback when absent). */
|
|
44
|
+
avgStepMs: number | null;
|
|
45
|
+
/** How many durations were actually measured (vs. steps with no time signal). */
|
|
46
|
+
stepSamples: number;
|
|
47
|
+
/** Mean per-step tokens — averages only, never summed (sums overlap). */
|
|
48
|
+
avgStepInput: number | null;
|
|
49
|
+
avgStepOutput: number | null;
|
|
50
|
+
avgStepCost: number | null;
|
|
51
|
+
/** Mean tool latency grouped by tool name (from data.state.time). */
|
|
52
|
+
toolLatencyMsByType: Record<string, ToolLatencyStat>;
|
|
53
|
+
note: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface StaleReadFile {
|
|
57
|
+
file: string;
|
|
58
|
+
/** How many distinct sessions read this file (diversity, not just count). */
|
|
59
|
+
sessions: number;
|
|
60
|
+
/** Total Read calls across those sessions. */
|
|
61
|
+
reads: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface UsageDetail {
|
|
65
|
+
/** Global tool-call counts across the filtered sessions (activity signal, not quality). */
|
|
66
|
+
tool_breakdown: Record<string, number>;
|
|
67
|
+
/** Context bytes per tool — size of tool_result content blocks. */
|
|
68
|
+
bytes_by_tool?: Record<string, number>;
|
|
69
|
+
/** Total step-finish parts across the filtered sessions. */
|
|
70
|
+
steps: number;
|
|
71
|
+
/** Per-session breakdown (SQL-aggregated, never raw part rows). */
|
|
72
|
+
by_session: SessionDetail[];
|
|
73
|
+
stale_reads?: StaleReadFile[];
|
|
74
|
+
note: string;
|
|
75
|
+
/** Per-step timing/token/latency signal — present only when detail:true was requested. */
|
|
76
|
+
timing?: StepTimingSummary | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PassiveUsageResult {
|
|
80
|
+
total_tokens_input: number;
|
|
81
|
+
total_tokens_output: number;
|
|
82
|
+
total_tokens_reasoning: number;
|
|
83
|
+
total_tokens_cache_read: number;
|
|
84
|
+
total_tokens_cache_write: number;
|
|
85
|
+
total_cost: number;
|
|
86
|
+
session_count: number;
|
|
87
|
+
by_model: ModelBreakdown[];
|
|
88
|
+
detail?: UsageDetail | null;
|
|
89
|
+
zcode?: PassiveUsageResult | null;
|
|
90
|
+
claude_code?: PassiveUsageResult | null;
|
|
91
|
+
error?: string;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export const EMPTY_RESULT: PassiveUsageResult = {
|
|
95
|
+
total_tokens_input: 0,
|
|
96
|
+
total_tokens_output: 0,
|
|
97
|
+
total_tokens_reasoning: 0,
|
|
98
|
+
total_tokens_cache_read: 0,
|
|
99
|
+
total_tokens_cache_write: 0,
|
|
100
|
+
total_cost: 0,
|
|
101
|
+
session_count: 0,
|
|
102
|
+
by_model: [],
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** Common shape every client reader implements — see registry.ts. */
|
|
106
|
+
export type PassiveUsageReader = (
|
|
107
|
+
worktree?: string,
|
|
108
|
+
since?: number,
|
|
109
|
+
until?: number,
|
|
110
|
+
detail?: boolean,
|
|
111
|
+
full?: boolean,
|
|
112
|
+
) => PassiveUsageResult;
|
|
113
|
+
|
|
114
|
+
export const STEP_TOKENS_NOTE =
|
|
115
|
+
"step tokens overlap (per-step context window) — SUM(step tokens) != session tokens; steps is a count only";
|
|
116
|
+
|
|
117
|
+
export const TIMING_NOTE =
|
|
118
|
+
"timing from embedded part fields only (data.time/data.state.time/step-finish tokens) with row-timestamp fallback; averages, never raw I/O";
|
|
119
|
+
|
|
120
|
+
// --- Tool result types (PLAN-lib-layer chunk 2d) ---
|
|
121
|
+
|
|
122
|
+
export interface ToolResult {
|
|
123
|
+
content: { type: "text"; text: string }[];
|
|
124
|
+
isError?: boolean;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function jsonResult(data: unknown): ToolResult {
|
|
128
|
+
return {
|
|
129
|
+
content: [{ type: "text", text: JSON.stringify(data) }],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function errorResult(message: string): ToolResult {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function parseToolResult(result: ToolResult): unknown {
|
|
141
|
+
return JSON.parse(result.content[0].text);
|
|
142
|
+
}
|
package/src/core/util.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Replacement values are inserted via a function so `$&`, `$'` etc. in the
|
|
2
|
+
// value are treated as literal text, not replace() special patterns.
|
|
3
|
+
export function templateArgs(
|
|
4
|
+
arr: string[],
|
|
5
|
+
vars: Record<string, string>,
|
|
6
|
+
): string[] {
|
|
7
|
+
return arr.map((s) => {
|
|
8
|
+
let out = s;
|
|
9
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
10
|
+
out = out.replaceAll(`{${k}}`, () => v);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Fill a prompt template with {{VARS}} (all occurrences). Missing vars → "". */
|
|
17
|
+
export function fillPrompt(
|
|
18
|
+
template: string,
|
|
19
|
+
vars: Record<string, string>,
|
|
20
|
+
): string {
|
|
21
|
+
let out = template;
|
|
22
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
23
|
+
out = out.replaceAll(`{{${k}}}`, () => v);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Minutes between two ISO-like timestamps (space separator, UTC assumed). */
|
|
29
|
+
export function minutesBetween(a: string, b: string): number {
|
|
30
|
+
const t0 = new Date(`${a.replace(" ", "T")}Z`).getTime();
|
|
31
|
+
const t1 = new Date(`${b.replace(" ", "T")}Z`).getTime();
|
|
32
|
+
return (t1 - t0) / 60000;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Arithmetic mean of a numeric array. 0 for empty arrays. */
|
|
36
|
+
export function avg(xs: number[]): number {
|
|
37
|
+
return xs.length ? xs.reduce((s, x) => s + x, 0) / xs.length : 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** True for "y"/"yes" (case-insensitive, trimmed) — the only affirmative answers. */
|
|
41
|
+
export function isAffirmative(answer: string): boolean {
|
|
42
|
+
const normalized = answer.trim().toLowerCase();
|
|
43
|
+
return normalized === "y" || normalized === "yes";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// --- Output capping ---
|
|
47
|
+
|
|
48
|
+
// Keep the head, always say how much was cut — a silent cut is
|
|
49
|
+
// indistinguishable from "that was everything".
|
|
50
|
+
export function capLines(lines: string[], cap: number, what: string): string[] {
|
|
51
|
+
if (lines.length <= cap) return lines;
|
|
52
|
+
const rest = lines.length - (cap - 1);
|
|
53
|
+
const kept = lines.slice(0, cap - 1);
|
|
54
|
+
kept.push(`… +${rest} more ${what}`);
|
|
55
|
+
return kept;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- Recursive directory walker ---
|
|
59
|
+
|
|
60
|
+
import { readdirSync } from "node:fs";
|
|
61
|
+
import { join } from "node:path";
|
|
62
|
+
|
|
63
|
+
export interface WalkDirOpts {
|
|
64
|
+
/** Max recursion depth (default 4). */
|
|
65
|
+
depth?: number;
|
|
66
|
+
/** Skip node_modules (default true). */
|
|
67
|
+
skipNodeModules?: boolean;
|
|
68
|
+
/** Dot-directories to descend into (default []). Others starting with . are skipped. */
|
|
69
|
+
includeDotDirs?: string[];
|
|
70
|
+
/** Extra filter: return true to collect this dir. Omit to collect all reached dirs. */
|
|
71
|
+
predicate?: (dirPath: string) => boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Recursively walk directories under `root`.
|
|
76
|
+
* Returns absolute paths of dirs for which `predicate` returned true (or all
|
|
77
|
+
* reached dirs when no predicate is given). Skips node_modules by default.
|
|
78
|
+
*/
|
|
79
|
+
export function walkDir(root: string, opts?: WalkDirOpts): string[] {
|
|
80
|
+
const maxDepth = opts?.depth ?? 4;
|
|
81
|
+
const skipNm = opts?.skipNodeModules ?? true;
|
|
82
|
+
const includeDot = new Set(opts?.includeDotDirs ?? []);
|
|
83
|
+
const pred = opts?.predicate;
|
|
84
|
+
const result: string[] = [];
|
|
85
|
+
|
|
86
|
+
const walk = (dir: string, depth: number): void => {
|
|
87
|
+
if (depth > maxDepth) return;
|
|
88
|
+
if (!pred || pred(dir)) result.push(dir);
|
|
89
|
+
try {
|
|
90
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
91
|
+
if (!entry.isDirectory()) continue;
|
|
92
|
+
if (entry.isSymbolicLink()) continue;
|
|
93
|
+
if (skipNm && entry.name === "node_modules") continue;
|
|
94
|
+
if (entry.name.startsWith(".") && !includeDot.has(entry.name)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
walk(join(dir, entry.name), depth + 1);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// ignore unreadable dirs
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
walk(root, 0);
|
|
104
|
+
return result;
|
|
105
|
+
}
|
package/src/db/store.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { existsSync, mkdirSync } from "node:fs";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
3
|
+
import type { Config, Event, Run, RunStatus } from "../core/config.js";
|
|
4
|
+
import { faponyDir } from "../core/config.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Schema version for state.db — tracked via `PRAGMA user_version`.
|
package/src/debt/cli.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
6
6
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
7
|
-
import { CONVENTIONS_FILE } from "../
|
|
7
|
+
import { CONVENTIONS_FILE } from "../core/config.js";
|
|
8
8
|
import { formatDebt } from "./format.js";
|
|
9
9
|
import { loadConventions } from "./load.js";
|
|
10
10
|
import { findPromotions, formatPromotions } from "./promotion.js";
|
package/src/debt/format.ts
CHANGED
|
@@ -1,107 +1,2 @@
|
|
|
1
|
-
// src/debt
|
|
2
|
-
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
import { type DebtReport, ZONE_CAP, ZONE_DEPTH } from "./types.js";
|
|
5
|
-
|
|
6
|
-
/** The zone of a file: its directory path, capped at `depth` segments. */
|
|
7
|
-
function zoneOf(file: string, depth: number): string {
|
|
8
|
-
const parts = dirname(file)
|
|
9
|
-
.split("/")
|
|
10
|
-
.filter((p) => p && p !== ".");
|
|
11
|
-
return parts.slice(0, depth).join("/") || ".";
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Group files by their directory zone (see `zoneOf`). */
|
|
15
|
-
function groupFilesByZone(
|
|
16
|
-
files: string[],
|
|
17
|
-
depth: number,
|
|
18
|
-
): Map<string, string[]> {
|
|
19
|
-
const zones = new Map<string, string[]>();
|
|
20
|
-
for (const f of files) {
|
|
21
|
-
const zone = zoneOf(f, depth);
|
|
22
|
-
const cur = zones.get(zone) ?? [];
|
|
23
|
-
cur.push(f);
|
|
24
|
-
zones.set(zone, cur);
|
|
25
|
-
}
|
|
26
|
-
// Sort zones by file count descending, then alphabetically
|
|
27
|
-
return new Map(
|
|
28
|
-
[...zones.entries()].sort((a, b) => {
|
|
29
|
-
const d = b[1].length - a[1].length;
|
|
30
|
-
return d !== 0 ? d : a[0].localeCompare(b[0]);
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** Escape a regex source for use in a shell grep command. */
|
|
36
|
-
function shellEscapeRe(src: string): string {
|
|
37
|
-
return src.replace(/'/g, "'\\''");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function formatDebt(report: DebtReport, showAll = false): string {
|
|
41
|
-
const lines: string[] = [];
|
|
42
|
-
lines.push(
|
|
43
|
-
`fapony debt — ${report.entries.length + report.declared.length + report.checkedCount} convention(s), ` +
|
|
44
|
-
`${report.scannedFiles} files scanned, ${report.ms}ms — derived fresh, not stored`,
|
|
45
|
-
);
|
|
46
|
-
for (const e of report.entries) {
|
|
47
|
-
const moved =
|
|
48
|
-
e.movedCount !== null && e.files.length > 0
|
|
49
|
-
? ` · moved ${e.movedCount} (${Math.round((e.movedCount / (e.files.length + e.movedCount)) * 100)}%)`
|
|
50
|
-
: e.movedCount !== null
|
|
51
|
-
? ` · moved ${e.movedCount}`
|
|
52
|
-
: "";
|
|
53
|
-
lines.push(`\n${e.conv.id} — ${e.conv.rule}`);
|
|
54
|
-
// Show the patterns actually used
|
|
55
|
-
const patterns: string[] = [];
|
|
56
|
-
if (e.conv.stale) patterns.push(`stale: ${e.conv.stale}`);
|
|
57
|
-
if (e.conv.ok) patterns.push(`ok: ${e.conv.ok}`);
|
|
58
|
-
if (e.conv.guard) patterns.push(`guard: ${e.conv.guard}`);
|
|
59
|
-
patterns.push(`where ${e.conv.where}`);
|
|
60
|
-
lines.push(` ${patterns.join(" · ")}`);
|
|
61
|
-
if (e.files.length === 0) {
|
|
62
|
-
lines.push(` debt 0${moved} — clean`);
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
lines.push(` debt ${e.files.length}${moved}`);
|
|
66
|
-
// Verify command derived from stale
|
|
67
|
-
if (e.conv.stale) {
|
|
68
|
-
lines.push(` verify: grep -rn '${shellEscapeRe(e.conv.stale)}' <zone>`);
|
|
69
|
-
}
|
|
70
|
-
// Zone grouping
|
|
71
|
-
const zones = groupFilesByZone(e.files, ZONE_DEPTH);
|
|
72
|
-
const zoneEntries = [...zones.entries()];
|
|
73
|
-
const cap = showAll
|
|
74
|
-
? zoneEntries.length
|
|
75
|
-
: Math.min(zoneEntries.length, ZONE_CAP);
|
|
76
|
-
let totalCapped = 0;
|
|
77
|
-
for (let i = 0; i < cap; i++) {
|
|
78
|
-
const [zone, zoneFiles] = zoneEntries[i];
|
|
79
|
-
const pad = " ".repeat(Math.max(0, 42 - zone.length));
|
|
80
|
-
lines.push(`\n ${zone}${pad}${zoneFiles.length} ไฟล์`);
|
|
81
|
-
lines.push(` ${zoneFiles.map((f) => f.split("/").pop()).join(" · ")}`);
|
|
82
|
-
totalCapped += zoneFiles.length;
|
|
83
|
-
}
|
|
84
|
-
if (zoneEntries.length > cap) {
|
|
85
|
-
const remaining = e.files.length - totalCapped;
|
|
86
|
-
const remainingZones = zoneEntries.length - cap;
|
|
87
|
-
lines.push(
|
|
88
|
-
`\n … อีก ${remainingZones} โซน (${remaining} ไฟล์) — fapony debt --id ${e.conv.id} --all`,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
for (const c of report.declared) {
|
|
93
|
-
lines.push(`\n${c.id} — ${c.rule} (where ${c.where})`);
|
|
94
|
-
lines.push(
|
|
95
|
-
` declared, no checker, stale not filled in — fill "stale" in conventions.json`,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
if (report.checkedCount > 0) {
|
|
99
|
-
lines.push(
|
|
100
|
-
`\n${report.checkedCount} convention(s) have a checker — fapony stays silent, the checker reports`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
for (const d of report.dropped) {
|
|
104
|
-
lines.push(`⚠ ${d.id}: ${d.reason}`);
|
|
105
|
-
}
|
|
106
|
-
return lines.join("\n");
|
|
107
|
-
}
|
|
1
|
+
// shim — moved to src/core/debt-format.ts
|
|
2
|
+
export { formatDebt } from "../core/debt-format.js";
|
package/src/debt/load.ts
CHANGED
package/src/debt/promotion.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// ≥ 6 chars from stale such as toLocaleString/useMutation — "throw"/"Error" are too
|
|
12
12
|
// short and don't count, to avoid over-matching)
|
|
13
13
|
|
|
14
|
-
import { openDb } from "../db/
|
|
14
|
+
import { openDb } from "../db/store.js";
|
|
15
15
|
import { readMemLog } from "../memory.js";
|
|
16
16
|
import {
|
|
17
17
|
type Convention,
|
package/src/debt/types.ts
CHANGED
|
@@ -1,79 +1,14 @@
|
|
|
1
|
-
// src/debt
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/** Regex source: files that already moved (informational count). */
|
|
16
|
-
ok?: string;
|
|
17
|
-
/** Regex source a file must ALSO match to be in scope (e.g. "extends Base"). */
|
|
18
|
-
guard?: string;
|
|
19
|
-
/** Non-null = a checker (eslint rule / script) exists → fapony never reports this debt. */
|
|
20
|
-
checker?: string | null;
|
|
21
|
-
/** Human answered "no checker" on the promotion question — never ask again. */
|
|
22
|
-
decided?: "no-checker" | null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface LoadedConventions {
|
|
26
|
-
path: string | null;
|
|
27
|
-
convs: Convention[];
|
|
28
|
-
/** Rows kept for display but not scannable, plus invalid rows — said out loud, never silent. */
|
|
29
|
-
warnings: string[];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface DebtEntry {
|
|
33
|
-
conv: Convention;
|
|
34
|
-
/** Files with the debt (stale match), sorted. */
|
|
35
|
-
files: string[];
|
|
36
|
-
/** Files that already moved (ok match) — null when ok is not set. */
|
|
37
|
-
movedCount: number | null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface DebtReport {
|
|
41
|
-
worktree: string;
|
|
42
|
-
scannedFiles: number;
|
|
43
|
-
ms: number;
|
|
44
|
-
/** Scannable conventions with their debt list (checker rows never land here). */
|
|
45
|
-
entries: DebtEntry[];
|
|
46
|
-
/** Declared but not fillable by fapony: checker null + no stale — the human/agent fills `stale`. */
|
|
47
|
-
declared: Convention[];
|
|
48
|
-
/** Skipped-with-reason: checker rows are silent by design (not dropped), these are real drops. */
|
|
49
|
-
dropped: { id: string; reason: string }[];
|
|
50
|
-
/** Silent-by-design count: checker non-null — reported as a number, not a list. */
|
|
51
|
-
checkedCount: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export interface Promotion {
|
|
55
|
-
convId: string;
|
|
56
|
-
rule: string;
|
|
57
|
-
occurrences: number;
|
|
58
|
-
dates: string[];
|
|
59
|
-
debtCount: number;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// A stale regex matching more than this many files is not a convention — it is
|
|
63
|
-
// a broken/wide regex (stale="e" would flag the repo). SPEC §6: drop the entry
|
|
64
|
-
// and say so, never report 600 files.
|
|
65
|
-
export const DEBT_FILE_CAP = 250;
|
|
66
|
-
|
|
67
|
-
export const PROMOTION_THRESHOLD = 3;
|
|
68
|
-
export const PROMOTION_MAX = 3;
|
|
69
|
-
/** Identifiers shorter than this are too generic to match prose on ("throw", "Error"). */
|
|
70
|
-
export const WORD_MIN = 6;
|
|
71
|
-
|
|
72
|
-
// Zone grouping: a zone is a file's *directory*, capped at this many leading
|
|
73
|
-
// segments — never a fixed-depth prefix of the path (which would cut into the
|
|
74
|
-
// filename) and never the filename itself. SPEC §4 shows zones at depth 5
|
|
75
|
-
// (`apps/mdl/src/server/services`) and depth 3 (`packages/cache/src`) in the
|
|
76
|
-
// same report, so the cap must follow the directory, not a constant.
|
|
77
|
-
export const ZONE_DEPTH = 5;
|
|
78
|
-
// Default cap on zones shown per convention — more than this is a wall, not an answer.
|
|
79
|
-
export const ZONE_CAP = 6;
|
|
1
|
+
// shim — moved to src/core/debt-types.ts
|
|
2
|
+
export {
|
|
3
|
+
type Convention,
|
|
4
|
+
DEBT_FILE_CAP,
|
|
5
|
+
type DebtEntry,
|
|
6
|
+
type DebtReport,
|
|
7
|
+
type LoadedConventions,
|
|
8
|
+
PROMOTION_MAX,
|
|
9
|
+
PROMOTION_THRESHOLD,
|
|
10
|
+
type Promotion,
|
|
11
|
+
WORD_MIN,
|
|
12
|
+
ZONE_CAP,
|
|
13
|
+
ZONE_DEPTH,
|
|
14
|
+
} from "../core/debt-types.js";
|
package/src/digest/collect.ts
CHANGED
|
@@ -10,11 +10,12 @@ import {
|
|
|
10
10
|
doneDir,
|
|
11
11
|
type Event,
|
|
12
12
|
loadConfig,
|
|
13
|
-
openDb,
|
|
14
13
|
planDir,
|
|
15
14
|
type Run,
|
|
16
|
-
} from "../
|
|
17
|
-
import {
|
|
15
|
+
} from "../core/config.js";
|
|
16
|
+
import { type HintImpact, hintLogPath } from "../core/hint-log.js";
|
|
17
|
+
import { openDb } from "../db/store.js";
|
|
18
|
+
import { computeHintImpact } from "../hook.js";
|
|
18
19
|
import { type MemRow, readMemLog } from "../memory.js";
|
|
19
20
|
import { isPassFamily, VERDICT_GRADES } from "../parse.js";
|
|
20
21
|
import { imputeResult, loadPrices } from "../price/index.js";
|
package/src/gate.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// gateOnce is the core review-verdict logic — still live, called by the CLI
|
|
2
2
|
// wrapper (cmdGate) was removed with the rest of the execute→review→fix loop
|
|
3
3
|
// (Wave 2). The MCP verdict_submit tool was removed in PLAN-verdict-to-mem.
|
|
4
|
+
import { loadConfig } from "./core/config.js";
|
|
4
5
|
import {
|
|
5
6
|
addEvent,
|
|
6
7
|
getRun,
|
|
7
8
|
incrementRound,
|
|
8
|
-
loadConfig,
|
|
9
9
|
openDb,
|
|
10
10
|
setStatus,
|
|
11
|
-
} from "./db/
|
|
11
|
+
} from "./db/store.js";
|
|
12
12
|
import { closeMemory, kickoffMemory } from "./memory.js";
|
|
13
13
|
import { isPassFamily, type VerdictGrade } from "./parse.js";
|
|
14
14
|
|
package/src/gates.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// cumulative. Events arrive sorted (run_id, id), so one bucketing pass plus a
|
|
10
10
|
// forward spawn pointer over the disjoint windows is O(events) total.
|
|
11
11
|
|
|
12
|
-
import type { Event } from "./
|
|
12
|
+
import type { Event } from "./core/config.js";
|
|
13
13
|
import { qualityScore, VERDICT_GRADES, type VerdictGrade } from "./parse.js";
|
|
14
14
|
import {
|
|
15
15
|
findSessionAt,
|