@coldtea/abide 0.0.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.
- package/LICENSE +21 -0
- package/README.md +10 -0
- package/dist/abide-hook.d.ts +2 -0
- package/dist/abide-hook.d.ts.map +1 -0
- package/dist/abide-hook.js +23 -0
- package/dist/abide-hook.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +64 -0
- package/dist/bin.js.map +1 -0
- package/dist/commands/audit.d.ts +3 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +79 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/bench.d.ts +3 -0
- package/dist/commands/bench.d.ts.map +1 -0
- package/dist/commands/bench.js +206 -0
- package/dist/commands/bench.js.map +1 -0
- package/dist/commands/calibrate.d.ts +3 -0
- package/dist/commands/calibrate.d.ts.map +1 -0
- package/dist/commands/calibrate.js +159 -0
- package/dist/commands/calibrate.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +106 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/compile.d.ts +3 -0
- package/dist/commands/compile.d.ts.map +1 -0
- package/dist/commands/compile.js +116 -0
- package/dist/commands/compile.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +93 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +58 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/replay.d.ts +4 -0
- package/dist/commands/replay.d.ts.map +1 -0
- package/dist/commands/replay.js +118 -0
- package/dist/commands/replay.js.map +1 -0
- package/dist/commands/report.d.ts +5 -0
- package/dist/commands/report.d.ts.map +1 -0
- package/dist/commands/report.js +78 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/rubric.d.ts +3 -0
- package/dist/commands/rubric.d.ts.map +1 -0
- package/dist/commands/rubric.js +40 -0
- package/dist/commands/rubric.js.map +1 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +31 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/hooks/postToolUse.d.ts +3 -0
- package/dist/hooks/postToolUse.d.ts.map +1 -0
- package/dist/hooks/postToolUse.js +145 -0
- package/dist/hooks/postToolUse.js.map +1 -0
- package/dist/hooks/sessionStart.d.ts +11 -0
- package/dist/hooks/sessionStart.d.ts.map +1 -0
- package/dist/hooks/sessionStart.js +83 -0
- package/dist/hooks/sessionStart.js.map +1 -0
- package/dist/hooks/stop.d.ts +26 -0
- package/dist/hooks/stop.d.ts.map +1 -0
- package/dist/hooks/stop.js +227 -0
- package/dist/hooks/stop.js.map +1 -0
- package/dist/hooks/turnStart.d.ts +8 -0
- package/dist/hooks/turnStart.d.ts.map +1 -0
- package/dist/hooks/turnStart.js +40 -0
- package/dist/hooks/turnStart.js.map +1 -0
- package/dist/lib/applyPatch.d.ts +22 -0
- package/dist/lib/applyPatch.d.ts.map +1 -0
- package/dist/lib/applyPatch.js +63 -0
- package/dist/lib/applyPatch.js.map +1 -0
- package/dist/lib/audit.d.ts +40 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/audit.js +215 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/band.d.ts +20 -0
- package/dist/lib/band.d.ts.map +1 -0
- package/dist/lib/band.js +40 -0
- package/dist/lib/band.js.map +1 -0
- package/dist/lib/calibration.d.ts +13 -0
- package/dist/lib/calibration.d.ts.map +1 -0
- package/dist/lib/calibration.js +25 -0
- package/dist/lib/calibration.js.map +1 -0
- package/dist/lib/checkRunner.d.ts +34 -0
- package/dist/lib/checkRunner.d.ts.map +1 -0
- package/dist/lib/checkRunner.js +76 -0
- package/dist/lib/checkRunner.js.map +1 -0
- package/dist/lib/claude.d.ts +4 -0
- package/dist/lib/claude.d.ts.map +1 -0
- package/dist/lib/claude.js +28 -0
- package/dist/lib/claude.js.map +1 -0
- package/dist/lib/compilePrompt.d.ts +22 -0
- package/dist/lib/compilePrompt.d.ts.map +1 -0
- package/dist/lib/compilePrompt.js +33 -0
- package/dist/lib/compilePrompt.js.map +1 -0
- package/dist/lib/constants.d.ts +33 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +33 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/coverage.d.ts +7 -0
- package/dist/lib/coverage.d.ts.map +1 -0
- package/dist/lib/coverage.js +17 -0
- package/dist/lib/coverage.js.map +1 -0
- package/dist/lib/credentials.d.ts +27 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +89 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/diff.d.ts +30 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +139 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/events.d.ts +5 -0
- package/dist/lib/events.d.ts.map +1 -0
- package/dist/lib/events.js +38 -0
- package/dist/lib/events.js.map +1 -0
- package/dist/lib/git.d.ts +37 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +163 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/hookRunner.d.ts +10 -0
- package/dist/lib/hookRunner.d.ts.map +1 -0
- package/dist/lib/hookRunner.js +52 -0
- package/dist/lib/hookRunner.js.map +1 -0
- package/dist/lib/hosts.d.ts +20 -0
- package/dist/lib/hosts.d.ts.map +1 -0
- package/dist/lib/hosts.js +101 -0
- package/dist/lib/hosts.js.map +1 -0
- package/dist/lib/jev.d.ts +24 -0
- package/dist/lib/jev.d.ts.map +1 -0
- package/dist/lib/jev.js +169 -0
- package/dist/lib/jev.js.map +1 -0
- package/dist/lib/lintConfig.d.ts +3 -0
- package/dist/lib/lintConfig.d.ts.map +1 -0
- package/dist/lib/lintConfig.js +55 -0
- package/dist/lib/lintConfig.js.map +1 -0
- package/dist/lib/loadRules.d.ts +12 -0
- package/dist/lib/loadRules.d.ts.map +1 -0
- package/dist/lib/loadRules.js +31 -0
- package/dist/lib/loadRules.js.map +1 -0
- package/dist/lib/opencodePlugin.d.ts +10 -0
- package/dist/lib/opencodePlugin.d.ts.map +1 -0
- package/dist/lib/opencodePlugin.js +36 -0
- package/dist/lib/opencodePlugin.js.map +1 -0
- package/dist/lib/output.d.ts +6 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +35 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/packageRoot.d.ts +12 -0
- package/dist/lib/packageRoot.d.ts.map +1 -0
- package/dist/lib/packageRoot.js +50 -0
- package/dist/lib/packageRoot.js.map +1 -0
- package/dist/lib/paths.d.ts +24 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +75 -0
- package/dist/lib/paths.js.map +1 -0
- package/dist/lib/reason.d.ts +11 -0
- package/dist/lib/reason.d.ts.map +1 -0
- package/dist/lib/reason.js +31 -0
- package/dist/lib/reason.js.map +1 -0
- package/dist/lib/regularFile.d.ts +26 -0
- package/dist/lib/regularFile.d.ts.map +1 -0
- package/dist/lib/regularFile.js +77 -0
- package/dist/lib/regularFile.js.map +1 -0
- package/dist/lib/replay.d.ts +74 -0
- package/dist/lib/replay.d.ts.map +1 -0
- package/dist/lib/replay.js +250 -0
- package/dist/lib/replay.js.map +1 -0
- package/dist/lib/replayCodex.d.ts +6 -0
- package/dist/lib/replayCodex.d.ts.map +1 -0
- package/dist/lib/replayCodex.js +132 -0
- package/dist/lib/replayCodex.js.map +1 -0
- package/dist/lib/replayOpencode.d.ts +15 -0
- package/dist/lib/replayOpencode.d.ts.map +1 -0
- package/dist/lib/replayOpencode.js +154 -0
- package/dist/lib/replayOpencode.js.map +1 -0
- package/dist/lib/rubricFile.d.ts +31 -0
- package/dist/lib/rubricFile.d.ts.map +1 -0
- package/dist/lib/rubricFile.js +75 -0
- package/dist/lib/rubricFile.js.map +1 -0
- package/dist/lib/scope.d.ts +4 -0
- package/dist/lib/scope.d.ts.map +1 -0
- package/dist/lib/scope.js +14 -0
- package/dist/lib/scope.js.map +1 -0
- package/dist/lib/session.d.ts +48 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +182 -0
- package/dist/lib/session.js.map +1 -0
- package/dist/lib/settings.d.ts +26 -0
- package/dist/lib/settings.d.ts.map +1 -0
- package/dist/lib/settings.js +104 -0
- package/dist/lib/settings.js.map +1 -0
- package/dist/lib/sources.d.ts +28 -0
- package/dist/lib/sources.d.ts.map +1 -0
- package/dist/lib/sources.js +137 -0
- package/dist/lib/sources.js.map +1 -0
- package/dist/lib/stdin.d.ts +3 -0
- package/dist/lib/stdin.d.ts.map +1 -0
- package/dist/lib/stdin.js +24 -0
- package/dist/lib/stdin.js.map +1 -0
- package/dist/lib/transcript.d.ts +3 -0
- package/dist/lib/transcript.d.ts.map +1 -0
- package/dist/lib/transcript.js +57 -0
- package/dist/lib/transcript.js.map +1 -0
- package/dist/lib/ui.d.ts +8 -0
- package/dist/lib/ui.d.ts.map +1 -0
- package/dist/lib/ui.js +34 -0
- package/dist/lib/ui.js.map +1 -0
- package/dist/ui/components/Buckets.d.ts +6 -0
- package/dist/ui/components/Buckets.d.ts.map +1 -0
- package/dist/ui/components/Buckets.js +11 -0
- package/dist/ui/components/Buckets.js.map +1 -0
- package/dist/ui/components/Callout.d.ts +11 -0
- package/dist/ui/components/Callout.d.ts.map +1 -0
- package/dist/ui/components/Callout.js +10 -0
- package/dist/ui/components/Callout.js.map +1 -0
- package/dist/ui/components/Checklist.d.ts +10 -0
- package/dist/ui/components/Checklist.d.ts.map +1 -0
- package/dist/ui/components/Checklist.js +8 -0
- package/dist/ui/components/Checklist.js.map +1 -0
- package/dist/ui/components/Columns.d.ts +28 -0
- package/dist/ui/components/Columns.d.ts.map +1 -0
- package/dist/ui/components/Columns.js +20 -0
- package/dist/ui/components/Columns.js.map +1 -0
- package/dist/ui/components/Header.d.ts +9 -0
- package/dist/ui/components/Header.d.ts.map +1 -0
- package/dist/ui/components/Header.js +7 -0
- package/dist/ui/components/Header.js.map +1 -0
- package/dist/ui/components/Live.d.ts +16 -0
- package/dist/ui/components/Live.d.ts.map +1 -0
- package/dist/ui/components/Live.js +40 -0
- package/dist/ui/components/Live.js.map +1 -0
- package/dist/ui/components/RuleTable.d.ts +15 -0
- package/dist/ui/components/RuleTable.d.ts.map +1 -0
- package/dist/ui/components/RuleTable.js +81 -0
- package/dist/ui/components/RuleTable.js.map +1 -0
- package/dist/ui/components/Section.d.ts +10 -0
- package/dist/ui/components/Section.d.ts.map +1 -0
- package/dist/ui/components/Section.js +8 -0
- package/dist/ui/components/Section.js.map +1 -0
- package/dist/ui/components/Verdicts.d.ts +9 -0
- package/dist/ui/components/Verdicts.d.ts.map +1 -0
- package/dist/ui/components/Verdicts.js +12 -0
- package/dist/ui/components/Verdicts.js.map +1 -0
- package/dist/ui/render.d.ts +20 -0
- package/dist/ui/render.d.ts.map +1 -0
- package/dist/ui/render.js +32 -0
- package/dist/ui/render.js.map +1 -0
- package/dist/ui/theme.d.ts +37 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +63 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/views/AuditView.d.ts +18 -0
- package/dist/ui/views/AuditView.d.ts.map +1 -0
- package/dist/ui/views/AuditView.js +48 -0
- package/dist/ui/views/AuditView.js.map +1 -0
- package/dist/ui/views/BenchView.d.ts +33 -0
- package/dist/ui/views/BenchView.d.ts.map +1 -0
- package/dist/ui/views/BenchView.js +19 -0
- package/dist/ui/views/BenchView.js.map +1 -0
- package/dist/ui/views/CalibrateView.d.ts +26 -0
- package/dist/ui/views/CalibrateView.d.ts.map +1 -0
- package/dist/ui/views/CalibrateView.js +42 -0
- package/dist/ui/views/CalibrateView.js.map +1 -0
- package/dist/ui/views/CheckView.d.ts +19 -0
- package/dist/ui/views/CheckView.d.ts.map +1 -0
- package/dist/ui/views/CheckView.js +15 -0
- package/dist/ui/views/CheckView.js.map +1 -0
- package/dist/ui/views/ErrorView.d.ts +4 -0
- package/dist/ui/views/ErrorView.d.ts.map +1 -0
- package/dist/ui/views/ErrorView.js +26 -0
- package/dist/ui/views/ErrorView.js.map +1 -0
- package/dist/ui/views/HelpView.d.ts +2 -0
- package/dist/ui/views/HelpView.d.ts.map +1 -0
- package/dist/ui/views/HelpView.js +25 -0
- package/dist/ui/views/HelpView.js.map +1 -0
- package/dist/ui/views/InitView.d.ts +27 -0
- package/dist/ui/views/InitView.d.ts.map +1 -0
- package/dist/ui/views/InitView.js +24 -0
- package/dist/ui/views/InitView.js.map +1 -0
- package/dist/ui/views/ReplayView.d.ts +16 -0
- package/dist/ui/views/ReplayView.d.ts.map +1 -0
- package/dist/ui/views/ReplayView.js +44 -0
- package/dist/ui/views/ReplayView.js.map +1 -0
- package/dist/ui/views/ReportView.d.ts +14 -0
- package/dist/ui/views/ReportView.d.ts.map +1 -0
- package/dist/ui/views/ReportView.js +86 -0
- package/dist/ui/views/ReportView.js.map +1 -0
- package/dist/ui/views/RubricView.d.ts +18 -0
- package/dist/ui/views/RubricView.d.ts.map +1 -0
- package/dist/ui/views/RubricView.js +18 -0
- package/dist/ui/views/RubricView.js.map +1 -0
- package/opencode/abide.mjs +246 -0
- package/package.json +50 -0
- package/skills/abide-compile/SKILL.md +173 -0
- package/src/abide-hook.ts +23 -0
- package/src/bin.ts +64 -0
- package/src/commands/audit.ts +97 -0
- package/src/commands/bench.ts +226 -0
- package/src/commands/calibrate.ts +165 -0
- package/src/commands/check.ts +114 -0
- package/src/commands/compile.ts +127 -0
- package/src/commands/init.ts +109 -0
- package/src/commands/login.ts +61 -0
- package/src/commands/replay.ts +148 -0
- package/src/commands/report.ts +83 -0
- package/src/commands/rubric.ts +45 -0
- package/src/commands/uninstall.ts +33 -0
- package/src/hooks/postToolUse.ts +171 -0
- package/src/hooks/sessionStart.ts +100 -0
- package/src/hooks/stop.ts +275 -0
- package/src/hooks/turnStart.ts +36 -0
- package/src/lib/applyPatch.ts +75 -0
- package/src/lib/audit.ts +265 -0
- package/src/lib/band.ts +49 -0
- package/src/lib/calibration.ts +35 -0
- package/src/lib/checkRunner.ts +124 -0
- package/src/lib/claude.ts +35 -0
- package/src/lib/compilePrompt.ts +67 -0
- package/src/lib/constants.ts +39 -0
- package/src/lib/coverage.ts +21 -0
- package/src/lib/credentials.ts +104 -0
- package/src/lib/diff.ts +209 -0
- package/src/lib/events.ts +34 -0
- package/src/lib/git.ts +206 -0
- package/src/lib/hookRunner.ts +55 -0
- package/src/lib/hosts.ts +112 -0
- package/src/lib/jev.ts +223 -0
- package/src/lib/lintConfig.ts +53 -0
- package/src/lib/loadRules.ts +39 -0
- package/src/lib/opencodePlugin.ts +38 -0
- package/src/lib/output.ts +44 -0
- package/src/lib/packageRoot.ts +48 -0
- package/src/lib/paths.ts +96 -0
- package/src/lib/reason.ts +46 -0
- package/src/lib/regularFile.ts +96 -0
- package/src/lib/replay.ts +314 -0
- package/src/lib/replayCodex.ts +130 -0
- package/src/lib/replayOpencode.ts +186 -0
- package/src/lib/rubricFile.ts +99 -0
- package/src/lib/scope.ts +18 -0
- package/src/lib/session.ts +223 -0
- package/src/lib/settings.ts +112 -0
- package/src/lib/sources.ts +154 -0
- package/src/lib/stdin.ts +23 -0
- package/src/lib/transcript.ts +49 -0
- package/src/lib/ui.ts +38 -0
- package/src/ui/components/Buckets.tsx +30 -0
- package/src/ui/components/Callout.tsx +26 -0
- package/src/ui/components/Checklist.tsx +29 -0
- package/src/ui/components/Columns.tsx +62 -0
- package/src/ui/components/Header.tsx +35 -0
- package/src/ui/components/Live.tsx +73 -0
- package/src/ui/components/RuleTable.tsx +94 -0
- package/src/ui/components/Section.tsx +24 -0
- package/src/ui/components/Verdicts.tsx +40 -0
- package/src/ui/render.tsx +51 -0
- package/src/ui/theme.ts +65 -0
- package/src/ui/views/AuditView.tsx +140 -0
- package/src/ui/views/BenchView.tsx +87 -0
- package/src/ui/views/CalibrateView.tsx +108 -0
- package/src/ui/views/CheckView.tsx +56 -0
- package/src/ui/views/ErrorView.tsx +32 -0
- package/src/ui/views/HelpView.tsx +51 -0
- package/src/ui/views/InitView.tsx +103 -0
- package/src/ui/views/ReplayView.tsx +100 -0
- package/src/ui/views/ReportView.tsx +225 -0
- package/src/ui/views/RubricView.tsx +82 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import { AbideError, assertNever, hostSchema, type Host } from "@coldtea/abide-schema";
|
|
6
|
+
import { hasApiKey, NO_KEY_HINT } from "../lib/credentials.js";
|
|
7
|
+
import { hostLabel } from "../lib/hosts.js";
|
|
8
|
+
import { loadRules } from "../lib/loadRules.js";
|
|
9
|
+
import { findRepoRoot } from "../lib/paths.js";
|
|
10
|
+
import {
|
|
11
|
+
driftByTurn,
|
|
12
|
+
parseTranscript,
|
|
13
|
+
replaySessions,
|
|
14
|
+
tallyRules,
|
|
15
|
+
type ReplaySession,
|
|
16
|
+
} from "../lib/replay.js";
|
|
17
|
+
import { codexSessionsDir, codexSessionsFor } from "../lib/replayCodex.js";
|
|
18
|
+
import { opencodeDbPath, opencodeSessionsFor } from "../lib/replayOpencode.js";
|
|
19
|
+
import { say, usd } from "../lib/ui.js";
|
|
20
|
+
import { Header } from "../ui/components/Header.js";
|
|
21
|
+
import { showLive } from "../ui/render.js";
|
|
22
|
+
import { ReplayView, type ReplayData } from "../ui/views/ReplayView.js";
|
|
23
|
+
|
|
24
|
+
/** Claude Code names the transcript directory after the repo path. */
|
|
25
|
+
export const claudeProjectDir = (root: string): string =>
|
|
26
|
+
path.join(homedir(), ".claude", "projects", root.replace(/[/.]/g, "-"));
|
|
27
|
+
|
|
28
|
+
const transcriptFiles = (target: string): string[] => {
|
|
29
|
+
const stat = statSync(target);
|
|
30
|
+
if (stat.isFile()) return [target];
|
|
31
|
+
return readdirSync(target)
|
|
32
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
33
|
+
.map((name) => path.join(target, name))
|
|
34
|
+
.sort();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const inside = (root: string, dir: string): boolean => {
|
|
38
|
+
const rel = path.relative(root, dir);
|
|
39
|
+
return rel === "" || (!rel.startsWith("..") && !path.isAbsolute(rel));
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const sessionsFor = (host: Host, root: string, paths: readonly string[]): ReplaySession[] => {
|
|
43
|
+
switch (host) {
|
|
44
|
+
case "claude":
|
|
45
|
+
return (paths.length > 0 ? paths : [claudeProjectDir(root)])
|
|
46
|
+
.flatMap(transcriptFiles)
|
|
47
|
+
.map(parseTranscript)
|
|
48
|
+
.filter((s) => s.turns.length > 0 && inside(root, s.cwd));
|
|
49
|
+
case "codex":
|
|
50
|
+
return codexSessionsFor(root, paths[0] ?? codexSessionsDir());
|
|
51
|
+
case "opencode":
|
|
52
|
+
return opencodeSessionsFor(root, paths[0] ?? opencodeDbPath());
|
|
53
|
+
default:
|
|
54
|
+
return assertNever(host);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const runReplay = async (argv: string[]): Promise<number> => {
|
|
59
|
+
const { values, positionals } = parseArgs({
|
|
60
|
+
args: argv,
|
|
61
|
+
allowPositionals: true,
|
|
62
|
+
options: {
|
|
63
|
+
repo: { type: "string" },
|
|
64
|
+
concurrency: { type: "string", default: "3" },
|
|
65
|
+
"max-sessions": { type: "string" },
|
|
66
|
+
diffs: { type: "boolean", default: false },
|
|
67
|
+
json: { type: "boolean", default: false },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
const [first, ...rest] = positionals;
|
|
71
|
+
if (first === undefined)
|
|
72
|
+
throw new AbideError(
|
|
73
|
+
"HOST_UNKNOWN",
|
|
74
|
+
"name the agent whose sessions to replay: abide replay claude|codex|opencode [--repo <path>]",
|
|
75
|
+
);
|
|
76
|
+
const named = hostSchema.safeParse(first.toLowerCase());
|
|
77
|
+
// no agent name: positionals are Claude Code transcripts
|
|
78
|
+
const host: Host = named.success ? named.data : "claude";
|
|
79
|
+
const paths = named.success ? rest : positionals;
|
|
80
|
+
const root = findRepoRoot(values.repo ?? process.cwd());
|
|
81
|
+
if (!hasApiKey(root)) throw new AbideError("NO_API_KEY", NO_KEY_HINT);
|
|
82
|
+
const loaded = loadRules(root);
|
|
83
|
+
if (loaded.rules.length === 0)
|
|
84
|
+
throw new AbideError(
|
|
85
|
+
"RUBRIC_MISSING",
|
|
86
|
+
`no rubric in ${root} or ~/.abide; run abide compile there first`,
|
|
87
|
+
);
|
|
88
|
+
const cap = values["max-sessions"] === undefined ? Infinity : Number(values["max-sessions"]);
|
|
89
|
+
const sessions = sessionsFor(host, root, paths).slice(0, cap);
|
|
90
|
+
const editCount = sessions.reduce(
|
|
91
|
+
(n, s) => n + s.turns.reduce((m, t) => m + t.edits.length, 0),
|
|
92
|
+
0,
|
|
93
|
+
);
|
|
94
|
+
const concurrency = Math.max(1, Number(values.concurrency));
|
|
95
|
+
|
|
96
|
+
const run = async (progress: (label: string) => void): Promise<ReplayData> => {
|
|
97
|
+
const started = performance.now();
|
|
98
|
+
const result = await replaySessions(
|
|
99
|
+
sessions,
|
|
100
|
+
loaded.rules,
|
|
101
|
+
loaded.thresholds,
|
|
102
|
+
concurrency,
|
|
103
|
+
(done, total, spend) => progress(`${done} of ${total} edits · about ${usd(spend)}`),
|
|
104
|
+
values.diffs,
|
|
105
|
+
);
|
|
106
|
+
return {
|
|
107
|
+
root,
|
|
108
|
+
host: hostLabel(host),
|
|
109
|
+
sessions: sessions.length,
|
|
110
|
+
edits: editCount,
|
|
111
|
+
result,
|
|
112
|
+
drift: driftByTurn(result.edits),
|
|
113
|
+
tallies: tallyRules(result, loaded.rules),
|
|
114
|
+
spendUsd:
|
|
115
|
+
result.edits.reduce((s, e) => s + e.costUsd, 0) +
|
|
116
|
+
result.turns.reduce((s, t) => s + t.costUsd, 0),
|
|
117
|
+
elapsedMs: performance.now() - started,
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (values.json) {
|
|
122
|
+
const data = await run(() => {});
|
|
123
|
+
say(
|
|
124
|
+
JSON.stringify({
|
|
125
|
+
root,
|
|
126
|
+
host,
|
|
127
|
+
sessions: data.sessions,
|
|
128
|
+
edits: data.edits,
|
|
129
|
+
spendUsd: data.spendUsd,
|
|
130
|
+
elapsedMs: data.elapsedMs,
|
|
131
|
+
drift: data.drift,
|
|
132
|
+
byRule: data.tallies,
|
|
133
|
+
editResults: data.result.edits,
|
|
134
|
+
turnResults: data.result.turns,
|
|
135
|
+
}),
|
|
136
|
+
);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
return showLive<ReplayData>({
|
|
140
|
+
header: Header({
|
|
141
|
+
command: "replay",
|
|
142
|
+
where: root,
|
|
143
|
+
note: `${hostLabel(host)}: ${sessions.length} sessions, ${editCount} edits, ${concurrency} at a time`,
|
|
144
|
+
}),
|
|
145
|
+
run,
|
|
146
|
+
done: (data) => ReplayView({ data }),
|
|
147
|
+
});
|
|
148
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import type { AbideEvent, Rule } from "@coldtea/abide-schema";
|
|
3
|
+
import { readEvents } from "../lib/events.js";
|
|
4
|
+
import { loadRules } from "../lib/loadRules.js";
|
|
5
|
+
import { findRepoRoot } from "../lib/paths.js";
|
|
6
|
+
import { say } from "../lib/ui.js";
|
|
7
|
+
import type { RuleStats } from "../ui/components/RuleTable.js";
|
|
8
|
+
import { showStatic } from "../ui/render.js";
|
|
9
|
+
import { ReportView, type ReportData } from "../ui/views/ReportView.js";
|
|
10
|
+
import { InitView } from "../ui/views/InitView.js";
|
|
11
|
+
|
|
12
|
+
const statsFrom = (events: readonly AbideEvent[]): Map<string, RuleStats> => {
|
|
13
|
+
const stats = new Map<string, RuleStats>();
|
|
14
|
+
for (const event of events) {
|
|
15
|
+
if (event.kind !== "check") continue;
|
|
16
|
+
for (const v of event.verdicts) {
|
|
17
|
+
const s = stats.get(v.ruleId) ?? { checks: 0, fired: 0, flagged: 0 };
|
|
18
|
+
s.checks += 1;
|
|
19
|
+
if (v.band === "act") s.fired += 1;
|
|
20
|
+
if (v.band === "flag") s.flagged += 1;
|
|
21
|
+
stats.set(v.ruleId, s);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return stats;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const MIN_CHECKS_TO_CALL_DEAD = 20;
|
|
28
|
+
|
|
29
|
+
export const collectReport = (root: string): ReportData | undefined => {
|
|
30
|
+
const loaded = loadRules(root);
|
|
31
|
+
if (loaded.rules.length === 0) return undefined;
|
|
32
|
+
const events = readEvents(root);
|
|
33
|
+
const stats = statsFrom(events);
|
|
34
|
+
// Dead means it never answers: no fire, no flag, and a calibration that
|
|
35
|
+
// never cleared confidently either. A rule at 0.02 on every check is not
|
|
36
|
+
// dead, it is a rule nobody has broken yet.
|
|
37
|
+
const dead: Rule[] = loaded.rules.filter((rule) => {
|
|
38
|
+
const s = stats.get(rule.id);
|
|
39
|
+
const middling = rule.calibration === undefined || rule.calibration.median >= 0.25;
|
|
40
|
+
return (
|
|
41
|
+
rule.status === "active" &&
|
|
42
|
+
rule.check.type === "model" &&
|
|
43
|
+
middling &&
|
|
44
|
+
s !== undefined &&
|
|
45
|
+
s.checks >= MIN_CHECKS_TO_CALL_DEAD &&
|
|
46
|
+
s.fired === 0 &&
|
|
47
|
+
s.flagged === 0
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
return { root, rules: loaded.rules, events, stats, dead, problems: loaded.problems };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/** Rules, calibration, and what has fired so far in this repository. */
|
|
54
|
+
export const runReport = async (argv: string[]): Promise<number> => {
|
|
55
|
+
const { values } = parseArgs({
|
|
56
|
+
args: argv,
|
|
57
|
+
options: { json: { type: "boolean", default: false } },
|
|
58
|
+
});
|
|
59
|
+
const root = findRepoRoot(process.cwd());
|
|
60
|
+
const data = collectReport(root);
|
|
61
|
+
if (data === undefined) {
|
|
62
|
+
if (values.json) {
|
|
63
|
+
say(JSON.stringify({ root, rules: [], events: [] }));
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
await showStatic(InitView({ data: { kind: "no-sources", root } }));
|
|
67
|
+
return 1;
|
|
68
|
+
}
|
|
69
|
+
if (values.json) {
|
|
70
|
+
say(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
root: data.root,
|
|
73
|
+
rules: data.rules,
|
|
74
|
+
stats: Object.fromEntries(data.stats),
|
|
75
|
+
dead: data.dead.map((r) => r.id),
|
|
76
|
+
events: data.events.length,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
await showStatic(ReportView({ data }));
|
|
82
|
+
return 0;
|
|
83
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { AbideError } from "@coldtea/abide-schema";
|
|
3
|
+
import { findRepoRoot, globalRubricPath, homeDir, rubricPath } from "../lib/paths.js";
|
|
4
|
+
import { fillSourceShas, readRubric, writeRubric } from "../lib/rubricFile.js";
|
|
5
|
+
import { showStatic } from "../ui/render.js";
|
|
6
|
+
import { RubricView } from "../ui/views/RubricView.js";
|
|
7
|
+
|
|
8
|
+
/** Parses the rubric the agent just wrote, fills in real source hashes, writes it back. */
|
|
9
|
+
export const runRubric = async (argv: string[]): Promise<number> => {
|
|
10
|
+
const { values, positionals } = parseArgs({
|
|
11
|
+
args: argv,
|
|
12
|
+
allowPositionals: true,
|
|
13
|
+
options: { global: { type: "boolean", default: false } },
|
|
14
|
+
});
|
|
15
|
+
const sub = positionals[0] ?? "validate";
|
|
16
|
+
if (sub !== "validate")
|
|
17
|
+
throw new AbideError(
|
|
18
|
+
"RUBRIC_INVALID",
|
|
19
|
+
`unknown rubric command "${sub}"; try: abide rubric validate`,
|
|
20
|
+
);
|
|
21
|
+
const root = values.global ? homeDir() : findRepoRoot(process.cwd());
|
|
22
|
+
const file = values.global ? globalRubricPath() : rubricPath(root);
|
|
23
|
+
const read = readRubric(file);
|
|
24
|
+
switch (read.kind) {
|
|
25
|
+
case "missing":
|
|
26
|
+
throw new AbideError("RUBRIC_MISSING", `${file} does not exist yet`);
|
|
27
|
+
case "invalid":
|
|
28
|
+
await showStatic(RubricView({ data: { kind: "invalid", root, file, issues: read.issues } }));
|
|
29
|
+
return 1;
|
|
30
|
+
case "ok": {
|
|
31
|
+
const { rubric, missing } = fillSourceShas(read.rubric, root);
|
|
32
|
+
const listed = new Set(rubric.sources.map((s) => s.path));
|
|
33
|
+
const orphaned = rubric.rules
|
|
34
|
+
.filter((r) => !listed.has(r.source.path))
|
|
35
|
+
.map((r) => `${r.id} (${r.source.path})`);
|
|
36
|
+
writeRubric(file, rubric);
|
|
37
|
+
await showStatic(
|
|
38
|
+
RubricView({ data: { kind: "ok", root, file, rules: rubric.rules, missing, orphaned } }),
|
|
39
|
+
);
|
|
40
|
+
return missing.length + orphaned.length > 0 ? 1 : 0;
|
|
41
|
+
}
|
|
42
|
+
default:
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { HOSTS } from "@coldtea/abide-schema";
|
|
3
|
+
import { hostLabel, parseHost, uninstallHost } from "../lib/hosts.js";
|
|
4
|
+
import { findRepoRoot } from "../lib/paths.js";
|
|
5
|
+
import { Callout } from "../ui/components/Callout.js";
|
|
6
|
+
import { showStatic } from "../ui/render.js";
|
|
7
|
+
|
|
8
|
+
export const runUninstall = async (argv: string[]): Promise<number> => {
|
|
9
|
+
const { values, positionals } = parseArgs({
|
|
10
|
+
args: argv,
|
|
11
|
+
allowPositionals: true,
|
|
12
|
+
options: { project: { type: "boolean", default: false } },
|
|
13
|
+
});
|
|
14
|
+
const root = findRepoRoot(process.cwd());
|
|
15
|
+
const hosts = positionals.length > 0 ? [...new Set(positionals.map(parseHost))] : [...HOSTS];
|
|
16
|
+
const removed = hosts.map((host) => ({
|
|
17
|
+
host,
|
|
18
|
+
count: uninstallHost(host, root, values.project),
|
|
19
|
+
}));
|
|
20
|
+
const touched = removed.filter((r) => r.count > 0);
|
|
21
|
+
await showStatic(
|
|
22
|
+
touched.length === 0
|
|
23
|
+
? Callout({
|
|
24
|
+
tone: "muted",
|
|
25
|
+
title: "No abide entries found for " + hosts.map(hostLabel).join(", "),
|
|
26
|
+
})
|
|
27
|
+
: Callout({
|
|
28
|
+
tone: "ok",
|
|
29
|
+
title: `Removed abide from ${touched.map((r) => hostLabel(r.host)).join(", ")}. Your rubric files are untouched.`,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
return 0;
|
|
33
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createBlobId,
|
|
3
|
+
createBlockKey,
|
|
4
|
+
isAbideError,
|
|
5
|
+
postToolUseInputSchema,
|
|
6
|
+
turnIdOf,
|
|
7
|
+
type HookOutput,
|
|
8
|
+
type Rule,
|
|
9
|
+
type Verdict,
|
|
10
|
+
} from "@coldtea/abide-schema";
|
|
11
|
+
import { runCheck, type CheckOutcome } from "../lib/checkRunner.js";
|
|
12
|
+
import { EDIT_CHECK_TIMEOUT_MS, MAX_BLOCKS_PER_RULE_PER_TURN } from "../lib/constants.js";
|
|
13
|
+
import { hasApiKey } from "../lib/credentials.js";
|
|
14
|
+
import { boundState, editsFromPostToolUse, type EditHunk } from "../lib/diff.js";
|
|
15
|
+
import { appendEvent } from "../lib/events.js";
|
|
16
|
+
import { loadRules } from "../lib/loadRules.js";
|
|
17
|
+
import { debug } from "../lib/output.js";
|
|
18
|
+
import { findRepoRoot, isExcludedPath, relativeToRoot } from "../lib/paths.js";
|
|
19
|
+
import { flagNotice, repairReason } from "../lib/reason.js";
|
|
20
|
+
import {
|
|
21
|
+
blockCount,
|
|
22
|
+
incrementBlock,
|
|
23
|
+
readPrompt,
|
|
24
|
+
recordBlockedFile,
|
|
25
|
+
recordChecked,
|
|
26
|
+
recordFileStart,
|
|
27
|
+
turnDir,
|
|
28
|
+
} from "../lib/session.js";
|
|
29
|
+
import { lastUserPrompt } from "../lib/transcript.js";
|
|
30
|
+
|
|
31
|
+
type Pair = { rule: Rule; verdict: Verdict };
|
|
32
|
+
|
|
33
|
+
type Checked = { edit: EditHunk; relative: string; outcome: CheckOutcome };
|
|
34
|
+
|
|
35
|
+
export const handlePostToolUse = async (raw: unknown): Promise<HookOutput> => {
|
|
36
|
+
const parsed = postToolUseInputSchema.safeParse(raw);
|
|
37
|
+
if (!parsed.success) return { kind: "silent" };
|
|
38
|
+
const input = parsed.data;
|
|
39
|
+
const started = performance.now();
|
|
40
|
+
const at = new Date().toISOString();
|
|
41
|
+
const all = editsFromPostToolUse(input);
|
|
42
|
+
const root = findRepoRoot(all[0]?.filePath ?? input.cwd);
|
|
43
|
+
const edits = all.filter((e) => !isExcludedPath(relativeToRoot(root, e.filePath)));
|
|
44
|
+
if (edits.length === 0) return { kind: "silent" };
|
|
45
|
+
|
|
46
|
+
const turn = turnDir(input.session_id, turnIdOf(input));
|
|
47
|
+
for (const edit of edits) recordFileStart(turn, edit.filePath, edit.original);
|
|
48
|
+
|
|
49
|
+
const loaded = loadRules(root);
|
|
50
|
+
for (const problem of loaded.problems) debug(problem);
|
|
51
|
+
if (loaded.rules.length === 0) return { kind: "silent" };
|
|
52
|
+
|
|
53
|
+
const checkable: { edit: EditHunk; relative: string; diff: string }[] = [];
|
|
54
|
+
for (const edit of edits) {
|
|
55
|
+
const relative = relativeToRoot(root, edit.filePath);
|
|
56
|
+
if (edit.text === undefined) {
|
|
57
|
+
appendEvent(root, {
|
|
58
|
+
kind: "skip",
|
|
59
|
+
at,
|
|
60
|
+
phase: "edit",
|
|
61
|
+
sessionId: input.session_id,
|
|
62
|
+
reason: "diff too large to compute in time",
|
|
63
|
+
files: [relative],
|
|
64
|
+
});
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const { text: diff } = boundState(edit.text);
|
|
68
|
+
if (diff.trim() !== "") checkable.push({ edit, relative, diff });
|
|
69
|
+
}
|
|
70
|
+
if (checkable.length === 0) return { kind: "silent" };
|
|
71
|
+
const files = checkable.map((c) => c.relative);
|
|
72
|
+
|
|
73
|
+
if (!hasApiKey(root)) {
|
|
74
|
+
appendEvent(root, {
|
|
75
|
+
kind: "skip",
|
|
76
|
+
at,
|
|
77
|
+
phase: "edit",
|
|
78
|
+
sessionId: input.session_id,
|
|
79
|
+
reason: "no api key",
|
|
80
|
+
files,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const task = lastUserPrompt(input.transcript_path ?? undefined) ?? readPrompt(turn);
|
|
85
|
+
let checked: Checked[];
|
|
86
|
+
try {
|
|
87
|
+
checked = await Promise.all(
|
|
88
|
+
checkable.map(async ({ edit, relative, diff }) => ({
|
|
89
|
+
edit,
|
|
90
|
+
relative,
|
|
91
|
+
outcome: await runCheck({
|
|
92
|
+
phase: "edit",
|
|
93
|
+
fileDiffs: [{ file: relative, text: diff }],
|
|
94
|
+
task,
|
|
95
|
+
rules: loaded.rules,
|
|
96
|
+
thresholds: loaded.thresholds,
|
|
97
|
+
timeoutMs: EDIT_CHECK_TIMEOUT_MS,
|
|
98
|
+
}),
|
|
99
|
+
})),
|
|
100
|
+
);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
appendEvent(root, {
|
|
103
|
+
kind: "error",
|
|
104
|
+
at,
|
|
105
|
+
phase: "edit",
|
|
106
|
+
sessionId: input.session_id,
|
|
107
|
+
code: isAbideError(error) ? error.code : "CHECK_FAILED",
|
|
108
|
+
message: error instanceof Error ? error.message : String(error),
|
|
109
|
+
latencyMs: Math.round(performance.now() - started),
|
|
110
|
+
});
|
|
111
|
+
return { kind: "silent" };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const { edit, relative } of checked) {
|
|
115
|
+
if (edit.after === null) continue;
|
|
116
|
+
recordChecked(turn, {
|
|
117
|
+
path: relative,
|
|
118
|
+
before: edit.original === null ? null : createBlobId(edit.original),
|
|
119
|
+
after: createBlobId(edit.after),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const byId = new Map(loaded.rules.map((r) => [r.id, r]));
|
|
124
|
+
const acting: Pair[] = [];
|
|
125
|
+
const flagged: Pair[] = [];
|
|
126
|
+
const actedOn: string[] = [];
|
|
127
|
+
for (const { relative, outcome } of checked) {
|
|
128
|
+
const pairs = (band: Verdict["band"]): Pair[] =>
|
|
129
|
+
outcome.verdicts.flatMap((verdict) => {
|
|
130
|
+
const rule = byId.get(verdict.ruleId);
|
|
131
|
+
return rule !== undefined && verdict.band === band ? [{ rule, verdict }] : [];
|
|
132
|
+
});
|
|
133
|
+
const actPairs = pairs("act");
|
|
134
|
+
const actingHere = actPairs.filter(
|
|
135
|
+
({ rule }) =>
|
|
136
|
+
blockCount(turn, createBlockKey(rule.id, relative)) < MAX_BLOCKS_PER_RULE_PER_TURN,
|
|
137
|
+
);
|
|
138
|
+
for (const { rule } of actingHere) incrementBlock(turn, createBlockKey(rule.id, relative));
|
|
139
|
+
if (actingHere.length > 0) {
|
|
140
|
+
actedOn.push(relative);
|
|
141
|
+
recordBlockedFile(turn, relative);
|
|
142
|
+
}
|
|
143
|
+
acting.push(...actingHere);
|
|
144
|
+
flagged.push(...pairs("flag"), ...actPairs.filter((p) => !actingHere.includes(p)));
|
|
145
|
+
|
|
146
|
+
appendEvent(root, {
|
|
147
|
+
kind: "check",
|
|
148
|
+
at,
|
|
149
|
+
phase: "edit",
|
|
150
|
+
sessionId: input.session_id,
|
|
151
|
+
promptId: turnIdOf(input),
|
|
152
|
+
files: [relative],
|
|
153
|
+
rules: outcome.modelRules.length,
|
|
154
|
+
latencyMs: Math.round(performance.now() - started),
|
|
155
|
+
modelLatencyMs: outcome.modelLatencyMs,
|
|
156
|
+
usage: outcome.usage,
|
|
157
|
+
verdicts: outcome.verdicts,
|
|
158
|
+
blocked: actingHere.length > 0,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const systemMessage = flagged.length > 0 ? flagNotice("edit", flagged, files) : undefined;
|
|
163
|
+
if (acting.length > 0) {
|
|
164
|
+
return {
|
|
165
|
+
kind: "block",
|
|
166
|
+
reason: repairReason("edit", acting, actedOn),
|
|
167
|
+
...(systemMessage === undefined ? {} : { systemMessage }),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return systemMessage === undefined ? { kind: "silent" } : { kind: "notice", systemMessage };
|
|
171
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { sessionStartInputSchema, type HookOutput } from "@coldtea/abide-schema";
|
|
2
|
+
import { compilePrompt, type CompileTarget } from "../lib/compilePrompt.js";
|
|
3
|
+
import { appendEvent } from "../lib/events.js";
|
|
4
|
+
import { hasApiKey } from "../lib/credentials.js";
|
|
5
|
+
import { findLintConfigs } from "../lib/lintConfig.js";
|
|
6
|
+
import { placeCompileSkill } from "../lib/packageRoot.js";
|
|
7
|
+
import { findRepoRoot, globalRubricPath, homeDir, rubricPath } from "../lib/paths.js";
|
|
8
|
+
import { readRubric } from "../lib/rubricFile.js";
|
|
9
|
+
import { pruneOldTurns } from "../lib/session.js";
|
|
10
|
+
import { checkStaleness, discoverGlobalSources, discoverProjectSources } from "../lib/sources.js";
|
|
11
|
+
|
|
12
|
+
export type CompilePlan = {
|
|
13
|
+
targets: CompileTarget[];
|
|
14
|
+
invalid: string[];
|
|
15
|
+
noSources: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** What, if anything, needs compiling for this repository and this machine. */
|
|
19
|
+
export const planCompile = (root: string): CompilePlan => {
|
|
20
|
+
const targets: CompileTarget[] = [];
|
|
21
|
+
const invalid: string[] = [];
|
|
22
|
+
|
|
23
|
+
const project = discoverProjectSources(root);
|
|
24
|
+
const projectRead = readRubric(rubricPath(root));
|
|
25
|
+
if (projectRead.kind === "invalid") {
|
|
26
|
+
invalid.push(`${projectRead.path}: ${projectRead.issues.slice(0, 3).join("; ")}`);
|
|
27
|
+
}
|
|
28
|
+
const projectRubric = projectRead.kind === "ok" ? projectRead.rubric : undefined;
|
|
29
|
+
const projectStale = checkStaleness(projectRubric, project, root);
|
|
30
|
+
if (
|
|
31
|
+
project.some((c) => c.required) &&
|
|
32
|
+
projectStale.status !== "fresh" &&
|
|
33
|
+
projectRead.kind !== "invalid"
|
|
34
|
+
) {
|
|
35
|
+
targets.push({
|
|
36
|
+
which: "project",
|
|
37
|
+
root,
|
|
38
|
+
candidates: project,
|
|
39
|
+
staleness: projectStale,
|
|
40
|
+
lintConfigs: findLintConfigs(root),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const global = discoverGlobalSources();
|
|
45
|
+
const globalRead = readRubric(globalRubricPath());
|
|
46
|
+
if (globalRead.kind === "invalid") {
|
|
47
|
+
invalid.push(`${globalRead.path}: ${globalRead.issues.slice(0, 3).join("; ")}`);
|
|
48
|
+
}
|
|
49
|
+
const globalRubric = globalRead.kind === "ok" ? globalRead.rubric : undefined;
|
|
50
|
+
const globalStale = checkStaleness(globalRubric, global, homeDir());
|
|
51
|
+
if (global.length > 0 && globalStale.status !== "fresh" && globalRead.kind !== "invalid") {
|
|
52
|
+
targets.push({
|
|
53
|
+
which: "global",
|
|
54
|
+
root: homeDir(),
|
|
55
|
+
candidates: global,
|
|
56
|
+
staleness: globalStale,
|
|
57
|
+
lintConfigs: [],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { targets, invalid, noSources: project.length === 0 && global.length === 0 };
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export const handleSessionStart = async (raw: unknown): Promise<HookOutput> => {
|
|
65
|
+
const parsed = sessionStartInputSchema.safeParse(raw);
|
|
66
|
+
if (!parsed.success) return { kind: "silent" };
|
|
67
|
+
const input = parsed.data;
|
|
68
|
+
const root = findRepoRoot(input.cwd);
|
|
69
|
+
pruneOldTurns();
|
|
70
|
+
|
|
71
|
+
const plan = planCompile(root);
|
|
72
|
+
const notices: string[] = [];
|
|
73
|
+
if (!hasApiKey(root)) {
|
|
74
|
+
notices.push(
|
|
75
|
+
"Abide: no API key was found, so edits are not being checked. Run abide login, or put TYPESAFE_AI_API_KEY in the environment or a .env at the repo root, then start a new session.",
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
for (const problem of plan.invalid) {
|
|
79
|
+
notices.push(`Abide: rubric could not be read and is being ignored: ${problem}`);
|
|
80
|
+
}
|
|
81
|
+
const systemMessage = notices.length > 0 ? notices.join("\n") : undefined;
|
|
82
|
+
|
|
83
|
+
if (plan.targets.length === 0) {
|
|
84
|
+
return systemMessage === undefined ? { kind: "silent" } : { kind: "notice", systemMessage };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
appendEvent(root, {
|
|
88
|
+
kind: "compile-needed",
|
|
89
|
+
at: new Date().toISOString(),
|
|
90
|
+
sessionId: input.session_id,
|
|
91
|
+
reason: plan.targets.map((t) => `${t.which}: ${t.staleness.status}`).join(", "),
|
|
92
|
+
sources: plan.targets.flatMap((t) => t.candidates.map((c) => c.path)),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
kind: "session-context",
|
|
97
|
+
additionalContext: compilePrompt(placeCompileSkill(root), plan.targets),
|
|
98
|
+
...(systemMessage === undefined ? {} : { systemMessage }),
|
|
99
|
+
};
|
|
100
|
+
};
|