@stablekernel/pi-background-run 0.5.0 → 0.6.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/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@stablekernel/pi-background-run",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Run long shell commands detached in the background for pi; get woken on completion. Output lands in a file; context stays clean.",
5
5
  "type": "module",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
6
9
  "license": "MIT",
7
10
  "publishConfig": {
8
11
  "access": "public"
@@ -15,15 +18,18 @@
15
18
  "./extension/index.ts"
16
19
  ],
17
20
  "skills": [
18
- "./skill/run-bg"
21
+ "./skill/run-bg",
22
+ "./skill/digest-config"
19
23
  ]
20
24
  },
21
25
  "scripts": {
22
26
  "test": "bun test extension/index.test.ts",
23
- "lint": "tsc --noEmit"
27
+ "lint": "tsc --noEmit",
28
+ "lint:pr-title": "bun scripts/check-conventional-commit.ts"
24
29
  },
25
30
  "files": [
26
31
  "extension/",
32
+ "!extension/index.test.ts",
27
33
  "skill/",
28
34
  "README.md",
29
35
  "LICENSE"
@@ -56,6 +62,7 @@
56
62
  "devDependencies": {
57
63
  "@earendil-works/pi-coding-agent": "*",
58
64
  "@earendil-works/pi-tui": "*",
65
+ "@types/node": "^24.0.0",
59
66
  "typebox": "^1.3.0",
60
67
  "typescript": "^5.7.0"
61
68
  }
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: digest-config
3
+ description: Set up the pi-bgrun digest scorecard for this project. Use when the user asks to configure a digest, enable digest heuristics, or when a pi-bgrun nudge points at this skill. Samples the project's real job logs, picks a shipped preset (go-test, jest, pytest, junit-xml) or drafts a custom digest command, validates it against green AND red logs, then writes the digest section into .pi/pi-bgrun.json.
4
+ ---
5
+
6
+ # Configure a project digest scorecard
7
+
8
+ Goal: a `digest` section in `<project>/.pi/pi-bgrun.json` whose command turns a
9
+ job log into a short pass/fail scorecard, appended to every `bgrun` wake as
10
+ `digest (<label>): ...`. The scorecard must be reliable on both green and red
11
+ logs — a wrong scorecard is worse than none. Most projects run more than one
12
+ kind of job (unit tests, a build, e2e); configure one entry per job type rather
13
+ than one command that guesses.
14
+
15
+ Config shapes — a single object (legacy) or an ordered **list** of entries; if
16
+ both `preset` and `command` are set within one entry, the preset wins:
17
+
18
+ ```json
19
+ { "digest": { "preset": "go-test" } }
20
+ ```
21
+
22
+ ```json
23
+ { "digest": { "command": "grep -E 'FAIL|ok ' \"$1\" | head -5" } }
24
+ ```
25
+
26
+ ```json
27
+ {
28
+ "digest": [
29
+ { "type": "test", "preset": "go-test" },
30
+ { "type": "build", "label": "build",
31
+ "command": "grep -E '^error' \"$1\" | head -5" },
32
+ { "match": { "command": "*cargo*" }, "label": "cargo", "preset": "go-test" },
33
+ { "preset": "go-test" }
34
+ ]
35
+ }
36
+ ```
37
+
38
+ Prefer a `type` on each entry: it is matched exactly (case-insensitive) against
39
+ the `type:` the agent passes to `bgrun`, so it does not depend on job names or
40
+ command lines staying stable. When you configure a `type`, tell the agent to
41
+ pass it: `bgrun(command: …, name: …, type: "test")`. If a job's `type`/name
42
+ selects no entry, pi-bgrun logs a one-line diagnostic naming the job and the
43
+ configured types — check it when a scorecard is expected but absent.
44
+
45
+ Selection (exactly one entry, or none):
46
+
47
+ 1. Entries with a `type` are checked **first**, in config order, and match only
48
+ a job declaring that exact type that also satisfies the entry's `match` if
49
+ it has one. First type match wins.
50
+ 2. Otherwise the entries **without** a `type` are scanned in config order:
51
+ `match.name` / `match.command` globs and no-`match` defaults.
52
+ 3. No match → no digest.
53
+
54
+ - `type` and `match` compose (AND): with both present, only a job of that type
55
+ that also satisfies the glob matches.
56
+ - `match.name` / `match.command` are **globs** tested against the job's
57
+ `name` and command line; both present → both must match. They are
58
+ **case-insensitive and whole-string** (`*` any run, `?` one character;
59
+ write `*text*` for a substring; `\` escapes the next character).
60
+ - An entry with no `match` (or an empty `match`) matches every job — put it
61
+ **last** as the default. Include one so jobs you did not anticipate still
62
+ get a scorecard.
63
+ - `label` sets the wake tag; without it a type entry uses its `type` string, a
64
+ glob entry uses the matched `match.name`, else the preset id (or `command`).
65
+ - An invalid `match` (non-string), an invalid `type`, or an entry with no valid
66
+ `preset`/`command` is dropped silently; an empty/all-invalid list counts as
67
+ unconfigured.
68
+
69
+ Shipped presets: `go-test` (package ok/FAIL counts + failing test names),
70
+ `jest` (Tests/Test Suites summary + failed test names), `pytest` (final
71
+ passed/failed/error summary line + FAILED test ids), `junit-xml`
72
+ (`<failure>`/`<error>` counts + failing testcase names). Each preset also
73
+ carries a `suggestedType` (all `test`) — use it as the `type` when scaffolding
74
+ an entry, e.g. `{ "type": "test", "preset": "go-test" }`. The suggestion is
75
+ advisory; a preset entry with no `type` still applies to every job.
76
+
77
+ ## Procedure
78
+
79
+ 1. **Find done-job logs.** Locate the project's jobsDir from the config
80
+ layering: in a project root it defaults to `<project>/.pi-bgrun/jobs`;
81
+ otherwise to the machine-global `~/.pi-bgrun/jobs`. An explicit `jobsDir`
82
+ (any config layer) or `PI_BGRUN_DIR` overrides it; `PI_BGRUN_GLOBAL_DIR`
83
+ retargets the machine-global base. The resolved path is printed as
84
+ `log: <path>` by `bgrun` and by `bgstatus <id>` — read it back there if
85
+ unsure. List the `*.log` files of finished jobs.
86
+ 2. **Sample the formats across job types.** Group the logs by job type using
87
+ each job's `name` and command line (from `bgstatus`); most projects have at
88
+ least a test job and a build job. Pick 2-3 logs per type — at least one
89
+ green and one red run each — and inspect them with `ctx_execute_file`
90
+ (context-mode sandbox, so only your printed summary enters context).
91
+ Identify the runner / output format for each type, and name the type with a
92
+ short token (`test`, `build`, `lint`, `e2e`).
93
+ 3. **Try a preset first, per type.** Run each shipped preset's command against
94
+ a sample log (`sh -c '<preset command>' sh <logpath>`). Preset commands are
95
+ data in the package's `extension/digestPresets.ts`. Clean scorecard on green
96
+ AND red samples → use that preset for that type, seeding the entry's
97
+ `type` from the preset's `suggestedType` (all shipped presets suggest
98
+ `test`). Repeat for each type.
99
+ 4. **Draft custom commands** for types no preset fits. Use awk/sed/grep/jq; the
100
+ command receives the log path as `$1` and MUST end in `head -N` so output is
101
+ bounded. Keep it to a count line plus failed-item names.
102
+ 5. **Validate each entry on green AND red.** Run every drafted command against
103
+ every sample for its type. Each must produce a correct scorecard on both: no
104
+ phantom failures on green logs, no missing failures on red ones. If a type
105
+ has no reliable command, omit that entry (or leave the digest unconfigured)
106
+ rather than shipping a wrong scorecard — say why.
107
+ 6. **Write the config, one `type` entry per job type.** Merge the entries into
108
+ `<project>/.pi/pi-bgrun.json`, preserving any existing keys, giving each
109
+ entry the `type` you identified in step 2, and putting the no-`match`
110
+ default entry **last**. Use `match.name` / `match.command` globs only for
111
+ jobs that will not pass a `type`. Create the file if absent. Tell the user
112
+ (or the agent driving `bgrun`) which `type:` value to pass for each job.
113
+ 7. **Smoke-test each entry.** Start a real `bgrun` job of each configured type
114
+ (e.g. the test command AND the build command), passing the matching
115
+ `type:`, and check that its wake carries a correct `digest (<label>):` block
116
+ for the right entry. If a block is empty, wrong, or comes from the wrong
117
+ entry, fix the command / type / ordering and repeat step 7.
@@ -28,7 +28,7 @@ no polling.
28
28
 
29
29
  | Action | Tool |
30
30
  |---|---|
31
- | Start | `bgrun(command: "make test-short", name: "unit-tests")` → `started: <job-id>` (name is an optional short label; use it so jobs are recognizable in `bgstatus`, the status widget, and wake messages) |
31
+ | Start | `bgrun(command: "make test-short", name: "unit-tests", type: "test")` → `started: <job-id>` (name is an optional short label; use it so jobs are recognizable in `bgstatus`, the status widget, and wake messages) |
32
32
  | Status | `bgstatus(<job-id>)` for one job, or `bgstatus()` for this session's running jobs — finished jobs are hidden by default; pass `includeDone: true` to list them |
33
33
  | Tail | `bgtail(<job-id>, 40)` — first read: last-40 tail; later reads: only lines appended since (delta tailing) |
34
34
  | Grep | `bggrep(<job-id>, "pattern", context?)` — line-numbered matches, capped and condensed; default pattern = generic failure signatures (override when you know the format) |
@@ -37,7 +37,9 @@ no polling.
37
37
  ## Workflow
38
38
 
39
39
  1. **Start:** call `bgrun` with the command (and a short `name`, e.g. `name: "unit-tests"`).
40
- Note the returned job-id. Continue other
40
+ When the project's digest config defines `type` entries, also pass the
41
+ matching `type` (e.g. `type: "test"`) — like `name`, it helps the wake
42
+ select the right digest scorecard. Note the returned job-id. Continue other
41
43
  work; you will be woken automatically when the job finishes.
42
44
  2. **On wake:** check the exit status in the wake message first.
43
45
  - `exit: 0` → success. `bgtail` to confirm.
@@ -48,23 +50,37 @@ no polling.
48
50
  - `done exit=<non-zero>` → failure; analyze the log.
49
51
  - `running` but the job should have finished long ago → likely crashed (the
50
52
  process died without writing the exit marker). Analyze the log with
51
- `ctx_execute_file`.
53
+ `bggrep` (any jobs dir, last 2 MB) or `ctx_execute_file` on the absolute path
54
+ (whole file — needed for logs bigger than 2 MB).
52
55
 
53
56
  ### Reading results without flooding context
54
57
 
58
+ If the wake message carries a `digest (<label>):` block (the label is the
59
+ entry's `label`, its type, a matched `match.name`, or the preset id /
60
+ `command`), read that
61
+ first — it is a short pass/fail scorecard configured for this project and
62
+ usually answers "what failed" without any follow-up read. `bgtail` stays the
63
+ positional-peek tool for everything else.
64
+
55
65
  - **Quick peek (≤40 lines):** call `bgtail` with the job id and `lines: 40` — strips the `__BGRUN_EXIT__` marker. The first read returns the last-40 tail; repeat reads return only lines appended since your last read (delta tailing) — polling a running job is nearly free.
56
- - **Failure extraction:** `bggrep(<job-id>, "pattern")` — line-numbered matches with optional context lines, capped and condensed. Works on global jobs dirs that `ctx_execute_file` cannot reach (it runs inside the extension). Pass your own pattern whenever you know the tool's output format; the default only catches common failure signatures.
57
- - **Whole-log failure analysis:** `ctx_execute_file` on the log path:
66
+ - **Failure extraction:** `bggrep(<job-id>, "pattern")` — line-numbered matches with optional context lines, capped and condensed. Resolves the job id to the configured jobs dir itself — no path to reconstruct. (`ctx_execute_file` can read the same file given its absolute path.) Searches the last 2 MiB by default; `bytes: 67108864` widens it to the whole capped log — more scanning costs latency and memory, **not context**, since the returned matches stay capped. Pass your own pattern whenever you know the tool's output format; the default only catches common failure signatures.
67
+ - **Whole-log failure analysis:** `ctx_execute_file` on the log's **absolute
68
+ path**. Unlike `bgtail`/`bggrep` (bounded to the last 2 MB), this reads the
69
+ whole file — the only way to cover a log bigger than 2 MB, e.g. one that hit
70
+ the size ceiling. Copy the `log:` path from `bgrun`'s `started:` line and
71
+ expand `~` yourself (it is not expanded for you; the tool takes an absolute
72
+ path or one relative to the project root). Otherwise it is an ordinary tool
73
+ call: your normal Read-deny rules still apply.
58
74
 
59
75
  ```javascript
60
76
  ctx_execute_file(
61
- path: "~/.pi-bgrun/jobs/<JOB>.log",
77
+ path: "/Users/me/project/.pi-bgrun/jobs/<JOB>.log",
62
78
  language: "javascript",
63
79
  code: "const L=FILE_CONTENT.split('\\n'); \
64
80
  const fails=L.filter(l=>/(--- FAIL|FAIL|panic:|Error:)/.test(l)); \
65
81
  console.log(`lines: ${L.length}, failures: ${fails.length}`); \
66
82
  console.log(fails.slice(0,40).join('\\n'));"
67
- )
83
+ })
68
84
  ```
69
85
 
70
86
  A 10 000-line `make test` log collapses to a ~30-line summary in context.
@@ -74,17 +90,36 @@ no polling.
74
90
  - `bash grep` output is uncapped — a retry-storm log can dump thousands of
75
91
  matching lines (megabytes) straight into context, and staying safe depends
76
92
  on remembering `| head` on every single call. `bggrep` is bounded by design
77
- (~50 matches, ~2KB/line, ~8KB).
93
+ (last 2 MB of the log, per-line 10 000-char pre-truncation, ~50 matches,
94
+ ~8KB, plus a wall-clock match budget so a runaway regex errors instead of
95
+ hanging).
78
96
  - It takes the job id — no log-path reconstruction, no shell-quoting of the
79
- regex and works on any jobs dir, including global logs that
80
- project-sandboxed `ctx_execute_file` cannot reach.
97
+ regex, and no reliance on the agent getting `~` expansion right.
81
98
  - Output is self-describing: match count, line numbers, `…[N skipped]…` gap
82
99
  markers, `— none` for no-match.
83
100
 
84
101
  Plain `grep` via bash is fine only for a one-off search you know is tiny.
85
102
 
86
103
  **Never `cat`, `Read`, `bash cat`, or `bash grep` a full bgrun log.** Always
87
- `bgtail`, `bggrep`, or `ctx_execute_file`.
104
+ `bgtail`, `bggrep`, or (for project-local logs) `ctx_execute_file`.
105
+
106
+ **Order of preference, cheapest first: `bgtail` → `bggrep` → `ctx_execute_file`.**
107
+ Reach for the sandbox only when you need something a regex over lines cannot
108
+ express — totals, dedup, grouping, joining the log against another file.
109
+
110
+ `ctx_execute_file` is not itself a context dump: the file's bytes never enter
111
+ context, only your script's **stdout** does ("raw content never leaves"). So the
112
+ cost is exactly what you print — which makes `console.log(FILE_CONTENT)` (or
113
+ `print(open(path).read())`, or a big unbounded slice) the one way a whole-log
114
+ analysis turns into a context dump, and a capped-by-default 64 MiB log makes
115
+ that expensive rather than merely rude. Aggregate, then cap what you print:
116
+
117
+ - print counts / grouped summaries / the first N matches — not the content;
118
+ - keep a `.slice(0, 40)` / `[:40]` on anything you echo;
119
+ - for many different questions about one big log, index it once (`ctx_index`)
120
+ and `ctx_search` it, instead of re-scanning the file per call;
121
+ - `bgtail` with a larger `lines`, or a tighter `bggrep` pattern, is usually the
122
+ cheaper answer to "I need to see more".
88
123
 
89
124
  ## After a pi restart or session switch
90
125
 
@@ -93,23 +128,45 @@ Plain `grep` via bash is fine only for a one-off search you know is tiny.
93
128
  - After a restart/switch, run `bgstatus(<job-id>)` — the id still resolves via the
94
129
  log's `__BGRUN_EXIT__=N` marker. To browse everything on disk, use
95
130
  `bgstatus(includeDone: true)`.
96
- - Each session only tracks its own jobs by default. Jobs from other sessions
97
- appear only when `adoptForeignJobs` is enabled in `~/.pi/agent/pi-bgrun.json`
98
- (or `PI_BGRUN_FOREIGN_JOBS=1`).
131
+ - Each session only tracks its own jobs by default. Running jobs from other
132
+ sessions appear only when `adoptForeignJobs` is enabled in
133
+ `~/.pi/agent/pi-bgrun.json` (or `PI_BGRUN_FOREIGN_JOBS=1`); finished foreign
134
+ logs appear with `bgstatus(includeDone: true)` regardless.
99
135
 
100
136
  ## Rules
101
137
 
102
138
  - Call the tools; never hand-roll `nohup … &` inline.
103
139
  - One job = one id. Multiple concurrent jobs are fine — each has its own log.
104
- - Logs live in `~/.pi-bgrun/jobs` (override with `PI_BGRUN_DIR`). A **relative**
105
- `jobsDir` in the project config (e.g. `.pi-bgrun/jobs`) puts logs inside the
106
- project auto-ignored via `.git/info/exclude` which keeps them reachable
107
- for project-sandboxed analysis tools like `ctx_execute_file`.
140
+ - Job logs are capped by default (`maxLogBytes` / `PI_BGRUN_MAX_LOG_BYTES`,
141
+ 64 MiB; `0` = unlimited) and the cap keeps the **first** bytes. A log that
142
+ ends with `__BGRUN_TRUNC__ output truncated: kept the first <N> bytes` (or
143
+ `__BGRUN_NOCAP__ log ceiling unavailable`, when the ceiling could not be
144
+ installed and the job ran uncapped) hit that ceiling: output past it was dropped, not lost to a failure — the job
145
+ still ran to completion with its real exit code, and readers (`bgtail`,
146
+ `bggrep`, the wake's line count/last line) filter the notice out. The wake's
147
+ Stats line, `bgtail` and `bggrep` all say when a log was capped (and report
148
+ `truncatedAtBytes` in their details), and a configured digest scorecard is
149
+ skipped rather than scored against an incomplete log — so on a capped job,
150
+ read a missing digest as "unknown", **not** as "no failures", and do not
151
+ re-run the command to see the missing tail; raise the ceiling if you need the
152
+ whole log. The flag lives in the exit marker (`__BGRUN_EXIT__=0
153
+ truncated=<N>`, or `nocap=1`), never in printable text — the `__BGRUN_*__`
154
+ lines are reserved, so a notice-looking line printed by the command itself is
155
+ content, not a signal. A search window is also limited to its last
156
+ 500 000 lines: when that bites, `bgtail`/`bggrep` say so — a "none" from a
157
+ trimmed window means the head was not searched.
158
+ - Logs default to `<project>/.pi-bgrun/jobs` in a repo — project-scoped is the
159
+ model (`~/.pi-bgrun/jobs` is a deprecated fallback for a cwd with no project
160
+ root; an absolute `PI_BGRUN_DIR`/`jobsDir` still works but is legacy). Project-local dirs are
161
+ auto-ignored via `.git/info/exclude`, which keeps `git status` clean; the
162
+ logs stay reachable for project-sandboxed analysis tools like
163
+ `ctx_execute_file` because they live inside the project.
108
164
  - Cleanup: `bgclean` removes only THIS session's old logs; `bgclean all`
109
165
  sweeps every session's. Auto-sweeps at session start/shutdown are
110
- session-scoped plus a global orphan pass (default on — removes finished
111
- week-old logs from crashed/abandoned sessions; disable with
112
- `globalAutoClean: false` / `PI_BGRUN_GLOBAL_AUTO_CLEAN=0`). Retention is
113
- `cleanupDays` (default 7, configurable).
114
- - To stop a running job, use `bash` with `kill <pid>` (the pid is in the `bgstatus`
115
- output). There is no `bgkill` tool.
166
+ session-scoped plus an orphan pass (default on — removes finished week-old
167
+ logs from crashed/abandoned sessions; disable with `globalAutoClean: false`
168
+ / `PI_BGRUN_GLOBAL_AUTO_CLEAN=0`). Under the project-local default the orphan
169
+ pass covers the current project's jobs dir AND the machine-global
170
+ `~/.pi-bgrun/jobs`; an explicit absolute `jobsDir` is swept alone. Retention
171
+ is `cleanupDays` (default 7, configurable).
172
+ - To stop a running job, use `bash` with `kill -- -<pid>` (process group — required because the child is spawned detached). The pid is the last `--`-separated segment of the job id; it is not shown as a separate field in `bgstatus` output. There is no `bgkill` tool.