@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,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi suppresses its own package-update block while bytefly owns the surface.
|
|
3
|
+
*
|
|
4
|
+
* Pi draws `Package Updates Available` from `InteractiveMode.run()` after the
|
|
5
|
+
* session starts, with no setting or extension event to turn it off. The one
|
|
6
|
+
* public seam is the package manager it calls: replacing
|
|
7
|
+
* `checkForAvailableUpdates` with an empty result suppresses that block, while
|
|
8
|
+
* the captured original still serves bytefly's own query. The patch is
|
|
9
|
+
* process-wide, so it is installed at `session_start` and restored at
|
|
10
|
+
* `session_shutdown`.
|
|
11
|
+
*
|
|
12
|
+
* A future host that changes the method shape makes the patch a no-op rather
|
|
13
|
+
* than an error; the update surface then falls back to Pi's block.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { DefaultPackageManager } from "@earendil-works/pi-coding-agent";
|
|
17
|
+
import type { PackageUpdate, PackageUpdateCheck } from "./types.ts";
|
|
18
|
+
|
|
19
|
+
/** A patchable class prototype, kept narrow so a test needs no host object. */
|
|
20
|
+
export interface PackageManagerPrototype {
|
|
21
|
+
checkForAvailableUpdates: PackageUpdateCheck;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The installed suppression: the real check, and how to restore Pi's method. */
|
|
25
|
+
export interface UpdateSuppression {
|
|
26
|
+
/** The original method, bound at call time so it still sees its manager. */
|
|
27
|
+
readonly check: PackageUpdateCheck;
|
|
28
|
+
/** Put Pi's method back; safe to call more than once. */
|
|
29
|
+
restore(): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Marker for the installed patch, so a second registration reuses it. */
|
|
33
|
+
const SUPPRESSION = Symbol.for("@gavin-hu/my-pi-agent/pi-update-suppression");
|
|
34
|
+
|
|
35
|
+
interface MarkedPrototype extends PackageManagerPrototype {
|
|
36
|
+
[SUPPRESSION]?: UpdateSuppression;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Replace Pi's package-update check with one that reports nothing.
|
|
41
|
+
*
|
|
42
|
+
* `prototype` is a seam for tests; production passes Pi's
|
|
43
|
+
* `DefaultPackageManager.prototype`. When the method is missing, the returned
|
|
44
|
+
* suppression is inert and the original read stays `[]`.
|
|
45
|
+
*/
|
|
46
|
+
export function suppressPiPackageUpdates(
|
|
47
|
+
prototype: PackageManagerPrototype = DefaultPackageManager.prototype as PackageManagerPrototype,
|
|
48
|
+
): UpdateSuppression {
|
|
49
|
+
const marked = prototype as MarkedPrototype;
|
|
50
|
+
const existing = marked[SUPPRESSION];
|
|
51
|
+
if (existing) return existing;
|
|
52
|
+
|
|
53
|
+
const original = prototype.checkForAvailableUpdates;
|
|
54
|
+
if (typeof original !== "function") return { check: async () => [], restore: () => {} };
|
|
55
|
+
const originalCheck = original as unknown as (this: DefaultPackageManager) => Promise<PackageUpdate[]>;
|
|
56
|
+
const check: PackageUpdateCheck = (manager) => originalCheck.call(manager);
|
|
57
|
+
const patched: PackageUpdateCheck = async () => [];
|
|
58
|
+
const restore = (): void => {
|
|
59
|
+
if (prototype.checkForAvailableUpdates === patched) prototype.checkForAvailableUpdates = original;
|
|
60
|
+
delete marked[SUPPRESSION];
|
|
61
|
+
};
|
|
62
|
+
const suppression: UpdateSuppression = { check, restore };
|
|
63
|
+
prototype.checkForAvailableUpdates = patched;
|
|
64
|
+
marked[SUPPRESSION] = suppression;
|
|
65
|
+
return suppression;
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The package-update shape Pi's manager returns.
|
|
3
|
+
*
|
|
4
|
+
* Pi exports the manager but not its `PackageUpdate` interface, so the type is
|
|
5
|
+
* derived from the public method. That keeps the extension on the same contract
|
|
6
|
+
* without reaching into a dist path or restating the fields.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { DefaultPackageManager } from "@earendil-works/pi-coding-agent";
|
|
10
|
+
|
|
11
|
+
/** One package Pi reports as updateable. */
|
|
12
|
+
export type PackageUpdate = Awaited<ReturnType<DefaultPackageManager["checkForAvailableUpdates"]>>[number];
|
|
13
|
+
|
|
14
|
+
/** Pi's check, captured before suppression and called with a manager. */
|
|
15
|
+
export type PackageUpdateCheck = (manager: DefaultPackageManager) => Promise<PackageUpdate[]>;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# check — cross-stack verification runs
|
|
2
|
+
|
|
3
|
+
Run the project's test, lint, or typecheck command from Pi, watch it live, and
|
|
4
|
+
turn its output into a navigable `file:line` list. The same run is available to
|
|
5
|
+
the model as `run_check`, with structured failures instead of a wall of log
|
|
6
|
+
text.
|
|
7
|
+
|
|
8
|
+
`check` is deliberately generic: it detects the project's command from its
|
|
9
|
+
manifest or takes `check.command`, and it does not assume Node, Bun, a test
|
|
10
|
+
framework, or a particular CI system.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
pi --extension ./extensions/check # load just this extension
|
|
14
|
+
pi -e . # load the whole @gavin-hu/my-pi-agent package
|
|
15
|
+
pi install ./ # install the package
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Screens
|
|
19
|
+
|
|
20
|
+
<img src="../../assets/check.png" alt="The /check screen: a failed run with failure rows and the focused failure's message" width="100%">
|
|
21
|
+
|
|
22
|
+
<img src="../../assets/check-detail.png" alt="The /check failure detail: the location and message over a source excerpt with the failing line marked by >" width="100%">
|
|
23
|
+
|
|
24
|
+
## Tool rendering
|
|
25
|
+
|
|
26
|
+
The call line is `run_check <target>`. While the command runs, the result is one
|
|
27
|
+
line — `Running bun run check… · 12s` — and it repaints once a second, so a
|
|
28
|
+
silent compiler still shows progress. The settled block keeps the transcript to
|
|
29
|
+
a capped view of the run: a status summary, at most five failure rows, and the
|
|
30
|
+
bounded tail when nothing parsed. `ctrl+o` expands it to the model's 20-failure
|
|
31
|
+
cap; `/check` remains the full navigable list.
|
|
32
|
+
|
|
33
|
+
<img src="../../assets/tools/run_check.png" alt="The run_check transcript block: a status summary and two failure rows under a rail" width="100%">
|
|
34
|
+
|
|
35
|
+
A failed command whose output parsed no location names the exit status in the
|
|
36
|
+
summary, prints `no parsed failure; last output:`, and shows the last five tail
|
|
37
|
+
lines instead of an exit-status row. The transcript never prints the model
|
|
38
|
+
report; the full failure list and the tail stay in `details`.
|
|
39
|
+
|
|
40
|
+
## What it does
|
|
41
|
+
|
|
42
|
+
- **Detects a command** from `package.json`, `Cargo.toml`, `go.mod`, a pytest
|
|
43
|
+
configuration, a `justfile`, or a `Makefile`, or uses `check.command`.
|
|
44
|
+
- **Runs it in a supervised process** with a wall-clock timeout and a bounded
|
|
45
|
+
log; `Esc` cancels the process tree.
|
|
46
|
+
- **Shows a live tail** while the command runs, then a failure list with a
|
|
47
|
+
detail view for the focused failure: a source excerpt around `path:line` when
|
|
48
|
+
the file is readable, otherwise the bounded tail.
|
|
49
|
+
- **Returns structured failures** to the model with `path`, `line`, `column`,
|
|
50
|
+
`test`, `message`, and `command`.
|
|
51
|
+
- **Falls back honestly**: a failed command with no recognizable failure becomes
|
|
52
|
+
one “Command exited with status N” entry, with the bounded tail in its detail.
|
|
53
|
+
- **One line on success**: `Check passed: bun run check · 12.4s`.
|
|
54
|
+
|
|
55
|
+
## Tool
|
|
56
|
+
|
|
57
|
+
`run_check` takes optional `target`:
|
|
58
|
+
|
|
59
|
+
| Field | Value |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `target` | `test`, `lint`, `typecheck`, or `all`; defaults to `all`. |
|
|
62
|
+
| `exposure` | `direct`, active by default. |
|
|
63
|
+
| `executionMode` | `sequential` — one run owns the process and the live buffer. |
|
|
64
|
+
| `annotations` | `readOnlyHint: false`, `destructiveHint: false`, `openWorldHint: false`. |
|
|
65
|
+
| `outputSchema` | `{ target, status, commands, exitCode, durationMs, truncated, failures[] }`, mirrored by `structuredContent`. |
|
|
66
|
+
|
|
67
|
+
`content` is a short summary plus up to 20 failures. `details` carries the full
|
|
68
|
+
bounded failure list and the tail, which the transcript block reads. A failing
|
|
69
|
+
check is a normal result (`isError: false`) so the model can fix it; a check
|
|
70
|
+
that cannot run — no detected command, a spawn failure, or another run already
|
|
71
|
+
active — is an error with the corrective action.
|
|
72
|
+
|
|
73
|
+
## Command
|
|
74
|
+
|
|
75
|
+
| Command | What it does |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `/check` | Run every detected command (or `check.command`). |
|
|
78
|
+
| `/check test` | Run the detected test command. |
|
|
79
|
+
| `/check lint` | Run the detected lint command. |
|
|
80
|
+
| `/check typecheck` | Run the detected typecheck command. |
|
|
81
|
+
| `/check all` | Same as `/check`. |
|
|
82
|
+
|
|
83
|
+
`getArgumentCompletions` offers the four targets. The command refuses while plan
|
|
84
|
+
mode is on: relying on tests is still writing to the project (caches, snapshots,
|
|
85
|
+
generated files), so the plan must be approved first.
|
|
86
|
+
|
|
87
|
+
## Behaviour by mode
|
|
88
|
+
|
|
89
|
+
| Mode | Behaviour |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Interactive TUI | A live run screen with a scrolling tail and `Esc cancel`; then a failure list with `Enter` detail, `r` rerun, and `Esc` close. A pass posts one notice. |
|
|
92
|
+
| RPC (`hasUI`) | Runs headless and posts the summary plus the failure list; no custom screen. |
|
|
93
|
+
| `print` / `json` | Runs headless and posts the same report. |
|
|
94
|
+
| `run_check` | No UI of its own. Returns the summary in `content`, the structured result, and a capped transcript block drawn from `details`. |
|
|
95
|
+
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
The `check` section of the shared `bytefly.json` (global then project, project
|
|
99
|
+
wins). All keys are JSON-only; none has a `/bytefly settings` row.
|
|
100
|
+
|
|
101
|
+
| Key | Default | Bounds | Meaning |
|
|
102
|
+
|---|---|---|---|
|
|
103
|
+
| `command` | `""` | | When non-empty, run this shell command for every target instead of detection. |
|
|
104
|
+
| `timeoutMs` | `600000` | `0`–`3600000` | Wall-clock limit per command; `0` disables it. |
|
|
105
|
+
| `maxLogBytes` | `1048576` | `65536`–`16777216` | Bytes of tail retained per command. |
|
|
106
|
+
|
|
107
|
+
`command` is the escape hatch for any stack without a detector. It is a shell
|
|
108
|
+
command and runs with the project root as its working directory.
|
|
109
|
+
|
|
110
|
+
## Detection
|
|
111
|
+
|
|
112
|
+
Detection order stops at the first family that declares a command:
|
|
113
|
+
|
|
114
|
+
| Manifest | `test` | `lint` | `typecheck` | `all` |
|
|
115
|
+
|---|---|---|---|---|
|
|
116
|
+
| `package.json` | `scripts.test` | `scripts.lint` | `scripts.typecheck` | `scripts.check`, else the detected targets |
|
|
117
|
+
| `Cargo.toml` | `cargo test` | `cargo clippy -- -D warnings` | `cargo check` | the detected targets |
|
|
118
|
+
| `go.mod` | `go test ./...` | `go vet ./...` | `go build ./...` | the detected targets |
|
|
119
|
+
| pytest config or `tests/` | `pytest` | `ruff check .` with `[tool.ruff]` | `mypy .` with `[tool.mypy]` | the detected targets |
|
|
120
|
+
| `justfile` | `just test` if declared | `just lint` if declared | `just typecheck` if declared | `just check` if declared, else the detected targets |
|
|
121
|
+
| `Makefile` | `make test` if declared | `make lint` if declared | — | `make check` if declared, else the detected targets |
|
|
122
|
+
| any other stack | — | — | — | `check.command` |
|
|
123
|
+
|
|
124
|
+
Node package managers come from the lockfile (`bun`, `pnpm`, `yarn`, `npm`;
|
|
125
|
+
default `npm`). Scripts run as `<pm> run <name>`; Make and Just recipes only by
|
|
126
|
+
their declared names. Nothing is guessed beyond those manifests.
|
|
127
|
+
|
|
128
|
+
A detector is one function returning `DetectedCommands`; adding a family is one
|
|
129
|
+
module entry, not a change to the runtime, screens, or tool.
|
|
130
|
+
|
|
131
|
+
## Failure parsing
|
|
132
|
+
|
|
133
|
+
Known runner lines are recognized first; the generic compiler-style location is
|
|
134
|
+
the fallback for everything else:
|
|
135
|
+
|
|
136
|
+
| Output shape | Parser |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `path:line(:col): error …` | generic, including a Windows `C:\…` path |
|
|
139
|
+
| `FAIL path` and `● test` | Vitest/Jest |
|
|
140
|
+
| `● test` and `at … (path:line:col)` | Jest/Vitest, Java, and bare stack frames |
|
|
141
|
+
| `File "path", line N, in test` | Python tracebacks |
|
|
142
|
+
| `FAILED path::test - message` | pytest |
|
|
143
|
+
| `--- FAIL: Test` and `path:line:` | Go |
|
|
144
|
+
| `panicked at path:line` / `thread 'test'` | Cargo |
|
|
145
|
+
| anything else on a non-zero exit | one fallback entry with the bounded tail |
|
|
146
|
+
|
|
147
|
+
Failures are deduped and capped at 200; the model report shows 20. A miss is
|
|
148
|
+
never turned into an invented location.
|
|
149
|
+
|
|
150
|
+
## Security
|
|
151
|
+
|
|
152
|
+
`check` executes project scripts. That is the same trust boundary as running the
|
|
153
|
+
command yourself or letting the agent call `bash`: a project with a hostile
|
|
154
|
+
`test` script can do anything your user can do. The extension does not add a
|
|
155
|
+
sandbox. `/check` is user-invoked and refuses in plan mode; `run_check` declares
|
|
156
|
+
`readOnlyHint: false`, so a permission policy can gate it, and plan mode blocks
|
|
157
|
+
it.
|
|
158
|
+
|
|
159
|
+
The extension adds no network calls of its own. The command's environment is
|
|
160
|
+
made non-interactive (`CI=1`, `NO_COLOR=1`, `FORCE_COLOR=0`, `TERM=dumb`,
|
|
161
|
+
`GIT_PAGER=cat`, `PAGER=cat`).
|
|
162
|
+
|
|
163
|
+
## Limitations
|
|
164
|
+
|
|
165
|
+
- Detection covers the families above. Other stacks need `check.command`.
|
|
166
|
+
- Parsing is best-effort. A custom reporter that prints neither a location nor a
|
|
167
|
+
known shape falls back to the tail, not to a guessed file.
|
|
168
|
+
- The failure list and detail are TUI-only; other modes print the report.
|
|
169
|
+
- A source excerpt is read only from a regular file inside the project root, at
|
|
170
|
+
most 512 KiB, and only when the reported line exists. A missing, binary,
|
|
171
|
+
oversized, or external file falls back to the bounded tail with the reason.
|
|
172
|
+
- There is no run history, retry policy, or watch mode. Each invocation is one
|
|
173
|
+
run.
|
|
174
|
+
- A test runner that opens a full-screen UI is not supported: stdout is a pipe,
|
|
175
|
+
stdin is closed, and `TERM=dumb`.
|
|
176
|
+
- The tail is bounded; earlier lines are dropped and marked truncated.
|
|
177
|
+
|
|
178
|
+
## Non-goals
|
|
179
|
+
|
|
180
|
+
- Auto-fixing or editing code in response to a failure.
|
|
181
|
+
- A remote CI client, a scheduler, or a durable run queue.
|
|
182
|
+
- Watching files and rerunning on change.
|
|
183
|
+
- Parsing every reporter in existence; the generic location and the bounded tail
|
|
184
|
+
are the contract.
|
|
185
|
+
- Replacing `job` for long-lived processes.
|
|
186
|
+
|
|
187
|
+
## Pi integration
|
|
188
|
+
|
|
189
|
+
| Aspect | Contract |
|
|
190
|
+
|---|---|
|
|
191
|
+
| Registration | `check(pi)` guards on `isExtensionEnabled("check")` and calls `registerCheck(pi)`, which registers `run_check`, `/check`, and one `session_shutdown` hook. The factory only registers. |
|
|
192
|
+
| Tool | `run_check`, `exposure: "direct"`, `executionMode: "sequential"`, annotations `{ readOnlyHint: false, destructiveHint: false, openWorldHint: false }`, TypeBox `parameters` and `outputSchema`, `structuredContent` mirror. |
|
|
193
|
+
| Command | `/check [target]` with `getArgumentCompletions` for the four targets; reports through `ctx.ui.notify`. |
|
|
194
|
+
| State | None persisted. The runtime closure owns the single active child, its controller, and the live line buffer; `session_shutdown` aborts it. |
|
|
195
|
+
| Lifecycle | Each run spawns one process per command and kills the tree on timeout, cancel, or shutdown. No `session_start` work. |
|
|
196
|
+
| Config | `check` section of `bytefly.json`, read once per run. |
|
|
197
|
+
|
|
198
|
+
## Design notes
|
|
199
|
+
|
|
200
|
+
- **The excerpt is guarded like a path-taking tool.** The path comes from command
|
|
201
|
+
output, so `source.ts` resolves it, requires both string and real-path
|
|
202
|
+
containment under the project root, rejects non-files, binaries, and files over
|
|
203
|
+
512 KiB, and checks the line before reading. A miss shows the tail, never a
|
|
204
|
+
guessed source line.
|
|
205
|
+
- **Why `check`.** It runs the project's *check* command, matching `bun run check`
|
|
206
|
+
and the scripts it detects. `/verify` describes a verdict rather than the
|
|
207
|
+
action, and `run_verify` reads worse than `run_check`; the extension,
|
|
208
|
+
command, tool, and config section all use one word.
|
|
209
|
+
- **Generic by design.** Only `detect.ts` knows manifests, and only `failures.ts`
|
|
210
|
+
knows runner output. The runtime, screens, and tool speak in `CommandSpec` and
|
|
211
|
+
`Failure`.
|
|
212
|
+
- **One-shot, not a job.** `job` owns reattachment, stdin, detached lifetime, and
|
|
213
|
+
a registry; `check` owns a foreground verdict. Reusing `job` would make both
|
|
214
|
+
surfaces worse, so the runner is local and smaller.
|
|
215
|
+
- **A bounded tail is the honest fallback.** A parser miss must not invent a
|
|
216
|
+
location; the exit status and the tail are useful on their own.
|
|
217
|
+
- **The transcript is a capped view.** `render.ts` draws from the same `details`
|
|
218
|
+
the model report reads but caps the rows and never prints the report; `/check`
|
|
219
|
+
is still the full navigable list.
|
|
220
|
+
- **The failure list is a `DockListScreen`.** The frame, the id-anchored cursor,
|
|
221
|
+
the navigation keys, the range row, and the detail pane come from
|
|
222
|
+
`lib/tui/dock-screen.ts`, the base `/jobs`, `/watch`, and `/agents` share, so
|
|
223
|
+
this module holds only the check vocabulary. A failure has no id, so its index
|
|
224
|
+
in the finished run's fixed array is the cursor key.
|
|
225
|
+
- **One run at a time.** The runtime refuses a second start instead of
|
|
226
|
+
interleaving outputs or processes.
|
|
227
|
+
|
|
228
|
+
## Files
|
|
229
|
+
|
|
230
|
+
| File | Responsibility |
|
|
231
|
+
|---|---|
|
|
232
|
+
| `index.ts` | Registrar: guard, runtime, tool, command, shutdown. |
|
|
233
|
+
| `types.ts` | `CheckTarget`, `CommandSpec`, `Failure`, `CheckResult`, `RunCheckDetails`, `ActiveCheck`. |
|
|
234
|
+
| `config.ts` | `check` config defaults, clamps, and loader. |
|
|
235
|
+
| `detect.ts` | The detector registry and `check.command` planning. |
|
|
236
|
+
| `runner.ts` | One-shot streaming process, bounded log, timeout, cancel, tree kill. |
|
|
237
|
+
| `failures.ts` | Known-runner and generic failure parsers, stack frames, and the tail fallback. |
|
|
238
|
+
| `source.ts` | Guarded source excerpt around a failure's `path:line`. |
|
|
239
|
+
| `runtime.ts` | Planning, sequencing, aggregation, live lines, cancel/shutdown. |
|
|
240
|
+
| `format.ts` | Summary, failure rows, source-excerpt/detail text, model report. |
|
|
241
|
+
| `tui.ts` | `CheckRunComponent` (a live tail) and `CheckResultComponent` (a `DockListScreen` over the failures). |
|
|
242
|
+
| `tools.ts` | `run_check` schema, registration, the running partial result, and the call/result renderers. |
|
|
243
|
+
| `render.ts` | The transcript result block: status summary, capped failure rows, tail fallback. |
|
|
244
|
+
| `commands.ts` | `/check`, target parsing, completions, TUI/headless flow. |
|
|
245
|
+
|
|
246
|
+
## Testing
|
|
247
|
+
|
|
248
|
+
Co-located tests cover config clamps, every detector family, the runner's
|
|
249
|
+
streaming/timeout/cancel/log cap, the parsers, command planning, aggregation,
|
|
250
|
+
the tool's output contract and transcript block, the command's guards and
|
|
251
|
+
headless path, and both screens' keys and width bounds.
|
|
252
|
+
`extensions/check/check.test.ts` runs a real Node script through the extension,
|
|
253
|
+
so the process path is exercised without depending on Bun or a test framework.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/check` — run the project's verification command with a live tail and a
|
|
3
|
+
* navigable failure list.
|
|
4
|
+
*
|
|
5
|
+
* The TUI gets the two screens; RPC, print, and JSON run headless and report
|
|
6
|
+
* the summary plus the failure list. The command refuses while plan mode is on
|
|
7
|
+
* because running tests is a write to the project (caches, logs, generated
|
|
8
|
+
* files), and the tool is separately blocked by its annotation.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { resolveEffectiveCwd } from "../../lib/env.ts";
|
|
13
|
+
import { isPlanModeEnabled } from "../../lib/plan-state.ts";
|
|
14
|
+
import { withRailsSuppressed } from "../../lib/rails.ts";
|
|
15
|
+
import { formatCheckReport, formatCheckSummary } from "./format.ts";
|
|
16
|
+
import { CheckError, type CheckRuntime } from "./runtime.ts";
|
|
17
|
+
import { CheckResultComponent, CheckRunComponent } from "./tui.ts";
|
|
18
|
+
import type { CheckResult, CheckTarget } from "./types.ts";
|
|
19
|
+
|
|
20
|
+
/** The accepted targets and their completion descriptions. */
|
|
21
|
+
const TARGETS: ReadonlyArray<{ value: CheckTarget; description: string }> = [
|
|
22
|
+
{ value: "test", description: "Run the test command" },
|
|
23
|
+
{ value: "lint", description: "Run the lint command" },
|
|
24
|
+
{ value: "typecheck", description: "Run the typecheck command" },
|
|
25
|
+
{ value: "all", description: "Run every detected command (default)" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Parse one target, or report usage and return undefined. */
|
|
29
|
+
function parseTarget(raw: string, ctx: ExtensionCommandContext): CheckTarget | undefined {
|
|
30
|
+
const token = raw.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
31
|
+
if (token === "") return "all";
|
|
32
|
+
if (TARGETS.some((target) => target.value === token)) return token as CheckTarget;
|
|
33
|
+
ctx.ui.notify(`Unknown check target "${token}". Use test, lint, typecheck, or all.`, "warning");
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The one-line command summary shown while the process runs. */
|
|
38
|
+
function runLabel(commands: readonly string[]): string {
|
|
39
|
+
return commands.length > 0 ? commands.join(" + ") : "check";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The TUI loop: run, then list/detail, with rerun. */
|
|
43
|
+
async function runTui(runtime: CheckRuntime, ctx: ExtensionCommandContext, target: CheckTarget): Promise<void> {
|
|
44
|
+
while (true) {
|
|
45
|
+
let active: ReturnType<CheckRuntime["start"]>;
|
|
46
|
+
try {
|
|
47
|
+
active = runtime.start(target, ctx);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
ctx.ui.notify(error instanceof CheckError ? error.message : String(error), "warning");
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = await ctx.ui.custom<CheckResult | undefined>((tui, theme, _keybindings, done) => {
|
|
54
|
+
const component = new CheckRunComponent({
|
|
55
|
+
command: runLabel(active.commands),
|
|
56
|
+
lines: active.lines,
|
|
57
|
+
elapsedMs: active.elapsedMs,
|
|
58
|
+
theme,
|
|
59
|
+
onCancel: () => active.cancel(),
|
|
60
|
+
requestRender: () => tui.requestRender(),
|
|
61
|
+
viewportRows: () => tui.terminal?.rows,
|
|
62
|
+
});
|
|
63
|
+
active.promise.then(done);
|
|
64
|
+
return component;
|
|
65
|
+
});
|
|
66
|
+
if (!result) return;
|
|
67
|
+
|
|
68
|
+
if (result.status === "passed") {
|
|
69
|
+
ctx.ui.notify(formatCheckSummary(result), "info");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (result.status === "cancelled") {
|
|
73
|
+
ctx.ui.notify("Check cancelled.", "info");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (result.status === "error") {
|
|
77
|
+
ctx.ui.notify(formatCheckReport(result), "error");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const choice = await ctx.ui.custom<"rerun" | undefined>((tui, theme, _keybindings, done) => {
|
|
82
|
+
const component = new CheckResultComponent({
|
|
83
|
+
result,
|
|
84
|
+
cwd: resolveEffectiveCwd(ctx.cwd),
|
|
85
|
+
theme,
|
|
86
|
+
onRerun: () => done("rerun"),
|
|
87
|
+
onClose: () => done(undefined),
|
|
88
|
+
requestRender: () => tui.requestRender(),
|
|
89
|
+
viewportRows: () => tui.terminal?.rows,
|
|
90
|
+
});
|
|
91
|
+
return component;
|
|
92
|
+
});
|
|
93
|
+
if (choice !== "rerun") return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function registerCheckCommand(pi: ExtensionAPI, runtime: CheckRuntime): void {
|
|
98
|
+
pi.registerCommand("check", {
|
|
99
|
+
description: "Run the project's test, lint, or typecheck command and show failures",
|
|
100
|
+
getArgumentCompletions: (prefix) => {
|
|
101
|
+
const trimmed = prefix.trim();
|
|
102
|
+
if (trimmed.includes(" ")) return null;
|
|
103
|
+
const items = TARGETS.filter((target) => target.value.startsWith(trimmed));
|
|
104
|
+
return items.length > 0
|
|
105
|
+
? items.map((target) => ({ label: target.value, value: `${target.value} `, description: target.description }))
|
|
106
|
+
: null;
|
|
107
|
+
},
|
|
108
|
+
handler: async (args, ctx) => {
|
|
109
|
+
if (isPlanModeEnabled(ctx.sessionManager.getBranch())) {
|
|
110
|
+
ctx.ui.notify("Plan mode is read-only. Approve the plan before running checks.", "warning");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const target = parseTarget(args, ctx);
|
|
114
|
+
if (!target) return;
|
|
115
|
+
|
|
116
|
+
if (ctx.mode !== "tui") {
|
|
117
|
+
const result = await runtime.run(target, ctx);
|
|
118
|
+
ctx.ui.notify(
|
|
119
|
+
result.status === "passed" ? formatCheckSummary(result) : formatCheckReport(result),
|
|
120
|
+
result.status === "error" ? "error" : "info",
|
|
121
|
+
);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
await withRailsSuppressed(pi, () => runTui(runtime, ctx, target));
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for the check extension.
|
|
3
|
+
*
|
|
4
|
+
* The `check` section of the shared `bytefly.json` (global then project, project
|
|
5
|
+
* wins). Missing or malformed documents are ignored, and every value is clamped
|
|
6
|
+
* to a safe range.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { clampInteger, cleanString, loadConfigSection } from "../../lib/config.ts";
|
|
10
|
+
|
|
11
|
+
export interface CheckConfig {
|
|
12
|
+
/** Shell command used instead of detection for every target. Empty = detect. */
|
|
13
|
+
command: string;
|
|
14
|
+
/** Wall-clock limit for one command; 0 disables it. */
|
|
15
|
+
timeoutMs: number;
|
|
16
|
+
/** Bytes of output retained per command. */
|
|
17
|
+
maxLogBytes: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_CONFIG: CheckConfig = {
|
|
21
|
+
command: "",
|
|
22
|
+
timeoutMs: 600_000,
|
|
23
|
+
maxLogBytes: 1_048_576,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Normalize one merged config object over the running base. */
|
|
27
|
+
export function normalizeConfig(raw: Record<string, unknown> | undefined, base: CheckConfig): CheckConfig {
|
|
28
|
+
if (!raw) return base;
|
|
29
|
+
return {
|
|
30
|
+
command: cleanString(raw.command, base.command),
|
|
31
|
+
timeoutMs: clampInteger(raw.timeoutMs, base.timeoutMs, 0, 3_600_000),
|
|
32
|
+
maxLogBytes: clampInteger(raw.maxLogBytes, base.maxLogBytes, 65_536, 16_777_216),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Load the effective config for a working directory. */
|
|
37
|
+
export function loadConfig(cwd: string): CheckConfig {
|
|
38
|
+
return loadConfigSection(cwd, "check", DEFAULT_CONFIG, normalizeConfig);
|
|
39
|
+
}
|