@gavin-hu/my-pi-agent 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -33
- package/README.zh-CN.md +44 -27
- package/extensions/bytefly/README.md +193 -140
- package/extensions/bytefly/block/component.ts +7 -15
- package/extensions/bytefly/block/register.ts +6 -1
- package/extensions/bytefly/doctor/register.ts +24 -5
- package/extensions/bytefly/doctor/tools.ts +4 -0
- package/extensions/bytefly/footer/collect/snapshot.ts +6 -1
- package/extensions/bytefly/footer/config.ts +10 -6
- package/extensions/bytefly/footer/price/price.ts +5 -3
- package/extensions/bytefly/footer/price/rates.ts +63 -26
- package/extensions/bytefly/footer/render/status.ts +3 -3
- package/extensions/bytefly/header/component.ts +3 -2
- package/extensions/bytefly/index.ts +3 -3
- package/extensions/bytefly/notice/component.ts +8 -3
- package/extensions/bytefly/notify/config.ts +6 -11
- package/extensions/bytefly/notify/dispatch.ts +13 -5
- package/extensions/bytefly/panel/commands.ts +21 -1
- package/extensions/bytefly/panel/format.ts +5 -2
- package/extensions/bytefly/panel/settings-runtime.ts +26 -6
- package/extensions/bytefly/panel/settings.ts +41 -4
- package/extensions/bytefly/panel/themes.ts +32 -0
- package/extensions/bytefly/panel/tui/editing.ts +77 -0
- package/extensions/bytefly/panel/tui/entries.ts +24 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +13 -8
- package/extensions/bytefly/panel/tui/frame.ts +121 -4
- package/extensions/bytefly/panel/tui/settings-view.ts +54 -15
- package/extensions/bytefly/panel/tui.ts +94 -152
- package/extensions/bytefly/picker/discovery.ts +1 -0
- package/extensions/bytefly/picker/types.ts +2 -0
- package/extensions/bytefly/separator/component.ts +14 -4
- package/extensions/bytefly/update/cache.ts +58 -19
- package/extensions/bytefly/update/check.ts +35 -0
- package/extensions/bytefly/update/format.ts +76 -35
- package/extensions/bytefly/update/register.ts +144 -55
- package/extensions/bytefly/update/state.ts +4 -2
- package/extensions/bytefly/update/suppress.ts +66 -0
- package/extensions/bytefly/update/types.ts +15 -0
- package/extensions/check/README.md +253 -0
- package/extensions/check/commands.ts +128 -0
- package/extensions/check/config.ts +39 -0
- package/extensions/check/detect.ts +269 -0
- package/extensions/check/failures.ts +230 -0
- package/extensions/check/format.ts +135 -0
- package/extensions/check/index.ts +26 -0
- package/extensions/check/render.ts +185 -0
- package/extensions/check/runner.ts +176 -0
- package/extensions/check/runtime.ts +244 -0
- package/extensions/check/source.ts +80 -0
- package/extensions/check/tools.ts +173 -0
- package/extensions/check/tui.ts +195 -0
- package/extensions/check/types.ts +99 -0
- package/extensions/document/README.md +1 -1
- package/extensions/document/extract/pdf.ts +8 -16
- package/extensions/document/formats.ts +2 -2
- package/extensions/file-browse/README.md +66 -32
- package/extensions/file-browse/client.ts +95 -32
- package/extensions/file-browse/config.ts +9 -9
- package/extensions/file-browse/git.ts +37 -47
- package/extensions/file-browse/html.ts +39 -58
- package/extensions/file-browse/markdown.ts +110 -92
- package/extensions/file-browse/raw-html.ts +478 -0
- package/extensions/file-browse/router.ts +32 -70
- package/extensions/file-browse/server.ts +18 -56
- package/extensions/file-browse/urls.ts +80 -0
- package/extensions/goal/README.md +103 -47
- package/extensions/goal/clarify-parse.ts +4 -9
- package/extensions/goal/clarify.ts +27 -4
- package/extensions/goal/commands.ts +24 -11
- package/extensions/goal/format.ts +41 -10
- package/extensions/goal/index.ts +39 -16
- package/extensions/goal/plan-route.ts +51 -0
- package/extensions/goal/schema.ts +6 -23
- package/extensions/goal/state.ts +39 -57
- package/extensions/goal/tools.ts +26 -20
- package/extensions/goal/types.ts +11 -1
- package/extensions/job/README.md +20 -6
- package/extensions/job/format.ts +18 -68
- package/extensions/job/index.ts +26 -41
- package/extensions/job/log-writer.ts +208 -0
- package/extensions/job/logs.ts +3 -2
- package/extensions/job/process.ts +6 -99
- package/extensions/job/runtime.ts +42 -181
- package/extensions/job/schema.ts +1 -4
- package/extensions/job/tools.ts +1 -15
- package/extensions/job/tui.ts +117 -307
- package/extensions/job/ui.ts +7 -33
- package/extensions/memory/README.md +1 -1
- package/extensions/memory/errors.ts +5 -6
- package/extensions/memory/tui.ts +14 -24
- package/extensions/plan/README.md +94 -18
- package/extensions/plan/commands.ts +9 -5
- package/extensions/plan/format.ts +87 -0
- package/extensions/plan/index.ts +31 -15
- package/extensions/plan/plans.ts +2 -3
- package/extensions/plan/policy.ts +0 -6
- package/extensions/plan/runtime.ts +4 -2
- package/extensions/plan/steps.ts +37 -8
- package/extensions/plan/todos.ts +44 -3
- package/extensions/plan/tools/enter.ts +94 -0
- package/extensions/plan/tools/exit.ts +296 -0
- package/extensions/plan/tools/nested.ts +21 -0
- package/extensions/plan/tools/steps.ts +136 -0
- package/extensions/plan/tools/todo-seed.ts +76 -0
- package/extensions/plan/tools/write.ts +127 -0
- package/extensions/plan/tools.ts +18 -703
- package/extensions/plan/web-client.ts +6 -22
- package/extensions/plan/web-page.ts +19 -30
- package/extensions/plan/web-router.ts +34 -71
- package/extensions/plan/web.ts +13 -40
- package/extensions/question/answers.ts +3 -8
- package/extensions/question/tui.ts +6 -6
- package/extensions/rewind/README.md +95 -22
- package/extensions/rewind/checklist.ts +185 -0
- package/extensions/rewind/commands.ts +8 -0
- package/extensions/rewind/config.ts +3 -11
- package/extensions/rewind/format.ts +220 -2
- package/extensions/rewind/git.ts +74 -7
- package/extensions/rewind/hunk-picker.ts +114 -0
- package/extensions/rewind/patch.ts +88 -0
- package/extensions/rewind/path-picker.ts +67 -100
- package/extensions/rewind/paths.ts +93 -19
- package/extensions/rewind/restore.ts +53 -10
- package/extensions/rewind/review-tui.ts +320 -0
- package/extensions/rewind/review.ts +397 -0
- package/extensions/rewind/rewind-tui.ts +4 -3
- package/extensions/rewind/runtime.ts +94 -17
- package/extensions/subagent/README.md +61 -6
- package/extensions/subagent/background-tui.ts +208 -0
- package/extensions/subagent/background.ts +470 -0
- package/extensions/subagent/commands.ts +80 -0
- package/extensions/subagent/config.ts +4 -0
- package/extensions/subagent/format.ts +24 -1
- package/extensions/subagent/index.ts +91 -2
- package/extensions/subagent/render.ts +13 -0
- package/extensions/subagent/run.ts +17 -16
- package/extensions/subagent/schema.ts +12 -0
- package/extensions/subagent/types.ts +11 -0
- package/extensions/subagent/ui.ts +50 -0
- package/extensions/todo/README.md +18 -13
- package/extensions/todo/format.ts +3 -2
- package/extensions/todo/runtime.ts +3 -2
- package/extensions/todo/schema.ts +8 -84
- package/extensions/todo/state.ts +5 -46
- package/extensions/todo/tools.ts +2 -1
- package/extensions/todo/types.ts +6 -15
- package/extensions/watch/README.md +14 -6
- package/extensions/watch/child.ts +150 -0
- package/extensions/watch/format.ts +8 -48
- package/extensions/watch/index.ts +42 -58
- package/extensions/watch/runtime.ts +49 -128
- package/extensions/watch/schema.ts +1 -4
- package/extensions/watch/tools.ts +1 -11
- package/extensions/watch/tui.ts +103 -273
- package/extensions/watch/ui.ts +5 -32
- package/extensions/web-access/README.md +1 -1
- package/extensions/web-access/fetch/page.ts +1 -1
- package/extensions/web-access/fetch/tool.ts +6 -2
- package/extensions/web-access/search/search.ts +2 -5
- package/extensions/wechat/README.md +1 -1
- package/extensions/wechat/client.ts +23 -0
- package/extensions/wechat/commands.ts +57 -42
- package/extensions/wechat/deliver.ts +5 -9
- package/extensions/wechat/errors.ts +3 -6
- package/extensions/wechat/inbound.ts +2 -5
- package/extensions/wechat/index.ts +2 -8
- package/extensions/wechat/outbound.ts +2 -4
- package/extensions/wechat/peer-worker.ts +3 -8
- package/extensions/wechat/poll.ts +10 -9
- package/extensions/wechat/presence.ts +35 -62
- package/extensions/wechat/reply-pipeline.ts +3 -1
- package/extensions/wechat/status-screen.ts +1 -1
- package/extensions/wechat/timer.ts +57 -0
- package/extensions/worktree/config.ts +8 -20
- package/extensions/worktree/enter.ts +7 -12
- package/extensions/worktree/git.ts +38 -69
- package/extensions/worktree/index.ts +5 -4
- package/extensions/worktree/registry.ts +7 -7
- package/extensions/worktree/root-tools.ts +2 -2
- package/extensions/worktree/status.ts +3 -2
- package/lib/README.md +22 -8
- package/lib/config.ts +50 -3
- package/lib/errors.ts +12 -0
- package/lib/format.ts +50 -0
- package/lib/git/read.ts +8 -0
- package/lib/goal-state.ts +23 -8
- package/lib/http/headers.ts +54 -0
- package/lib/http/index.ts +13 -0
- package/lib/http/response.ts +46 -0
- package/lib/http/router.ts +52 -0
- package/lib/http/server.ts +91 -0
- package/lib/lines.ts +77 -0
- package/lib/pdf.ts +82 -0
- package/lib/plan-entry.ts +71 -0
- package/lib/plan-state.ts +34 -0
- package/lib/report.ts +99 -0
- package/lib/requirements.ts +25 -19
- package/lib/spawn.ts +170 -0
- package/lib/todo-state.ts +110 -23
- package/lib/tool-names.ts +7 -0
- package/lib/tool-result.ts +33 -0
- package/lib/tui/README.md +68 -3
- package/lib/tui/chrome.ts +28 -4
- package/lib/tui/columns.ts +6 -1
- package/lib/tui/confirm.ts +65 -0
- package/lib/tui/dock-screen.ts +410 -0
- package/lib/tui/glyphs.ts +2 -1
- package/lib/tui/indent.ts +1 -1
- package/lib/tui/index.ts +2 -0
- package/lib/tui/rows.ts +21 -0
- package/lib/tui/status-controller.ts +35 -1
- package/lib/tui/tool.ts +59 -1
- package/lib/web/components.ts +20 -1
- package/lib/web/markup.ts +32 -0
- package/lib/web/runtime.ts +50 -0
- package/package.json +22 -5
- package/skills/init/SKILL.md +165 -0
- package/skills/init/references/agents-md-quality.md +103 -0
- package/skills/init/references/interview-questions.md +81 -0
- package/skills/refactor/SKILL.md +141 -0
- package/extensions/watch/process.ts +0 -63
- package/extensions/web-access/fetch/pdf.ts +0 -45
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-stack command detection for the check extension.
|
|
3
|
+
*
|
|
4
|
+
* A detector is one pure function over a directory: it reads the manifests it
|
|
5
|
+
* understands and returns the same `DetectedCommands` shape. The registry is
|
|
6
|
+
* ordered, and detection stops at the first family that yields a command, so a
|
|
7
|
+
* Node project with a Makefile does not silently become a `make` project.
|
|
8
|
+
*
|
|
9
|
+
* `check.command` bypasses this module entirely. That is the deliberate escape
|
|
10
|
+
* hatch for stacks without a detector, not a failure of the design.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import type { CheckTarget, CommandSpec, DetectedCommands } from "./types.ts";
|
|
16
|
+
|
|
17
|
+
/** One family of manifests and the commands it declares. */
|
|
18
|
+
export interface Detector {
|
|
19
|
+
/** Stable name for tests and documentation. */
|
|
20
|
+
name: string;
|
|
21
|
+
/** Commands this directory declares, or undefined when it is not this family. */
|
|
22
|
+
detect(cwd: string): DetectedCommands | undefined;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** One resolved command list, or the corrective reason there is none. */
|
|
26
|
+
export type PlannedCommands = { ok: true; commands: CommandSpec[] } | { ok: false; error: string };
|
|
27
|
+
|
|
28
|
+
/** A command whose label is its own text. */
|
|
29
|
+
function spec(command: string): CommandSpec {
|
|
30
|
+
return { command, label: command };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Read a UTF-8 file, or undefined when it is missing or unreadable. */
|
|
34
|
+
function read(cwd: string, name: string): string | undefined {
|
|
35
|
+
try {
|
|
36
|
+
return readFileSync(join(cwd, name), "utf-8");
|
|
37
|
+
} catch {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Whether a directory entry is a file or a directory. */
|
|
43
|
+
function has(cwd: string, name: string, kind: "file" | "dir" = "file"): boolean {
|
|
44
|
+
try {
|
|
45
|
+
const path = join(cwd, name);
|
|
46
|
+
return kind === "dir" ? statSync(path).isDirectory() : statSync(path).isFile();
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A plain object, for untrusted JSON. */
|
|
53
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
54
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The package manager whose lockfile is present, defaulting to npm. */
|
|
58
|
+
export function packageManager(cwd: string): "bun" | "pnpm" | "yarn" | "npm" {
|
|
59
|
+
if (existsSync(join(cwd, "bun.lock")) || existsSync(join(cwd, "bun.lockb"))) return "bun";
|
|
60
|
+
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
61
|
+
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
62
|
+
return "npm";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The declared scripts in package.json, or undefined outside a Node project. */
|
|
66
|
+
function packageScripts(cwd: string): Record<string, string> | undefined {
|
|
67
|
+
const text = read(cwd, "package.json");
|
|
68
|
+
if (text === undefined) return undefined;
|
|
69
|
+
let parsed: unknown;
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(text);
|
|
72
|
+
} catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (!isRecord(parsed) || !isRecord(parsed.scripts)) return undefined;
|
|
76
|
+
const scripts: Record<string, string> = {};
|
|
77
|
+
for (const [name, value] of Object.entries(parsed.scripts)) {
|
|
78
|
+
if (typeof value === "string") scripts[name] = value;
|
|
79
|
+
}
|
|
80
|
+
return scripts;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const packageDetector: Detector = {
|
|
84
|
+
name: "package.json",
|
|
85
|
+
detect(cwd) {
|
|
86
|
+
const scripts = packageScripts(cwd);
|
|
87
|
+
if (!scripts) return undefined;
|
|
88
|
+
const pm = packageManager(cwd);
|
|
89
|
+
const run = (name: string): CommandSpec | undefined =>
|
|
90
|
+
scripts[name] !== undefined ? spec(`${pm} run ${name}`) : undefined;
|
|
91
|
+
const commands: DetectedCommands = {};
|
|
92
|
+
const test = run("test");
|
|
93
|
+
const lint = run("lint");
|
|
94
|
+
const typecheck = run("typecheck");
|
|
95
|
+
const all = run("check");
|
|
96
|
+
if (test) commands.test = test;
|
|
97
|
+
if (lint) commands.lint = lint;
|
|
98
|
+
if (typecheck) commands.typecheck = typecheck;
|
|
99
|
+
if (all) commands.all = all;
|
|
100
|
+
return Object.keys(commands).length > 0 ? commands : undefined;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const cargoDetector: Detector = {
|
|
105
|
+
name: "Cargo.toml",
|
|
106
|
+
detect(cwd) {
|
|
107
|
+
if (!has(cwd, "Cargo.toml")) return undefined;
|
|
108
|
+
return {
|
|
109
|
+
test: spec("cargo test"),
|
|
110
|
+
lint: spec("cargo clippy -- -D warnings"),
|
|
111
|
+
typecheck: spec("cargo check"),
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const goDetector: Detector = {
|
|
117
|
+
name: "go.mod",
|
|
118
|
+
detect(cwd) {
|
|
119
|
+
if (!has(cwd, "go.mod")) return undefined;
|
|
120
|
+
return {
|
|
121
|
+
test: spec("go test ./..."),
|
|
122
|
+
lint: spec("go vet ./..."),
|
|
123
|
+
typecheck: spec("go build ./..."),
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const pythonDetector: Detector = {
|
|
129
|
+
name: "python",
|
|
130
|
+
detect(cwd) {
|
|
131
|
+
const pyproject = read(cwd, "pyproject.toml") ?? "";
|
|
132
|
+
const pytest = has(cwd, "pytest.ini") || pyproject.includes("[tool.pytest") || has(cwd, "tests", "dir");
|
|
133
|
+
const commands: DetectedCommands = {};
|
|
134
|
+
if (pytest) commands.test = spec("pytest");
|
|
135
|
+
if (pyproject.includes("[tool.ruff")) commands.lint = spec("ruff check .");
|
|
136
|
+
if (pyproject.includes("[tool.mypy")) commands.typecheck = spec("mypy .");
|
|
137
|
+
return Object.keys(commands).length > 0 ? commands : undefined;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
/** Declared recipe names in a justfile. */
|
|
142
|
+
function justTargets(text: string): Set<string> {
|
|
143
|
+
const targets = new Set<string>();
|
|
144
|
+
for (const line of text.split("\n")) {
|
|
145
|
+
const match = /^([A-Za-z_][A-Za-z0-9_-]*)\s*:/.exec(line);
|
|
146
|
+
if (match) targets.add(match[1] ?? "");
|
|
147
|
+
}
|
|
148
|
+
return targets;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The recipe name each target runs, for a recipe runner's declared targets. */
|
|
152
|
+
const RECIPE_TARGETS: ReadonlyArray<readonly [CheckTarget, string]> = [
|
|
153
|
+
["test", "test"],
|
|
154
|
+
["lint", "lint"],
|
|
155
|
+
["typecheck", "typecheck"],
|
|
156
|
+
["all", "check"],
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A detector for a recipe runner: a manifest of `name:` targets, of which the
|
|
161
|
+
* declared `test`/`lint`/`typecheck`/`check` become the commands. `targets` is
|
|
162
|
+
* the family's own target parser, because a Makefile and a justfile do not agree
|
|
163
|
+
* on what a legal target name is.
|
|
164
|
+
*/
|
|
165
|
+
function recipeDetector(options: {
|
|
166
|
+
name: string;
|
|
167
|
+
/** Manifest names to look for, in the family's own preference order. */
|
|
168
|
+
files: readonly string[];
|
|
169
|
+
/** The runner binary, e.g. `just`. */
|
|
170
|
+
command: string;
|
|
171
|
+
targets(text: string): Set<string>;
|
|
172
|
+
}): Detector {
|
|
173
|
+
return {
|
|
174
|
+
name: options.name,
|
|
175
|
+
detect(cwd) {
|
|
176
|
+
const file = options.files.find((name) => has(cwd, name));
|
|
177
|
+
if (!file) return undefined;
|
|
178
|
+
const declared = options.targets(read(cwd, file) ?? "");
|
|
179
|
+
const commands: DetectedCommands = {};
|
|
180
|
+
for (const [target, recipe] of RECIPE_TARGETS) {
|
|
181
|
+
if (declared.has(recipe)) commands[target] = spec(`${options.command} ${recipe}`);
|
|
182
|
+
}
|
|
183
|
+
return Object.keys(commands).length > 0 ? commands : undefined;
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const justDetector = recipeDetector({
|
|
189
|
+
name: "justfile",
|
|
190
|
+
files: ["justfile", "Justfile", ".justfile"],
|
|
191
|
+
command: "just",
|
|
192
|
+
targets: justTargets,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
/** Declared target names in a Makefile. */
|
|
196
|
+
function makeTargets(text: string): Set<string> {
|
|
197
|
+
const targets = new Set<string>();
|
|
198
|
+
for (const line of text.split("\n")) {
|
|
199
|
+
const match = /^([A-Za-z0-9_.-]+)\s*:/.exec(line);
|
|
200
|
+
if (match?.[1] && !match[1].startsWith(".")) targets.add(match[1]);
|
|
201
|
+
}
|
|
202
|
+
return targets;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const makeDetector = recipeDetector({
|
|
206
|
+
name: "Makefile",
|
|
207
|
+
files: ["Makefile", "makefile"],
|
|
208
|
+
command: "make",
|
|
209
|
+
targets: makeTargets,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
/** Detectors in priority order; the first family with a command wins. */
|
|
213
|
+
export const DETECTORS: readonly Detector[] = [
|
|
214
|
+
packageDetector,
|
|
215
|
+
cargoDetector,
|
|
216
|
+
goDetector,
|
|
217
|
+
pythonDetector,
|
|
218
|
+
justDetector,
|
|
219
|
+
makeDetector,
|
|
220
|
+
];
|
|
221
|
+
|
|
222
|
+
/** The commands the first matching manifest family declares. */
|
|
223
|
+
export function detectCommands(cwd: string): DetectedCommands | undefined {
|
|
224
|
+
for (const detector of DETECTORS) {
|
|
225
|
+
const commands = detector.detect(cwd);
|
|
226
|
+
if (commands) return commands;
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The ordered targets `all` runs when no all-command exists. */
|
|
232
|
+
const ALL_ORDER: ReadonlyArray<Exclude<CheckTarget, "all">> = ["test", "lint", "typecheck"];
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve the commands for one target.
|
|
236
|
+
*
|
|
237
|
+
* `override` is the user's `check.command`; when set it is the only command, for
|
|
238
|
+
* every target. Otherwise detection supplies the target, and `all` prefers a
|
|
239
|
+
* declared all-command before expanding to the detected targets in order.
|
|
240
|
+
*/
|
|
241
|
+
export function planCommands(cwd: string, target: CheckTarget, override = ""): PlannedCommands {
|
|
242
|
+
const custom = override.trim();
|
|
243
|
+
if (custom) return { ok: true, commands: [spec(custom)] };
|
|
244
|
+
|
|
245
|
+
const detected = detectCommands(cwd);
|
|
246
|
+
if (!detected) {
|
|
247
|
+
return {
|
|
248
|
+
ok: false,
|
|
249
|
+
error: "No check command detected. Set `check.command` in bytefly.json to the command this project uses.",
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (target !== "all") {
|
|
254
|
+
const command = detected[target];
|
|
255
|
+
if (command) return { ok: true, commands: [command] };
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
error: `No ${target} command detected. Set \`check.command\` in bytefly.json to the command this project uses.`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (detected.all) return { ok: true, commands: [detected.all] };
|
|
263
|
+
const commands = ALL_ORDER.flatMap((name) => (detected[name] ? [detected[name]] : []));
|
|
264
|
+
if (commands.length > 0) return { ok: true, commands };
|
|
265
|
+
return {
|
|
266
|
+
ok: false,
|
|
267
|
+
error: "No check command detected. Set `check.command` in bytefly.json to the command this project uses.",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort failure extraction from a check command's output.
|
|
3
|
+
*
|
|
4
|
+
* Known runner lines (pytest, Go, Cargo, Vitest/Jest) are recognized first; the
|
|
5
|
+
* generic `path:line(:col): message` parser is the fallback, so any compiler or
|
|
6
|
+
* linter with a conventional location works without a runner-specific parser.
|
|
7
|
+
* A miss is never guessed at: the caller falls back to a bounded tail with the
|
|
8
|
+
* command's exit status.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { stripControlChars } from "../../lib/format.ts";
|
|
12
|
+
import type { Failure } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
/** Failures retained from one command; the model list is capped lower. */
|
|
15
|
+
const MAX_FAILURES = 200;
|
|
16
|
+
|
|
17
|
+
/** One clean line, without control characters or a trailing CR. */
|
|
18
|
+
function cleanLine(text: string): string {
|
|
19
|
+
return stripControlChars(text.replace(/\r/g, "")).trim();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Whether a candidate looks like a path rather than prose or a URL. */
|
|
23
|
+
function looksLikePath(path: string): boolean {
|
|
24
|
+
if (path === "" || path.startsWith("http://") || path.startsWith("https://")) return false;
|
|
25
|
+
return path.includes("/") || path.includes("\\") || path.includes(".");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A number from a regex group, or undefined when the group did not match. */
|
|
29
|
+
function numberFrom(group: string | undefined): number | undefined {
|
|
30
|
+
return group === undefined ? undefined : Number.parseInt(group, 10);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A failure at a parsed location, or undefined when the path is not a path the
|
|
35
|
+
* extension is willing to point at. A column is kept only when it parsed, so a
|
|
36
|
+
* two-part location never carries a `NaN`.
|
|
37
|
+
*/
|
|
38
|
+
function locatedFailure(
|
|
39
|
+
path: string,
|
|
40
|
+
line: number | undefined,
|
|
41
|
+
column: number | undefined,
|
|
42
|
+
rest: Pick<Failure, "message" | "command"> & { test?: string },
|
|
43
|
+
): Failure | undefined {
|
|
44
|
+
if (!looksLikePath(path) || line === undefined || !Number.isFinite(line)) return undefined;
|
|
45
|
+
const known = column !== undefined && Number.isFinite(column) ? column : undefined;
|
|
46
|
+
return { path, line, ...(known === undefined ? {} : { column: known }), ...rest };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The generic compiler/linter location: `path:line(:col): message`. */
|
|
50
|
+
function genericFailure(line: string, command: string): Failure | undefined {
|
|
51
|
+
const match = /^(.*?):(\d+)(?::(\d+))?:\s*(.+)$/.exec(line);
|
|
52
|
+
if (!match) return undefined;
|
|
53
|
+
const path = match[1]?.trim() ?? "";
|
|
54
|
+
const message = match[4]?.trim() ?? "";
|
|
55
|
+
if (message === "") return undefined;
|
|
56
|
+
if (path.startsWith("at ") || path.startsWith("thread ")) return undefined;
|
|
57
|
+
return locatedFailure(path, numberFrom(match[2]), numberFrom(match[3]), { message, command });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Pytest's `FAILED path::test - message` line. */
|
|
61
|
+
function pytestFailure(line: string, command: string): Failure | undefined {
|
|
62
|
+
const match = /^FAILED\s+(.+?)::(\S+)(?:\s+-\s+(.*))?$/.exec(line);
|
|
63
|
+
if (!match) return undefined;
|
|
64
|
+
const path = match[1]?.trim() ?? "";
|
|
65
|
+
const test = match[2]?.trim() ?? "";
|
|
66
|
+
const message = match[3]?.trim() || "Test failed";
|
|
67
|
+
if (!path || !test) return undefined;
|
|
68
|
+
return { path, test, message, command };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Vitest/Jest's `FAIL path > suite > test` line. */
|
|
72
|
+
function failLine(rest: string, command: string): Failure | undefined {
|
|
73
|
+
const match = /^\s*FAIL\s+(.+?)(?:\s+>\s+.*)?$/.exec(rest);
|
|
74
|
+
if (!match) return undefined;
|
|
75
|
+
const path = match[1]?.trim() ?? "";
|
|
76
|
+
if (!looksLikePath(path)) return undefined;
|
|
77
|
+
return { path, message: "Test failed", command };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Jest/Vitest's `● suite > test` name; the next located frame attaches it. */
|
|
81
|
+
function testName(line: string): string | undefined {
|
|
82
|
+
const match = /^●\s+(.+)$/.exec(line);
|
|
83
|
+
return match?.[1]?.trim() || undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** A stack frame with a real location: `at … (path:line:col)` or `at path:line:col`. */
|
|
87
|
+
function stackFailure(line: string, command: string, pendingTest: string | undefined): Failure | undefined {
|
|
88
|
+
const parenthesized = /^\s*at\s+.*?\((.+?):(\d+)(?::(\d+))?\)\s*$/.exec(line);
|
|
89
|
+
const bare = parenthesized ? undefined : /^\s*at\s+(.+?):(\d+)(?::(\d+))?\s*$/.exec(line);
|
|
90
|
+
const match = parenthesized ?? bare;
|
|
91
|
+
if (!match) return undefined;
|
|
92
|
+
const path = match[1]?.trim() ?? "";
|
|
93
|
+
return locatedFailure(path, numberFrom(match[2]), numberFrom(match[3]), {
|
|
94
|
+
...(pendingTest ? { test: pendingTest } : {}),
|
|
95
|
+
message: pendingTest ? "Test failed" : "Stack frame",
|
|
96
|
+
command,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Python's `File "path", line N, in test` traceback line. */
|
|
101
|
+
function pythonTraceback(line: string, command: string, pendingTest: string | undefined): Failure | undefined {
|
|
102
|
+
const match = /^File "(.+)", line (\d+)/.exec(line);
|
|
103
|
+
if (!match) return undefined;
|
|
104
|
+
const path = match[1]?.trim() ?? "";
|
|
105
|
+
return locatedFailure(path, numberFrom(match[2]), undefined, {
|
|
106
|
+
...(pendingTest ? { test: pendingTest } : {}),
|
|
107
|
+
message: pendingTest ? "Test failed" : "Traceback",
|
|
108
|
+
command,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Go's `--- FAIL: TestName` line; the next location attaches to it. */
|
|
113
|
+
function goFailure(line: string, command: string): Failure | undefined {
|
|
114
|
+
const match = /^--- FAIL:\s+(\S+)/.exec(line);
|
|
115
|
+
if (!match) return undefined;
|
|
116
|
+
return { test: match[1] ?? "test", message: "Test failed", command };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Cargo's `thread 'test' panicked at path:line:col:` line. */
|
|
120
|
+
function cargoFailure(line: string, command: string): Failure | undefined {
|
|
121
|
+
const match = /^thread '(.+)' panicked at (.+?):(\d+)(?::(\d+))?:$/.exec(line);
|
|
122
|
+
if (!match) return undefined;
|
|
123
|
+
const path = match[2]?.trim() ?? "";
|
|
124
|
+
return locatedFailure(path, numberFrom(match[3]), numberFrom(match[4]), {
|
|
125
|
+
test: match[1],
|
|
126
|
+
message: "Panicked",
|
|
127
|
+
command,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Stable identity for dedupe. The command joins the key only when two commands'
|
|
133
|
+
* failures must not merge, which is the cross-command pass in `runtime.ts`.
|
|
134
|
+
*/
|
|
135
|
+
export function failureKey(failure: Failure, options: { command?: boolean } = {}): string {
|
|
136
|
+
const fields: Array<string | number | undefined> = [failure.path, failure.line, failure.column, failure.test];
|
|
137
|
+
if (options.command) fields.push(failure.command);
|
|
138
|
+
fields.push(failure.message);
|
|
139
|
+
return fields.map((field) => field ?? "").join("\u0000");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parse the failures in one command's bounded output.
|
|
144
|
+
*
|
|
145
|
+
* A passing command has no failures even if a warning matched; parsing is only
|
|
146
|
+
* used after a non-zero exit. A failed command with no recognizable failure
|
|
147
|
+
* gets one honest fallback instead of an invented location.
|
|
148
|
+
*/
|
|
149
|
+
export function parseFailures(output: string, command: string, exitCode: number | null): Failure[] {
|
|
150
|
+
if (exitCode === 0) return [];
|
|
151
|
+
const failures: Failure[] = [];
|
|
152
|
+
const seen = new Set<string>();
|
|
153
|
+
let pendingTest: string | undefined;
|
|
154
|
+
|
|
155
|
+
const add = (failure: Failure | undefined): void => {
|
|
156
|
+
if (!failure) return;
|
|
157
|
+
const normalized: Failure = {
|
|
158
|
+
...failure,
|
|
159
|
+
message: cleanLine(failure.message),
|
|
160
|
+
...(failure.test ? { test: cleanLine(failure.test) } : {}),
|
|
161
|
+
...(failure.path ? { path: cleanLine(failure.path) } : {}),
|
|
162
|
+
};
|
|
163
|
+
if (!normalized.message) return;
|
|
164
|
+
const key = failureKey(normalized);
|
|
165
|
+
if (seen.has(key)) return;
|
|
166
|
+
seen.add(key);
|
|
167
|
+
if (failures.length < MAX_FAILURES) failures.push(normalized);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
for (const raw of output.split("\n")) {
|
|
171
|
+
const line = cleanLine(raw);
|
|
172
|
+
if (!line) continue;
|
|
173
|
+
|
|
174
|
+
const test = testName(line);
|
|
175
|
+
if (test) {
|
|
176
|
+
pendingTest = test;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const go = goFailure(line, command);
|
|
181
|
+
if (go) {
|
|
182
|
+
pendingTest = go.test;
|
|
183
|
+
add(go);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const stack = stackFailure(line, command, pendingTest);
|
|
188
|
+
if (stack) {
|
|
189
|
+
add(stack);
|
|
190
|
+
pendingTest = undefined;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const python = pythonTraceback(line, command, pendingTest);
|
|
195
|
+
if (python) {
|
|
196
|
+
add(python);
|
|
197
|
+
pendingTest = undefined;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const cargo = cargoFailure(line, command);
|
|
202
|
+
if (cargo) {
|
|
203
|
+
add(cargo);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const pytest = pytestFailure(line, command);
|
|
208
|
+
if (pytest) {
|
|
209
|
+
add(pytest);
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const failed = failLine(line, command);
|
|
214
|
+
if (failed) {
|
|
215
|
+
add(failed);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const generic = genericFailure(line, command);
|
|
220
|
+
if (generic) {
|
|
221
|
+
add(pendingTest ? { ...generic, test: pendingTest } : generic);
|
|
222
|
+
pendingTest = undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (failures.length > 0) return failures;
|
|
227
|
+
return [
|
|
228
|
+
{ message: exitCode === null ? "Command did not exit cleanly" : `Command exited with status ${exitCode}`, command },
|
|
229
|
+
];
|
|
230
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure text for the check extension: the one-line summary, failure rows, the
|
|
3
|
+
* detail body, and the model-facing report. No terminal access and no git.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { formatDuration, sanitizeLine, stripControlChars } from "../../lib/format.ts";
|
|
7
|
+
import { SEPARATORS } from "../../lib/tui/glyphs.ts";
|
|
8
|
+
import type { CheckResult, CheckStatus, Failure } from "./types.ts";
|
|
9
|
+
import type { SourceResult } from "./source.ts";
|
|
10
|
+
|
|
11
|
+
/** Failures shown in a model-facing report and in an expanded transcript result. */
|
|
12
|
+
export const MODEL_FAILURES = 20;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* What the summary helpers read: a final result, or the run details a partial
|
|
16
|
+
* transcript result carries with `status: "running"`.
|
|
17
|
+
*/
|
|
18
|
+
export interface CheckSummary {
|
|
19
|
+
status: CheckStatus | "running";
|
|
20
|
+
commands: readonly string[];
|
|
21
|
+
durationMs: number;
|
|
22
|
+
failures: readonly Failure[];
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Command labels are script names and config text, so they are untrusted too. */
|
|
27
|
+
function formatCommands(commands: readonly string[]): string {
|
|
28
|
+
return commands.length > 0 ? commands.map((command) => sanitizeLine(command)).join(", ") : "no command";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Whether a failure named a file, a test, or both. */
|
|
32
|
+
export function hasFailureLocation(failure: Failure): boolean {
|
|
33
|
+
return failure.path !== undefined || failure.test !== undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `path:line:col`, a test name, or a neutral label. */
|
|
37
|
+
export function formatFailureLocation(failure: Failure): string {
|
|
38
|
+
if (failure.path) {
|
|
39
|
+
const line = failure.line === undefined ? "" : `:${failure.line}`;
|
|
40
|
+
const column = failure.column === undefined ? "" : `:${failure.column}`;
|
|
41
|
+
return sanitizeLine(`${failure.path}${line}${column}`);
|
|
42
|
+
}
|
|
43
|
+
return sanitizeLine(failure.test ?? "check");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `path:line:col — message`, one line. */
|
|
47
|
+
export function formatFailureLine(failure: Failure): string {
|
|
48
|
+
return `${formatFailureLocation(failure)}${SEPARATORS.item}${sanitizeLine(failure.message)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The one-line result a passing run posts, and the headline of a failed one. */
|
|
52
|
+
export function formatCheckSummary(result: CheckResult): string {
|
|
53
|
+
const commands = formatCommands(result.commands);
|
|
54
|
+
const duration = formatDuration(result.durationMs);
|
|
55
|
+
if (result.status === "passed") return `Check passed: ${commands}${SEPARATORS.item}${duration}`;
|
|
56
|
+
if (result.status === "failed") {
|
|
57
|
+
const count = result.failures.length;
|
|
58
|
+
return `Check failed: ${count} failure${count === 1 ? "" : "s"}${SEPARATORS.item}${commands}${SEPARATORS.item}${duration}`;
|
|
59
|
+
}
|
|
60
|
+
if (result.status === "timeout") return `Check timed out: ${commands}${SEPARATORS.item}${duration}`;
|
|
61
|
+
if (result.status === "cancelled") return `Check cancelled: ${commands}`;
|
|
62
|
+
return `Check failed to run: ${sanitizeLine(result.error ?? "unknown error")}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The compact summary under the Check title and on a transcript result. */
|
|
66
|
+
export function formatResultSummary(result: CheckSummary): string {
|
|
67
|
+
const commands = formatCommands(result.commands);
|
|
68
|
+
const duration = formatDuration(result.durationMs);
|
|
69
|
+
if (result.status === "running") return `running${SEPARATORS.item}${commands}${SEPARATORS.item}${duration}`;
|
|
70
|
+
if (result.status === "passed") return `passed${SEPARATORS.item}${commands}${SEPARATORS.item}${duration}`;
|
|
71
|
+
if (result.status === "failed") {
|
|
72
|
+
const count = result.failures.length;
|
|
73
|
+
// A fallback entry is the exit status, not a counted failure; the summary
|
|
74
|
+
// says "failed" and the tail carries the reason.
|
|
75
|
+
if (count > 0 && result.failures.every((failure) => !hasFailureLocation(failure))) {
|
|
76
|
+
return `failed${SEPARATORS.item}${commands}${SEPARATORS.item}${duration}`;
|
|
77
|
+
}
|
|
78
|
+
return `${count} failure${count === 1 ? "" : "s"}${SEPARATORS.item}${commands}${SEPARATORS.item}${duration}`;
|
|
79
|
+
}
|
|
80
|
+
if (result.status === "timeout") return `timed out${SEPARATORS.item}${commands}${SEPARATORS.item}${duration}`;
|
|
81
|
+
if (result.status === "cancelled") return `cancelled${SEPARATORS.item}${commands}`;
|
|
82
|
+
return `error${SEPARATORS.item}${sanitizeLine(result.error ?? "unknown error")}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The source excerpt rows with a target marker and aligned line numbers. */
|
|
86
|
+
export function formatSourceExcerpt(excerpt: {
|
|
87
|
+
startLine: number;
|
|
88
|
+
endLine: number;
|
|
89
|
+
targetLine: number;
|
|
90
|
+
lines: readonly string[];
|
|
91
|
+
}): string[] {
|
|
92
|
+
const width = String(excerpt.endLine).length;
|
|
93
|
+
return excerpt.lines.map((text, index) => {
|
|
94
|
+
const number = excerpt.startLine + index;
|
|
95
|
+
const marker = number === excerpt.targetLine ? ">" : " ";
|
|
96
|
+
const clean = stripControlChars(text).replaceAll("\t", " ").slice(0, 2_000).trimEnd();
|
|
97
|
+
return `${marker} ${String(number).padStart(width)} │ ${clean}`;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The lines a failure detail pane shows: location, reason, source or tail. */
|
|
102
|
+
export function formatFailureDetail(failure: Failure, tail: string, source?: SourceResult): string[] {
|
|
103
|
+
const lines = [
|
|
104
|
+
`${formatFailureLocation(failure)}${SEPARATORS.item}${sanitizeLine(failure.command)}`,
|
|
105
|
+
"",
|
|
106
|
+
...sanitizeLine(failure.message).split("\n"),
|
|
107
|
+
];
|
|
108
|
+
if (source?.ok) {
|
|
109
|
+
lines.push("", "Source:", ...formatSourceExcerpt(source.excerpt));
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
if (source && failure.path) lines.push("", `(source unavailable: ${sanitizeLine(source.reason)})`);
|
|
113
|
+
const tailLines = tail
|
|
114
|
+
.split("\n")
|
|
115
|
+
.map((line) => sanitizeLine(line))
|
|
116
|
+
.filter((line, index, all) => line !== "" || index === all.length - 1);
|
|
117
|
+
if (tailLines.length > 0) lines.push("", "…", ...tailLines);
|
|
118
|
+
return lines;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The model-facing report: summary, up to 20 failures, then the fallback tail. */
|
|
122
|
+
export function formatCheckReport(result: CheckResult): string {
|
|
123
|
+
const lines = [formatCheckSummary(result)];
|
|
124
|
+
for (const failure of result.failures.slice(0, MODEL_FAILURES)) {
|
|
125
|
+
lines.push(`- ${formatFailureLine(failure)}${SEPARATORS.item}${sanitizeLine(failure.command)}`);
|
|
126
|
+
}
|
|
127
|
+
if (result.failures.length > MODEL_FAILURES) {
|
|
128
|
+
lines.push(`… ${result.failures.length - MODEL_FAILURES} more failures in details`);
|
|
129
|
+
}
|
|
130
|
+
const fallbackOnly = result.failures.length > 0 && result.failures.every((failure) => !hasFailureLocation(failure));
|
|
131
|
+
if ((result.failures.length === 0 || fallbackOnly) && result.status !== "passed" && result.tail.trim() !== "") {
|
|
132
|
+
lines.push("", "Tail:", result.tail);
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check — cross-stack verification runs.
|
|
3
|
+
*
|
|
4
|
+
* Registers `/check` and the `run_check` tool. Detection and parsing know about
|
|
5
|
+
* project manifests and runner output; the runtime, screens, and tool speak in
|
|
6
|
+
* commands and failures, never in a language or package manager.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { isExtensionEnabled } from "../../lib/env.ts";
|
|
11
|
+
import { registerCheckCommand } from "./commands.ts";
|
|
12
|
+
import { createCheckRuntime } from "./runtime.ts";
|
|
13
|
+
import { registerCheckTool } from "./tools.ts";
|
|
14
|
+
|
|
15
|
+
export function registerCheck(pi: ExtensionAPI): void {
|
|
16
|
+
const runtime = createCheckRuntime();
|
|
17
|
+
registerCheckTool(pi, runtime);
|
|
18
|
+
registerCheckCommand(pi, runtime);
|
|
19
|
+
pi.on("session_shutdown", () => runtime.shutdown());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Pi extension entrypoint. */
|
|
23
|
+
export default function checkExtension(pi: ExtensionAPI): void {
|
|
24
|
+
if (!isExtensionEnabled("check")) return;
|
|
25
|
+
registerCheck(pi);
|
|
26
|
+
}
|