mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
Remove file-budget guardrail
This commit is contained in:
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -40,7 +40,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Maintainability guardrails (fail-fast)
|
- name: Maintainability guardrails (fail-fast)
|
||||||
run: |
|
run: |
|
||||||
bun run check:file-budgets:strict
|
|
||||||
bun run check:main-fanin:strict
|
bun run check:main-fanin:strict
|
||||||
bun run check:runtime-cycles:strict
|
bun run check:runtime-cycles:strict
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ bun run test:subtitle:dist # subtitle dist lane (currently placeholder)
|
|||||||
Run guardrails locally before opening a PR:
|
Run guardrails locally before opening a PR:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run check:file-budgets:strict
|
|
||||||
bun run check:main-fanin:strict
|
bun run check:main-fanin:strict
|
||||||
bun run check:runtime-cycles:strict
|
bun run check:runtime-cycles:strict
|
||||||
```
|
```
|
||||||
@@ -112,10 +111,8 @@ Hotspot budget baselines (2026-02-22):
|
|||||||
|
|
||||||
Troubleshooting guardrail failures:
|
Troubleshooting guardrail failures:
|
||||||
|
|
||||||
- File budget hotspot failure: split logic by runtime/domain boundaries and keep orchestration facades thin.
|
|
||||||
- Main fan-in failure: move runtime imports behind `src/main/runtime/domains/*` or composer barrels.
|
- Main fan-in failure: move runtime imports behind `src/main/runtime/domains/*` or composer barrels.
|
||||||
- Runtime cycle failure: break bidirectional imports by extracting shared helpers into leaf modules.
|
- Runtime cycle failure: break bidirectional imports by extracting shared helpers into leaf modules.
|
||||||
- Optional broad-budget audit (legacy/global): run `bun run scripts/check-file-budgets.ts --strict --enforce-global`.
|
|
||||||
|
|
||||||
## Config Generation
|
## Config Generation
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,18 @@
|
|||||||
# File Size Budgets
|
# Maintainability Guardrails
|
||||||
|
|
||||||
Purpose: keep large modules from becoming maintenance bottlenecks.
|
Purpose: keep runtime composition boundaries maintainable.
|
||||||
|
|
||||||
## Current Budget
|
## Current Checks
|
||||||
|
|
||||||
- TypeScript source files in `src/` and `launcher/`
|
|
||||||
- Soft budget: `500` LOC
|
|
||||||
- Excludes generated bundle artifacts (for example `subminer`)
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
|
|
||||||
- Warning mode (non-blocking): `bun run check:file-budgets`
|
|
||||||
- Strict mode (CI/local gate): `bun run check:file-budgets:strict`
|
|
||||||
- Custom limit: `bun run scripts/check-file-budgets.ts --limit 650`
|
|
||||||
- Main runtime fan-in (warning): `bun run check:main-fanin`
|
- Main runtime fan-in (warning): `bun run check:main-fanin`
|
||||||
- Main runtime fan-in (strict): `bun run check:main-fanin:strict`
|
- Main runtime fan-in (strict): `bun run check:main-fanin:strict`
|
||||||
- Default main fan-in thresholds: `import lines <= 110`, `unique runtime paths <= 11`
|
- Default main fan-in thresholds: `import lines <= 110`, `unique runtime paths <= 11`
|
||||||
|
- Runtime cycle check (warning): `bun run check:runtime-cycles`
|
||||||
|
- Runtime cycle check (strict): `bun run check:runtime-cycles:strict`
|
||||||
|
|
||||||
## Policy
|
## Policy
|
||||||
|
|
||||||
- If file exceeds budget, prefer extracting domain module(s) first.
|
|
||||||
- Keep composition/orchestration files focused on wiring.
|
- Keep composition/orchestration files focused on wiring.
|
||||||
- Do not hand-edit generated artifacts; refactor source modules.
|
- Do not hand-edit generated artifacts; refactor source modules.
|
||||||
- Keep `src/main.ts` runtime import fan-in low by routing domain imports through `src/main/runtime/domains/*` and `src/main/runtime/registry.ts`.
|
- Keep `src/main.ts` runtime import fan-in low by routing domain imports through `src/main/runtime/domains/*` and `src/main/runtime/registry.ts`.
|
||||||
|
- Avoid runtime import cycles by extracting shared helpers into leaf modules.
|
||||||
|
|||||||
@@ -64,3 +64,4 @@ Read first. Keep concise.
|
|||||||
| `codex-task101-docs-archive-20260222T024156Z-hneu` | `codex-task101-docs-archive` | `Execute TASK-101 consolidate architecture docs and archive task-noise evidence` | `done` | `docs/subagents/agents/codex-task101-docs-archive-20260222T024156Z-hneu.md` | `2026-02-22T03:01:38Z` |
|
| `codex-task101-docs-archive-20260222T024156Z-hneu` | `codex-task101-docs-archive` | `Execute TASK-101 consolidate architecture docs and archive task-noise evidence` | `done` | `docs/subagents/agents/codex-task101-docs-archive-20260222T024156Z-hneu.md` | `2026-02-22T03:01:38Z` |
|
||||||
| `codex-fix-ci-20260222T025848Z-0xdl` | `codex-fix-ci` | `Inspect failing GitHub Actions PR checks and implement approved fix` | `done` | `docs/subagents/agents/codex-fix-ci-20260222T025848Z-0xdl.md` | `2026-02-22T03:25:40Z` |
|
| `codex-fix-ci-20260222T025848Z-0xdl` | `codex-fix-ci` | `Inspect failing GitHub Actions PR checks and implement approved fix` | `done` | `docs/subagents/agents/codex-fix-ci-20260222T025848Z-0xdl.md` | `2026-02-22T03:25:40Z` |
|
||||||
| `opencode-task100-dead-code-prune-20260222T033155Z-qenz` | `opencode-task100-dead-code-prune` | `Execute TASK-100 dead-code prune and cleanup via writing-plans + executing-plans (no commit)` | `done` | `docs/subagents/agents/opencode-task100-dead-code-prune-20260222T033155Z-qenz.md` | `2026-02-22T04:00:41Z` |
|
| `opencode-task100-dead-code-prune-20260222T033155Z-qenz` | `opencode-task100-dead-code-prune` | `Execute TASK-100 dead-code prune and cleanup via writing-plans + executing-plans (no commit)` | `done` | `docs/subagents/agents/opencode-task100-dead-code-prune-20260222T033155Z-qenz.md` | `2026-02-22T04:00:41Z` |
|
||||||
|
| `codex-gh-fix-ci-20260222T054631Z-m4t8` | `codex-gh-fix-ci` | `Inspect failing GitHub Actions checks; propose fix plan before implementation` | `done` | `docs/subagents/agents/codex-gh-fix-ci-20260222T054631Z-m4t8.md` | `2026-02-22T06:11:15Z` |
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Agent Log: codex-gh-fix-ci-20260222T054631Z-m4t8
|
||||||
|
|
||||||
|
- alias: codex-gh-fix-ci
|
||||||
|
- mission: Inspect failing GitHub Actions checks; propose fix plan before implementation.
|
||||||
|
- status: done
|
||||||
|
- last_update_utc: 2026-02-22T06:11:15Z
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
- verify auth + resolve target PR/run
|
||||||
|
- collect failing check logs
|
||||||
|
- summarize root cause + propose fix plan
|
||||||
|
|
||||||
|
## Planned Files
|
||||||
|
- docs/subagents/INDEX.md
|
||||||
|
- docs/subagents/collaboration.md
|
||||||
|
- docs/subagents/agents/codex-gh-fix-ci-20260222T054631Z-m4t8.md
|
||||||
|
|
||||||
|
## Assumptions
|
||||||
|
- no branch PR; fallback to latest failed Actions runs on `main`
|
||||||
|
|
||||||
|
## Completed
|
||||||
|
- verified GH auth + inspected latest failed CI run on `main`
|
||||||
|
- removed `scripts/check-file-budgets.ts`
|
||||||
|
- removed `check:file-budgets*` npm scripts
|
||||||
|
- removed file-budget command from CI guardrail step
|
||||||
|
- updated active docs pages to stop referencing removed command
|
||||||
@@ -89,3 +89,5 @@ Shared notes. Append-only.
|
|||||||
- [2026-02-22T03:01:38Z] [codex-task101-docs-archive-20260222T024156Z-hneu|codex-task101-docs-archive] completed TASK-101: canonicalized runtime architecture wording in `docs/architecture.md`, trimmed duplicated architecture bullets in `docs/development.md`, converted `docs/structure-roadmap.md` to archival notice, `bun run docs:build` passed, and backlog TASK-101 finalized Done with AC/DoD evidence.
|
- [2026-02-22T03:01:38Z] [codex-task101-docs-archive-20260222T024156Z-hneu|codex-task101-docs-archive] completed TASK-101: canonicalized runtime architecture wording in `docs/architecture.md`, trimmed duplicated architecture bullets in `docs/development.md`, converted `docs/structure-roadmap.md` to archival notice, `bun run docs:build` passed, and backlog TASK-101 finalized Done with AC/DoD evidence.
|
||||||
- [2026-02-22T03:32:21Z] [opencode-task100-dead-code-prune-20260222T033155Z-qenz|opencode-task100-dead-code-prune] starting TASK-100 via Backlog MCP + writing-plans/executing-plans; expected scope dead-code candidate report, safe removals, regression tests/gates, and backlog evidence updates.
|
- [2026-02-22T03:32:21Z] [opencode-task100-dead-code-prune-20260222T033155Z-qenz|opencode-task100-dead-code-prune] starting TASK-100 via Backlog MCP + writing-plans/executing-plans; expected scope dead-code candidate report, safe removals, regression tests/gates, and backlog evidence updates.
|
||||||
- [2026-02-22T04:00:41Z] [opencode-task100-dead-code-prune-20260222T033155Z-qenz|opencode-task100-dead-code-prune] completed TASK-100 execution pass: removed confirmed dead imports/helpers/exports across Anki/core/renderer/provider modules, added dead-code report at `docs/reports/2026-02-22-task-100-dead-code-report.md`, verified build + core/config src tests + file-budget checks, and prepared Backlog finalization updates.
|
- [2026-02-22T04:00:41Z] [opencode-task100-dead-code-prune-20260222T033155Z-qenz|opencode-task100-dead-code-prune] completed TASK-100 execution pass: removed confirmed dead imports/helpers/exports across Anki/core/renderer/provider modules, added dead-code report at `docs/reports/2026-02-22-task-100-dead-code-report.md`, verified build + core/config src tests + file-budget checks, and prepared Backlog finalization updates.
|
||||||
|
- [2026-02-22T05:46:31Z] [codex-gh-fix-ci-20260222T054631Z-m4t8|codex-gh-fix-ci] starting CI triage via gh-fix-ci skill; no branch PR found for `main`, falling back to latest failed GitHub Actions runs on `main` for root-cause summary + fix plan.
|
||||||
|
- [2026-02-22T06:11:15Z] [codex-gh-fix-ci-20260222T054631Z-m4t8|codex-gh-fix-ci] completed requested change: removed file-budget guardrail script + package scripts + CI invocation; retained main-fanin/runtime-cycle strict checks and updated active docs commands.
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
"docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort",
|
"docs:preview": "VITE_EXTRA_EXTENSIONS=jsonc vitepress preview docs --host 0.0.0.0 --port 4173 --strictPort",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"format:check": "prettier --check .",
|
"format:check": "prettier --check .",
|
||||||
"check:file-budgets": "bun run scripts/check-file-budgets.ts",
|
|
||||||
"check:file-budgets:strict": "bun run scripts/check-file-budgets.ts --strict",
|
|
||||||
"check:main-fanin": "bun run scripts/check-main-runtime-fanin.ts",
|
"check:main-fanin": "bun run scripts/check-main-runtime-fanin.ts",
|
||||||
"check:main-fanin:strict": "bun run scripts/check-main-runtime-fanin.ts --strict",
|
"check:main-fanin:strict": "bun run scripts/check-main-runtime-fanin.ts --strict",
|
||||||
"check:runtime-cycles": "bun run scripts/check-runtime-cycles.ts",
|
"check:runtime-cycles": "bun run scripts/check-runtime-cycles.ts",
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
import { spawnSync } from 'node:child_process';
|
|
||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
type FileBudgetResult = {
|
|
||||||
file: string;
|
|
||||||
lines: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type HotspotBudget = {
|
|
||||||
file: string;
|
|
||||||
baseline: number;
|
|
||||||
limit: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type HotspotStatus = HotspotBudget & {
|
|
||||||
lines: number;
|
|
||||||
delta: number;
|
|
||||||
overLimit: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_LINE_LIMIT = 500;
|
|
||||||
const TARGET_DIRS = ['src', 'launcher'];
|
|
||||||
const TARGET_EXTENSIONS = new Set(['.ts']);
|
|
||||||
const IGNORE_NAMES = new Set(['.DS_Store']);
|
|
||||||
const HOTSPOT_BUDGETS: readonly HotspotBudget[] = [
|
|
||||||
{ file: 'src/main.ts', baseline: 2978, limit: 3150 },
|
|
||||||
{ file: 'src/config/service.ts', baseline: 116, limit: 140 },
|
|
||||||
{ file: 'src/core/services/tokenizer.ts', baseline: 232, limit: 260 },
|
|
||||||
{ file: 'launcher/main.ts', baseline: 101, limit: 130 },
|
|
||||||
{ file: 'src/config/resolve.ts', baseline: 33, limit: 55 },
|
|
||||||
{ file: 'src/main/runtime/composers/contracts.ts', baseline: 13, limit: 30 },
|
|
||||||
];
|
|
||||||
|
|
||||||
function parseArgs(argv: string[]): { strict: boolean; limit: number; enforceGlobal: boolean } {
|
|
||||||
let strict = false;
|
|
||||||
let limit = DEFAULT_LINE_LIMIT;
|
|
||||||
let enforceGlobal = false;
|
|
||||||
|
|
||||||
for (let i = 0; i < argv.length; i += 1) {
|
|
||||||
const arg = argv[i];
|
|
||||||
if (arg === '--strict') {
|
|
||||||
strict = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === '--enforce-global') {
|
|
||||||
enforceGlobal = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (arg === '--limit') {
|
|
||||||
const raw = argv[i + 1];
|
|
||||||
const parsed = Number.parseInt(raw ?? '', 10);
|
|
||||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
||||||
throw new Error(`Invalid --limit value: ${raw ?? '<missing>'}`);
|
|
||||||
}
|
|
||||||
limit = parsed;
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { strict, limit, enforceGlobal };
|
|
||||||
}
|
|
||||||
|
|
||||||
function resolveFilesWithRipgrep(): string[] {
|
|
||||||
const rg = spawnSync('rg', ['--files', ...TARGET_DIRS], {
|
|
||||||
cwd: process.cwd(),
|
|
||||||
encoding: 'utf8',
|
|
||||||
});
|
|
||||||
if (rg.status !== 0) {
|
|
||||||
throw new Error(`rg --files failed:\n${rg.stderr || rg.stdout}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rg.stdout
|
|
||||||
.split('\n')
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.filter((line) => line.length > 0)
|
|
||||||
.filter((file) => TARGET_EXTENSIONS.has(path.extname(file)))
|
|
||||||
.filter((file) => !IGNORE_NAMES.has(path.basename(file)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function countLines(content: string): number {
|
|
||||||
if (content.length === 0) return 0;
|
|
||||||
return content.split('\n').length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectOverBudgetFiles(files: string[], limit: number): FileBudgetResult[] {
|
|
||||||
const results: FileBudgetResult[] = [];
|
|
||||||
for (const file of files) {
|
|
||||||
const content = fs.readFileSync(file, 'utf8');
|
|
||||||
const lines = countLines(content);
|
|
||||||
if (lines > limit) {
|
|
||||||
results.push({ file, lines });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results.sort((a, b) => b.lines - a.lines || a.file.localeCompare(b.file));
|
|
||||||
}
|
|
||||||
|
|
||||||
function collectHotspotStatuses(hotspots: readonly HotspotBudget[]): HotspotStatus[] {
|
|
||||||
return hotspots.map((hotspot) => {
|
|
||||||
const content = fs.readFileSync(hotspot.file, 'utf8');
|
|
||||||
const lines = countLines(content);
|
|
||||||
return {
|
|
||||||
...hotspot,
|
|
||||||
lines,
|
|
||||||
delta: lines - hotspot.baseline,
|
|
||||||
overLimit: lines > hotspot.limit,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function printGlobalReport(
|
|
||||||
overBudget: FileBudgetResult[],
|
|
||||||
limit: number,
|
|
||||||
strict: boolean,
|
|
||||||
enforceGlobal: boolean,
|
|
||||||
): void {
|
|
||||||
const mode = strict ? 'strict' : 'warning';
|
|
||||||
if (overBudget.length === 0) {
|
|
||||||
console.log(`[OK] file budget check (${mode}) — no files over ${limit} LOC`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const heading = strict && enforceGlobal ? '[FAIL]' : '[WARN]';
|
|
||||||
console.log(
|
|
||||||
`${heading} file budget check (${mode}) — ${overBudget.length} files over ${limit} LOC`,
|
|
||||||
);
|
|
||||||
for (const item of overBudget) {
|
|
||||||
console.log(` - ${item.file}: ${item.lines} LOC`);
|
|
||||||
}
|
|
||||||
console.log(' Hint: split by runtime/domain boundaries; keep composition roots thin.');
|
|
||||||
}
|
|
||||||
|
|
||||||
function printHotspotReport(hotspots: readonly HotspotStatus[], strict: boolean): void {
|
|
||||||
const mode = strict ? 'strict' : 'warning';
|
|
||||||
const overLimit = hotspots.filter((hotspot) => hotspot.overLimit);
|
|
||||||
|
|
||||||
if (overLimit.length === 0) {
|
|
||||||
console.log(`[OK] hotspot budget check (${mode}) — no hotspots over limit`);
|
|
||||||
} else {
|
|
||||||
const heading = strict ? '[FAIL]' : '[WARN]';
|
|
||||||
console.log(
|
|
||||||
`${heading} hotspot budget check (${mode}) — ${overLimit.length} hotspots over limit`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const hotspot of hotspots) {
|
|
||||||
const status = hotspot.overLimit ? 'OVER' : 'OK';
|
|
||||||
const delta = hotspot.delta >= 0 ? `+${hotspot.delta}` : `${hotspot.delta}`;
|
|
||||||
console.log(
|
|
||||||
` - [${status}] ${hotspot.file}: baseline=${hotspot.baseline}, limit=${hotspot.limit}, current=${hotspot.lines}, delta=${delta}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function main(): void {
|
|
||||||
const { strict, limit, enforceGlobal } = parseArgs(process.argv.slice(2));
|
|
||||||
const files = resolveFilesWithRipgrep();
|
|
||||||
const overBudget = collectOverBudgetFiles(files, limit);
|
|
||||||
const hotspotStatuses = collectHotspotStatuses(HOTSPOT_BUDGETS);
|
|
||||||
|
|
||||||
printGlobalReport(overBudget, limit, strict, enforceGlobal);
|
|
||||||
printHotspotReport(hotspotStatuses, strict);
|
|
||||||
|
|
||||||
const overGlobalLimit = overBudget.length > 0;
|
|
||||||
const hotspotOverLimit = hotspotStatuses.some((hotspot) => hotspot.overLimit);
|
|
||||||
const strictFailure = hotspotOverLimit || (strict && enforceGlobal && overGlobalLimit);
|
|
||||||
|
|
||||||
if (strict && strictFailure) {
|
|
||||||
process.exitCode = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
Reference in New Issue
Block a user