live: heartbeat sync 2026-10-30T17:23:49-07:00 (Refs #11)
This commit is contained in:
1 parent
92c127ca08
commit
674a22be48
18 files changed
+226
-65
No files matched your search
@@ -0,0 +1,59 @@
|
||||
---
|
||||
name: container-port-image-identity-binding
|
||||
description: "Diagnose or write fleet gates that silently verify the WRONG process/image: /health answered by a dying container, podman inspect naming a merely-created container, cgroup lacking the service name, RepoDigests[0] vs the pin, detached launchers losing cwd. Bind every fact to its object by host pid."
|
||||
---
|
||||
|
||||
# Container / port / image identity binding
|
||||
|
||||
For fleet gates, restart checks, "is it really up?" triage, and any bench/window harness that stops
|
||||
a production service and starts a rig on the same port. Use when a check *passes* but the thing
|
||||
still misbehaves, or when the same class of silent failure keeps recurring.
|
||||
|
||||
## The failure signature (all of these mean "you checked a proxy")
|
||||
|
||||
| Check that lied | What it actually measured |
|
||||
|---|---|
|
||||
| `curl /health` = 200 | whichever process owned the socket, often the one being stopped |
|
||||
| `podman inspect -f '{{.State.Running}}'` | a container that was created, not one that bound a port |
|
||||
| `podman ps` / container name | existence, not service |
|
||||
| `pgrep -f "<pattern>"` | your own polling shell, or a sibling arm |
|
||||
| log mtime advancing | some request completed, not necessarily yours |
|
||||
| `df <dir>` | a filesystem that merely looks related to the one the bytes go to |
|
||||
| `podman image inspect -f '{{index .RepoDigests 0}}'` | ONE of several digests — a by-digest pull makes an image carry the tag digest first |
|
||||
| rootless cgroup path | the container **ID**, never the service/container name |
|
||||
|
||||
## The bindings that work
|
||||
|
||||
- **Listener → process:** `ss -H -ltnp 'sport = :<port>'` → `pid=`; needs privilege for foreign
|
||||
processes (`sudo ss` when the listener is root's).
|
||||
- **Host pid → container:** `podman top <ctr> hpid` (header line first).
|
||||
**`podman top <ctr> pid` prints container-internal pids (`1`, `7`) and compares against nothing** —
|
||||
this is the single most expensive mistake in this class.
|
||||
- **Image pin:** `podman image inspect -f '{{range .RepoDigests}}{{println .}}{{end}}' <ref>` and test
|
||||
*membership* of the pin, never position. Message the difference: "present but does not carry X".
|
||||
- **Device holder:** `fuser /dev/kfd` → pids → resolve each through `podman top <ctr> hpid`; anything
|
||||
outside the allowed set is foreign (an orphaned engine wedges the next boot as `device error`).
|
||||
- **Detached launcher:** assert cwd (`cd "$(dirname "$0")/.."`; verify a known executable exists) and
|
||||
write a `PASS/FAIL` line per fact. `setsid nohup … &` turns a lost cwd into silence, not an error.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Write ONE preflight that prints `PASS <fact> …` / `FAIL <fact> — reason`, exit 2 on any FAIL.
|
||||
Facts: cwd, image digest membership, free space on the real target filesystem, stale artifacts,
|
||||
GPU holders, listener, inputs, reference data.
|
||||
2. Call it as step 0 of the orchestrator, before any GPU/port is touched. Delete the duplicated
|
||||
inline checks so the fact has one owner.
|
||||
3. Pin each lie with a bats test using PATH stubs (`podman`, `ss`, `fuser`): wrong digest, foreign
|
||||
listener pid, foreign GPU pid, dirty dir, wrong cwd. Fixture files must be NON-EMPTY — a `-s`
|
||||
fact on a `: > file` fixture fails for the wrong reason.
|
||||
4. Keep one driver. Ad-hoc copies beside the orchestrator drop guards, and two drivers racing on one
|
||||
port produce phantom "strays" that look like a hijack.
|
||||
|
||||
## Traps
|
||||
|
||||
- `podman top … pid` vs `hpid` — see above.
|
||||
- Stub collision: a test stub that answers `top` for both the rig and production must branch on the
|
||||
container name, or the test proves nothing.
|
||||
- A gate that reports a stale-but-present image must distinguish "absent" from "retagged/older" —
|
||||
the operator's fix differs.
|
||||
- Verify a kill by re-reading the port; `podman rm -f` frees the cgroup, a pattern-kill may not.
|
||||
@@ -1,58 +1,48 @@
|
||||
---
|
||||
name: dankvoice-bundle-swap-red-proof
|
||||
description: "Prove a dankvoice behavior test is red against the pre-change bundle by pointing DV_EXT at git show HEAD:dist/index.js, with the driver extracted from the bats heredoc into a pinned scratch directory. Use before claiming a new bats assertion is red-capable."
|
||||
description: Prove a dankvoice behavior test is red against the pre-change bundle by pointing DV_EXT at a built old bundle; includes the source-swap fallback when the extracted bundle cannot resolve node_modules outside the repo.
|
||||
---
|
||||
|
||||
Prove a new `tests/behavior.bats` assertion is **red-capable** — that it fails against the *previous* build, not just passes against the new one — without stashing, branching, or editing `src/`.
|
||||
# Red-proofing a dankvoice behavior test against the pre-change bundle
|
||||
|
||||
## Why
|
||||
The suite drives the **built** bundle (`dist/index.js`, rebuilt by `setup()` from `src/`), so "is this new assertion actually red-capable?" is a question about a bundle that no longer exists after the fix. Two routes; try A first, fall back to B.
|
||||
|
||||
`tests/behavior.bats` is a black-box suite: the plugin bundle is loaded through `DV_EXT`, so a *different bundle* is the red side. Stashing the fix would break the `dist` byte-identity pin test and the shared worktree.
|
||||
|
||||
## Working recipe
|
||||
## Route A — `DV_EXT` at the old committed bundle
|
||||
|
||||
```bash
|
||||
cd ~/Projects/dankvoice # or the feature worktree: ~/Projects/wt-dv-*
|
||||
bun run build # current src = the green side
|
||||
git show HEAD:dist/index.js > /tmp/old-dist.js # the red side (pre-change bundle)
|
||||
|
||||
W=/tmp/dv-redproof # PINNED name; see traps below
|
||||
rm -rf "$W" && mkdir -p "$W/bin"
|
||||
cat > "$W/player.sh" <<'SH'
|
||||
#!/usr/bin/env bash
|
||||
printf '%s\n' "$*" >> "$DV_STUB_LOG"
|
||||
for arg in "$@"; do case "$arg" in *.wav) cp "$arg" "$DV_COPY" ;; esac; done
|
||||
sleep "${DV_HOLD:-0.1}"
|
||||
SH
|
||||
chmod +x "$W/player.sh"
|
||||
ln -s "$W/player.sh" "$W/bin/pw-play"
|
||||
printf '#!/usr/bin/env bash\nprintf "116\\talsa_output.usb-KT.0\\taudio/sink\\t*\\n"\n' > "$W/bin/wpctl"
|
||||
chmod +x "$W/bin/wpctl"
|
||||
# The driver lives inside the bats file as a heredoc: extract it verbatim.
|
||||
end=$(grep -n '^TS$' tests/behavior.bats | cut -d: -f1)
|
||||
sed -n "86,$((end - 1))p" tests/behavior.bats > "$W/driver.ts"
|
||||
|
||||
export DV_EXT="$PWD/dist/index.js" DV_STUB="$W/player.sh" DV_STUB_LOG="$W/player.log" \
|
||||
DV_COPY="$W/played.wav" DV_OMPCONFIG=/dev/null DANKVOICE_CONFIG="$W/settings.json" \
|
||||
DV_MODELS="$W/models.yml" DV_WPCTL=x DV_SELECT=""
|
||||
for ext in /tmp/old-dist.js "$PWD/dist/index.js"; do
|
||||
rm -f "$W/player.log" "$DANKVOICE_CONFIG"
|
||||
DV_EXT="$ext" bun "$W/driver.ts" <scenario> 2>&1 | grep -E '^PLAYS|^CALLS|^NOTIFY|^SETTINGS'
|
||||
done
|
||||
git show HEAD:dist/index.js > "$W/old-dist.js"
|
||||
DV_EXT="$W/old-dist.js" DV_STUB=... <same env as the test> bun "$DV_DRIVER" <scenario> # expect RED
|
||||
DV_EXT="$PWD/dist/index.js" <same env> bun "$DV_DRIVER" <scenario> # expect GREEN
|
||||
```
|
||||
|
||||
The scenario name is the driver's third argv (`speak`, `auto`, `repeat`, `humanizecycle`, `badurl`, …); the bats `-f` test body's env (`DV_PLAYER_BIN`, `DV_SETTINGS_JSON`, `DANKVOICE_VERBOSITY`, …) is passed the same way. `DV_SELECT` holds the menu answers, `|`-separated, consumed in order.
|
||||
Build the driver harness by extracting the heredoc `setup()` writes into `tests/behavior.bats`:
|
||||
|
||||
## Traps, each one cost a turn
|
||||
```bash
|
||||
sed -n '<first-body-line>,<last-body-line>p' tests/behavior.bats > "$W/driver.ts"
|
||||
```
|
||||
|
||||
- **The scratch directory must be pinned by name.** `ls -dt /tmp/tmp.*` picks up *other sessions'* temp dirs, and then `player.sh` and `driver.ts` live in different directories — the run silently does the wrong thing.
|
||||
- **The stub must append to `$DV_STUB_LOG`.** The driver counts player invocations from that file; a stub that only `sleep`s reports `PLAYS 0` for *both* bundles and the whole proof reads as "fix does nothing".
|
||||
- **`DV_EXT` is required.** Missing it yields `Cannot find package 'undefined'` from the driver, which looks like a broken driver, not a missing env var.
|
||||
- **`DV_SETTINGS_JSON` supports `__PORT__`** (the driver substitutes the fixture port), so a preseed can name a prefixed route — needed whenever the test is about *which* endpoint got resolved.
|
||||
- **Never run this while a `bats` run is in flight** in the same repo: `setup()` runs `bun run build`, so two runs clobber `dist/` and the pin test.
|
||||
Re-check that range after every edit that adds lines to the driver (`console.log` census blocks included) — a stale end line silently appends the `TS` terminator and the driver dies after printing, which looks like a passing probe.
|
||||
|
||||
## Reading the result
|
||||
Copy the project's real player stub, `wpctl` fixture, `models.yml` and `ompconfig.yml` verbatim; a simplified stub prints a false `PLAYS 0`.
|
||||
|
||||
Red side must show the *defect* (`PLAYS 0`, `644`, a chat `CALL` with the wrong prefix, `Invalid URL`), green side the fix — assert on printed markers only (`PLAYS`, `CALLS`, `CALL <url>`, `NOTIFY`, `SETTINGS`). `CALLS` counts `/audio/speech` calls only; the rewrite call appears as its own `CALL /…/chat/completions … sys=<full system prompt>` line, which is how prompt-level knobs (verbosity, heckle) are observed.
|
||||
**Trap (why A fails in a worktree):** module externals resolve from the *bundle's* directory. A bundle extracted to `/tmp` cannot resolve `@oh-my-pi/pi-natives` and dies with a version-pinned `Cannot find module .../pi-natives@<ver>/.../loader-state.js` — nothing to do with the fix under test. Putting the old bundle inside the repo (next to `node_modules`) fixes resolution, but in a linked-worktree where `node_modules` is a symlink to another checkout whose installed version differs from the version baked into `HEAD:dist`, it still fails. Then use route B.
|
||||
|
||||
Prefer this over a synthetic leak: if the check already caught a real leftover/failure once, that event *is* the sensitivity proof — say so in the commit message instead of faking one.
|
||||
## Route B — swap the source, rebuild, restore (private worktree only)
|
||||
|
||||
`setup()` rebuilds `dist` from `src/`, so reverting one source file reproduces the pre-fix bundle exactly:
|
||||
|
||||
```bash
|
||||
cp src/<file>.ts /tmp/new-<file>.ts
|
||||
git show HEAD:src/<file>.ts > src/<file>.ts
|
||||
bun run build >/dev/null
|
||||
bats tests/behavior.bats --filter '<new test names>' # expect every new test NOT ok
|
||||
cp /tmp/new-<file>.ts src/<file>.ts && bun run build >/dev/null && echo RESTORED
|
||||
```
|
||||
|
||||
Always restore in the same command line that ran the red check, so an interrupted session cannot leave pre-fix source behind. `bats --filter` takes a regex over the `@test` titles; use it instead of the whole suite (the full run is ~5 min).
|
||||
|
||||
Read the failure lines, not just the count: a red test that fails because the driver crashed (`PLAYS 0`, no `CALL` lines) proves nothing about the fix.
|
||||
|
||||
## Gate afterwards
|
||||
|
||||
`bun run build` and commit `dist/` in the same commit as `src/` — `tests/packaging.bats` byte-compares the **committed** dist (`git diff --name-only -- dist` must be empty), so an uncommitted rebuild shows up as a red packaging test, not as a missing fix.
|
||||
@@ -0,0 +1,67 @@
|
||||
---
|
||||
name: media-library-dup-similarity-census
|
||||
description: "Census a large media library for byte duplicates and same-film/different-encode copies with codecs, over SSH, without disturbing a live inference service."
|
||||
---
|
||||
|
||||
# Media library duplicate + similarity census
|
||||
|
||||
Read-only pipeline for `find duplicates and similars in /mnt/... with sizes and codecs`
|
||||
on a multi-TiB spindle library. Everything `nice -n 19`, one owner per stage, results as TSV.
|
||||
|
||||
## Stages (never skip the cheap ones)
|
||||
|
||||
1. **Enumerate** → TSV `size \t ext \t path`. Skip `.AGENTTRASH`, `@eaDir`, `.thumbnails`.
|
||||
Report totals per extension + check `find -type f -links +2` (hardlinks are a separate
|
||||
duplicate class).
|
||||
2. **Size groups** → candidates for byte-identity. Seconds.
|
||||
3. **Partial hash**: blake2b of first 4 MiB + last 4 MiB. Kills size collisions cheaply.
|
||||
4. **Full blake2b** on the survivors only. Confirm, don't assume — report how many
|
||||
partial-groups split on the full hash (0 = the head+tail screen was exact).
|
||||
5. **ffprobe census** (codec, WxH, pix_fmt, duration, audio codec list) for every video
|
||||
container. Parallel 6 workers; expect ~11-21 files/s on a spindle array with contention.
|
||||
6. **Title/year/duration clustering** for "same film, different encode".
|
||||
|
||||
## Similarity engine: the rules that survive contact with real data
|
||||
|
||||
- **Runtime vetoes names.** Films titled `The Menu`, `Trailer`, `Trailer Park Boys`,
|
||||
`ET — The Extra-Terrestrial` run 100+ min → not bonus material. Any file over ~45 min
|
||||
is a film whatever it is called or wherever it sits.
|
||||
- **Long-shaped bonus words** (commentary, documentary, interview) believed only under
|
||||
~25 min; a feature-length commentary cut is a film.
|
||||
- **Bonus folder evidence is path-wide below the film root** — `Film/Extras/x.mp4`,
|
||||
`Film/Featurettes/Deleted Scenes/x.mkv`, `Film/04b. Film Extras/Logo.mp4` are all bonus.
|
||||
The ROOT folder is exempt: it names the film. Match whole words only, so
|
||||
`Extraordinary` / `The Extras` are not evidence.
|
||||
- **A file named for the folder's film name is that film**, not its bonus — otherwise a
|
||||
whole movie filed in another film's `Extras/` disappears from the film census.
|
||||
- **Cluster by duration inside a title bucket**, not by title alone: 108 min vs 121 min
|
||||
is a different cut, and franchise prefixes ("jump street") collide without a duration check.
|
||||
- **Exclude release-group watermark clips** (`ETRG.mp4`, `RARBG.com.mp4`, `TGx.mp4`,
|
||||
<20 MiB): they pair with themselves across unrelated films.
|
||||
- **Exclude samples/trailers/menus/galleries** — generic names create cross-film pairs.
|
||||
- BDMV trees: one pseudo-entry per tree, described by its longest stream (`BDMV/index.bdmv`
|
||||
ends in `.bdmv`, so a `VIDEO_SUFFIX` check catches it); size = whole tree.
|
||||
- AppleDouble `._*` files are not media.
|
||||
|
||||
## Operational traps
|
||||
|
||||
- `python3 enum.py` shadows the stdlib `enum` module — never name a script a stdlib name.
|
||||
- Detached remote jobs: `setsid nice -n 19 ... > log 2>&1 < /dev/null &`. Killing the SSH
|
||||
client does NOT kill the remote child — two hashers raced on one output file and truncated
|
||||
it to 0 bytes. `pgrep -af` on the remote before relaunching, and kill the orphan by pid.
|
||||
- A background bash job with a default deadline can die mid-read; for long reads use
|
||||
`timeout: 0` or run detached and poll the log.
|
||||
- Run probes and hashers **sequentially**, not in parallel — they contend on one array and
|
||||
each starves the other.
|
||||
- Ship scripts with `cat local | ssh host 'cat > /tmp/x.py'`; a filename collision with the
|
||||
remote's cwd is invisible until it crashes.
|
||||
- `/tmp/mc/` on Dankserver can vanish mid-session (systemd tmpfiles); pull TSVs local early.
|
||||
- Quote remote awk/heredocs carefully — nested quoting fails before it ever runs.
|
||||
|
||||
## Reporting
|
||||
|
||||
Give: per-extension library totals; exact-dup table (size, codec, path); top same-film groups
|
||||
by reclaim bytes with per-file size + codec + runtime; a reclaim table that states whether the
|
||||
classes overlap; and the noise classes you excluded (extras, watermarks, `.!qB` partials,
|
||||
TV episodes filed as movies). Audit your own duplicate tier: flag groups whose year/base-name/
|
||||
runtime disagree and read every flagged group by hand before reporting the total.
|
||||
@@ -1,26 +1,28 @@
|
||||
---
|
||||
name: monolith-split-under-dist-pin
|
||||
description: "Use when cutting a monolithic source file into modules behind a black-box test suite whose committed build artifact (dist) must stay byte-identical to src; one verified commit per cut, red-proof against the previous bundle. Not for feature work or doc-only edits."
|
||||
description: "\"Use when cutting a monolithic source file into modules behind a committed-bundle pin (dist must stay byte-identical to src); one verified commit per cut, red-proof against the previous bundle. Not for feature work or doc-only edits.\""
|
||||
---
|
||||
|
||||
# Monolith split under a committed-bundle pin
|
||||
|
||||
Procedure that landed a 1354-line `index.ts` as 9 modules + 301 lines of wiring across 9 commits with zero red commits. Assumes: black-box test suite (tests drive the built bundle, not internals) and a pin test asserting the committed `dist/` equals a fresh build of `src/`.
|
||||
Procedure that landed a 1354-line `index.ts` as 9 modules + 301 lines of wiring across 9 commits with zero red commits; the same loop then split a 487-line `utterance.ts` in two cuts. Assumes: black-box test suite (tests drive the built bundle, not internals) and a pin test asserting the committed `dist/` equals a fresh build of `src/`.
|
||||
|
||||
## Per-cut loop (one commit per cut, always in this order)
|
||||
|
||||
1. `bun run build` (or project equivalent) **before** staging — commit `dist/` in the same commit as the moved `src/`, otherwise the pin test fails ("committed dist is byte-identical to a fresh build") and you must amend. Amending is fine only while the branch is unpushed.
|
||||
1. `bun run build` (or project equivalent) **before** staging — commit `dist/` in the same commit as the moved `src/`, otherwise the pin test fails ("committed dist is byte-identical to a fresh build") and you must amend. Amending is fine only while the branch is unpushed. Expect this to be the ONLY red test before the commit lands: it is `git diff --name-only -- dist` being non-empty, not a broken build.
|
||||
2. `tsc --noEmit`, then run the full suite async (`bats tests/ > /tmp/x.log 2>&1; grep -E '^1\.\.'`).
|
||||
3. **Never write to `src/` while the suite runs.** `setup()` rebuilds the bundle from `src/`, so a concurrent edit attributes someone else's half-written state to your run. Read-only prep (planning the next cut's exact line ranges) is the correct thing to do while waiting.
|
||||
4. Gate green → next cut. Gate red → fix, rebuild, amend, re-gate.
|
||||
3. **Launch the async suite with no deadline.** The default background deadline (~300 s) kills a minutes-long suite mid-run; the log then ends on a plausible `ok` line and reads like a partial pass. Likewise never edit `src/` while it runs.
|
||||
4. **Never write to `src/` while the suite runs.** `setup()` rebuilds the bundle from `src/`, so a concurrent edit attributes someone else's half-written state to your run. Read-only prep (planning the next cut's exact line ranges) — or writing a NEW module file that nothing imports yet — is the correct thing to do while waiting: an unimported file cannot change the bundle.
|
||||
5. Gate green → next cut. Gate red → fix, rebuild, amend, re-gate.
|
||||
|
||||
## Cutting mechanics
|
||||
|
||||
- Cut leaf/pure modules first (grammar, transcript rules, URL resolution, pure segmenter), then stateful ones (config, IO, pipeline, UI). Never let a module import `index` — if the new module needs something from `index`, that thing is a leaf and must move too (this is what forced `segment.ts` out early).
|
||||
- Extract in **verbatim** bodies: read the exact ranges, write them into the new file, then delete the same ranges. Only module docs, export keywords, and injected dependencies change. Keeps each commit reviewable as a move.
|
||||
- Edit tools that require "displayed lines" from the latest read snapshot: re-read the exact ranges under the current tag, then issue one edit call with all ops listed **bottom-up** (original line numbers throughout). Adjacent ranges merge into one `CUT a=b`; leaving a doubled blank line is the usual scar — one extra `CUT` next turn.
|
||||
- Mutable config must reach a long-lived object as **getters**, not a construction-time snapshot: menu handlers mutate `cfg` after construction, and a snapshot freezes the settings the UI exists to change.
|
||||
- A class member's JSDoc keeps the class's own indent (` /**` then ` * ` at member-level+1): a `PUT` body that starts at column 0 silently de-indents the whole block and the next `PUT` on those lines gets rejected for "restating the line below".
|
||||
- Mutable config must reach a long-lived object as **getters**, not a construction-time snapshot: menu handlers mutate `cfg` after construction, and a snapshot freezes the settings the UI exists to change. Reading the getters once per unit of work (instead of per inner loop step) is a behavior improvement worth noting in the commit message.
|
||||
- After moving code, grep the old file for the moved names: stale call sites compile under bundlers that skip typechecking (`stop()` vs `speaker.stop()` was caught only by `tsc`, not by `bun build`).
|
||||
- A worktree may have no `node_modules` at all: the build fails on the natives loader with a version that doesn't match the primary checkout. `ln -sfn <primary>/node_modules <worktree>/node_modules` when `.gitignore` already anticipates the symlink (`git status` stays clean).
|
||||
- Don't invent a third module to hold state the class already owns. If `queue.ts` would own no fields and need the pipeline injected as a callback, the queue stays in the class; say so in the report rather than shipping callback soup.
|
||||
|
||||
## Proving a new assertion is red-capable (before trusting it green)
|
||||
|
||||
@@ -34,15 +36,20 @@ DV_EXT=/tmp/old-dist.js <same env as the test> bun "$W/driver.ts" <scenario> #
|
||||
DV_EXT="$PWD/dist/index.js" <same env> bun "$W/driver.ts" <scenario> # expect GREEN
|
||||
```
|
||||
|
||||
A pre-fix bundle copied to `/tmp` can fail to load its natives (`Cannot find module …/pi-natives@X/loader-state.js`) because module resolution follows the *bundle's* directory, and it reports every probe as zero — not red, just dead. The robust alternative in a private worktree: `git show HEAD:src/<file>.ts > src/<file>.ts`, rebuild, run the filtered bats tests (they rebuild from `src` themselves), then copy your version back and rebuild. Keep the copy in `/tmp` before overwriting.
|
||||
|
||||
Copy the project's real player/server stub verbatim — a simplified stub (e.g. one that skips writing the log file the driver counts) prints a false `PLAYS 0` and looks like a failing fix. Pin the scratch dir to a fixed path; `ls -dt /tmp/tmp.*` picks up another session's directory.
|
||||
|
||||
## Black-box test hygiene learned the hard way
|
||||
|
||||
- A test that counts artifacts in the **shared** temp directory observes every other process on the box (live agent sessions synthesize into the same `dankvoice-*` dirs). Give the run a private `TMPDIR="$WORK/scratch-tmp"` and assert there; the assertion's sensitivity is proven by the fact it caught a real leftover while scoped globally.
|
||||
- Let preseeds reference the ephemeral fixture port with a placeholder (`__PORT__`) substituted by the driver, so a test can preseed a URL pointing at its own fixture server.
|
||||
- Count what the user **heard**, not what was requested. Synthesis counts double-count legitimately whenever a turn is cut off and the remainder is re-synthesized, so an assertion like "the intro appears once" fails on correct code. Stamp each clip with the arrival index of the request that made it (the existing tag byte), print the played tags from the player stub, and emit one `SPOKEN <text>` line per played tag by joining tags back to request payloads — then count hearings.
|
||||
- Pinning an assertion to a timing window: drive both knobs from env (when the cut lands, how long a clip plays) and choose a window wider than the poll interval, so the test can't go red from a race it isn't about.
|
||||
- Refactor-only commits get **no new test**: re-asserting unchanged labels/order after a commit whose point is that they are unchanged is a tautology; the existing suite is the gate. New tests belong to behavior changes, red-first.
|
||||
- Tests must observe **output**, never internals: assert `PLAYS`/`CALLS`/`CALL <url>`/`NOTIFY <text>`, not that a variable was set.
|
||||
- When a behavior fix turns a cluster of pre-existing tests red, check whether the cluster was pinning the *delivery path* rather than the feature: scenarios that drove a "only-unsolicited-narration" knob through a typed command were accidentally pinning the retired behavior. Move the scenario to the real path (deliver through the harness event) and leave the assertions word-for-word; strengthen any test whose fixture never answered (its assertion was absence, not evidence). Report the move explicitly — it looks like nine changed tests.
|
||||
|
||||
## Docs after a split (same branch, own commit)
|
||||
|
||||
Move each explanation to the file that now owns it (knob list → the module that reads env; state-machine prose → the module that implements it) and leave the entry point a one-line-per-module map. Then grep for pointers into the old file — a README saying "see header of `src/index.ts`" becomes a lie the moment the knobs move.
|
||||
Move each explanation to the file that now owns it (knob list → the module that reads env; state-machine prose → the module that implements it) and leave the entry point a one-line-per-module map. Then grep for pointers into the old file — a README saying "see header of `src/index.ts`" becomes a lie the moment the knobs move. Also re-check the laws table's test pointers: a law that claimed "pinned by scenarios asserting X" while those scenarios never armed the feature is a false citation, found by running the suite once.
|
||||
@@ -0,0 +1,16 @@
|
||||
---
|
||||
name: read-managed-skill-before-update
|
||||
description: "Before manage_skill update (which overwrites the whole body), read the existing SKILL.md via skill:// and merge; recover lost bodies from the git repo tracking managed-skills."
|
||||
---
|
||||
|
||||
# Never overwrite a managed skill blind
|
||||
|
||||
`manage_skill action:"update"` replaces the **entire** `SKILL.md` body. Issuing it without the current text in hand silently destroys whatever the earlier session learned.
|
||||
|
||||
## Rule
|
||||
|
||||
1. Read first: `read skill://<name>` (or the file at `~/.omp/agent/managed-skills/<name>/SKILL.md`) in the SAME turn as the update call, then re-issue the body as existing-content + additions.
|
||||
2. Keep additions append-shaped: a new section at the end, not a rewrite of prose that already worked. Near-duplicate sections are cheaper to clean up than lost knowledge.
|
||||
3. If a body was already clobbered, recover before rewriting: `~/.omp/agent` (or the agent home) is commonly git-tracked — `git -C ~/.omp/agent log --oneline -- managed-skills/<name>/SKILL.md` then `git -C ~/.omp/agent show HEAD:managed-skills/<name>/SKILL.md`. If untracked, the version is gone; say so instead of pretending the rewrite preserved it.
|
||||
|
||||
Detection detail: a clobber usually shows up as a skill that still reads coherently but has lost the concrete env vars, paths, or trap list that made it actionable — i.e. the version you rewrote from memory of a sibling skill.
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1792536631688
|
||||
"lastConsolidatedAt": 1793403795365
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1793396927634
|
||||
"lastConsolidatedAt": 1793403526285
|
||||
}
|
||||
+1
-1
@@ -10,4 +10,4 @@ Use the 1M corpus threshold for payload serving decisions.
|
||||
|
||||
## Merging Strategy
|
||||
|
||||
Merge anything that hasn't been merged yet, and let us try that faster shape.
|
||||
Merge anything that hasn't been merged yet, and let us try that faster shape.
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
## Behavioral Updates
|
||||
|
||||
Merge fixes immediately.
|
||||
Merge fixes immediately.
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1793402274788,
|
||||
"lastConsolidatedAt": 1793405934807,
|
||||
"lastResult": {
|
||||
"at": 1793401924358,
|
||||
"at": 1793403514528,
|
||||
"sessions": 1,
|
||||
"deltas": 1,
|
||||
"model": "frank-zealot"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1793402231969,
|
||||
"lastConsolidatedAt": 1793402531972,
|
||||
"lastResult": {
|
||||
"at": 1793400701944,
|
||||
"sessions": 1,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1793402365714,
|
||||
"lastConsolidatedAt": 1793402665716,
|
||||
"lastResult": {
|
||||
"at": 1793401119393,
|
||||
"sessions": 1,
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1,8 @@
|
||||
# Product Decisions
|
||||
|
||||
## File Organization
|
||||
- Reorganize files to fit Jellyfin standard, clean filenames
|
||||
|
||||
## Constraints
|
||||
- Do not delete any files
|
||||
- Do not overwrite any files
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1793405994161,
|
||||
"lastResult": {
|
||||
"at": 1793405994161,
|
||||
"sessions": 1,
|
||||
"deltas": 2,
|
||||
"model": "frank-zealot"
|
||||
}
|
||||
}
|
||||
Whitespace-only changes.
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"v": 1,
|
||||
"lastConsolidatedAt": 1793406016281
|
||||
}
|
||||
Reference in new issue
Block a user