Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
# listing), so a Node toolchain here would be dead weight. The one thing a
# Node job would add is the V8/worker_threads side of bggrep's bounded
# matching — verified by hand with `node --test extension/index.test.ts`
# (193 pass, ~20s). Re-run that by hand after touching the bggrep worker.
# (214 pass, ~20s). Re-run that by hand after touching the bggrep worker.
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
Expand Down
194 changes: 156 additions & 38 deletions README.md

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions docs/dogfooding.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Dogfooding bgrun in this repo

This repo's maintainers run pi-bgrun on its own test suite. The setup is a
*personal* project config, not repo policy: `<project>/.pi/pi-bgrun.json` is read
*personal* project config, not repo policy: `<project>/$CONFIG_DIR/pi-bgrun.json` is read
only for a trusted project, it changes what every `bgrun` job in the checkout
does, and one of its keys runs a shell command at wake time. So it is gitignored
here — copy the example below into your own working copy if you want the same.
Expand All @@ -22,8 +22,9 @@ here — copy the example below into your own working copy if you want the same.

## What it does

- `showCompletedJobs: true` — finished jobs stay in the widget and in
`bgstatus` instead of disappearing (the extension's default is `false`).
- `showCompletedJobs: true` — finished jobs stay in `bgstatus` instead of
disappearing (the extension's default is `false`). The live panel shows only
running jobs; the status line holds the latest outcome.
- `digest[0]` — a **scorecard**: at wake time, for a job whose `type` is `test`
*and* whose command matches `*bun test*`, the `command` runs with `$1` set to
the job's log path, and its stdout is appended to the wake as
Expand Down Expand Up @@ -62,7 +63,7 @@ through to the type-less/glob pass (and, with no entry there, gets no scorecard)

Config layers are `defaults ← user ← project ← env`, so an environment variable
beats the file (`PI_BGRUN_SHOW_COMPLETED=0`, `PI_BGRUN_MAX_LOG_BYTES=0`). To drop
the setup, delete `.pi/pi-bgrun.json`; nothing else depends on it.
the setup, delete `$CONFIG_DIR/pi-bgrun.json`; nothing else depends on it.

## See also

Expand Down
12 changes: 6 additions & 6 deletions docs/log-size-ceiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ machine down. A secondary effect: `countLogLines` streams the whole file at exit

Any fix has to respect these; most of the wrapper's odd shape is one of them.

1. The cap must live **inside the detached process tree** — pi can exit at any
time, so no parent-side streaming. A watchdog that dies with pi does not keep
the promise.
1. The cap must live **inside the detached process tree** — the agent can exit at
any time, so no parent-side streaming. A watchdog that dies with the agent
does not keep the promise.
2. The `__BGRUN_EXIT__` marker must remain the **last non-empty line**.
Completion evidence is the *last* non-blank line only, so a marker that ends
up mid-file is job output that happens to contain the string. A head cap
Expand Down Expand Up @@ -168,9 +168,9 @@ They are listed because they explain why the wrapper is not simpler.

- **`head -c` without a drain**: producer dies on SIGPIPE (`141`) — hostile to
legitimately verbose builds.
- **pi-side watchdog** (stat running logs, kill and truncate the tail): a soft
bound only while pi lives; the overshoot is write-rate × poll interval, and it
is unbounded if pi died. Keeps the tail, loses the promise.
- **agent-side watchdog** (stat running logs, kill and truncate the tail): a soft
bound only while the agent lives; the overshoot is write-rate × poll interval,
and it is unbounded if the agent died. Keeps the tail, loses the promise.
- **`ulimit -f`**: caps *every* file the job writes (artifacts, downloads) and
kills it (`SIGXFSZ`/`153`). Opt-in material, not a default.
- **Document-and-trim-finished-logs**: no bound at all while the job runs.
Expand Down
69 changes: 56 additions & 13 deletions extension/digestPresets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,20 +222,31 @@ function labelFromMatchName(pattern: string): string {
}

/**
* One-line diagnostic for the silent no-digest case: a digest IS configured
* but no entry selected for this job. The usual causes are a `type` the agent
* never passes (or spells differently) and a `match` glob that never fires.
* Pure — the wake path decides whether to log it.
* The distinct `type` values a digest config declares, in config order. Empty
* when no entry is typed. Single-sourced because three surfaces report the same
* vocabulary — the no-match log line, the wake note, and bgrun's spawn hint —
* and they must never disagree about what the project offers.
*/
export function digestNoMatchWarning(
target: DigestJobTarget,
entries: DigestEntry[],
): string {
const declaredTypes = [
...new Set(
entries.map((e) => e.type).filter((t): t is string => typeof t === "string"),
),
];
export function digestTypes(entries: readonly DigestEntry[]): string[] {
return [
...new Set(
entries
.map((e) => e.type)
.filter((t): t is string => typeof t === "string"),
),
];
}

/**
* The no-match diagnostic's shared facts: how the job was identified and which
* types the config declares. Both the log line and the wake note are built from
* these, so the two can never describe different things.
*/
function digestNoMatchParts(
target: DigestJobTarget,
entries: DigestEntry[],
): { job: string; types: string } {
const declaredTypes = digestTypes(entries);
const job =
target.type === undefined
? target.name === undefined
Expand All @@ -245,9 +256,41 @@ export function digestNoMatchWarning(
const types = declaredTypes.length
? ` — configured types: ${declaredTypes.join(", ")}`
: "";
return { job, types };
}

/**
* One-line diagnostic for the silent no-digest case: a digest IS configured
* but no entry selected for this job. The usual causes are a `type` the agent
* never passes (or spells differently) and a `match` glob that never fires.
* Pure — the wake path decides whether to log it.
*/
export function digestNoMatchWarning(
target: DigestJobTarget,
entries: DigestEntry[],
): string {
const { job, types } = digestNoMatchParts(target, entries);
return `[pi-bgrun] digest configured but selected no entry for ${job}${types}`;
}

/**
* Agent-facing form of the same diagnostic, for the wake message. oh-my-pi
* routes `pi.logger` output to a file the *agent* never reads, so a bare log
* line would leave a mismatched `type` invisible to the only party that can fix
* it. Phrased as an instruction rather than a log line; the wake path emits it
* at most once per distinct mismatch (see DIGEST_NO_MATCH_WARN_CAP).
*/
export function digestNoMatchWakeLine(
target: DigestJobTarget,
entries: DigestEntry[],
): string {
const { job, types } = digestNoMatchParts(target, entries);
return (
`digest: no scorecard selected for ${job}${types} — ` +
"pass the matching `type` on the next bgrun call to get one"
);
}

/**
* Select the digest entry for a job and resolve it to a concrete command +
* wake label. Selection order:
Expand Down
Loading
Loading