9d8104f7a5a3eb0d2b45506b8c0471bf6d6be278
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a8f35e465e |
refactor(plugin+tests): the last two parallel-family gaps — pageable list tools, one config preamble (#2278)
CI & Build / Python lint (push) Successful in 4s
CI & Build / integration (push) Successful in 1m34s
CI & Build / Python tests (push) Successful in 2m21s
CI & Build / Plugin hooks (push) Successful in 27s
CI & Build / TypeScript typecheck (push) Successful in 36s
CI & Build / Build & push image (push) Successful in 25s
DRY pass 3's remainder. Both halves start from enumeration, because the task's
candidate list was hypotheses and the process requires counting before
proposing — and counting changed the answer twice.
## The list_* family: a limit with no offset
Enumerated all 19 `list_*` MCP tools first. They are genuinely heterogeneous —
8 take `project_id`, 6 take `limit`, six take no arguments at all — so a
common-parameter guard would invent a convention the API does not have, which
is the over-DRY trap (§5). One contract IS real: a `limit` without an `offset`
is a truncation with no continuation. The caller is told there are 250 results,
handed 50, and given no way to ask for the rest.
Two tools had it, and both were capped over a service that already accepted an
offset: `snippets_svc.list_snippets(offset=0)` was simply not exposed, and
`list_processes` passed a hardcoded `offset=0` into `query_knowledge`. The
capability existed one layer down in both; only the door was missing — the
missing-sibling shape exactly. Both now expose it.
`tests/test_mcp_list_family.py` guards it, with `list_tags` exempted for a
stated reason (a ranked top-N over a bounded vocabulary has no "rest" to page
into). Candidates derived, decision explicit, same design as test_mcp_auth —
plus the reverse checks: a stale exemption, and an offset with no limit, which
would page through an unbounded result set. Verified non-vacuous by running the
sweep against the pre-fix tree, where it fails naming both tools.
## The verb pairs: no finding, which is the finding
`preview`/`apply` and `dry_run`/`commit` do not exist anywhere in the 102
tools — those were guesses about a shape Scribe never adopted. `count_*` does
not exist either. Of the create/delete stems only `project_rule` lacks a
`delete_X`, and deliberately: a project rule IS a rule, `delete_rule` removes
it, and the docstring says so. `force` sits on 6 of 7 duplicate-gated creates;
the exception is `create_system`, whose gate is an exact normalized-NAME match
rather than a semantic near-match — forcing it would split one area's records
across two piles, which its own message explains. No guard added: it would
need a seven-entry exemption list to defend against a hypothetical. Recorded
on the leave-alone list instead, which the process asks for by name.
## The hook config preamble
Not 3 of 6 hooks as recorded — all FIVE carried their own copy, and of four
lines rather than two. The extra two are a guard treating an unexpanded
`${...}` placeholder as unset, so it is never sent as a garbage Bearer token:
precisely the correctness detail a sixth hook would omit with nothing failing
loudly. Now `scribe_config` in scribe_defs.sh, which also declares the two
names it owns. It sets globals rather than echoing, so a token never passes
through a subshell's output where xtrace or a log could catch it, and returns
a status so a caller can bail (`|| exit 0`) or continue degraded — the
session-context hook still owes its static floor when Scribe is unconfigured.
`check_plugin.py` now runs shellcheck with `-x`. Without it the shared helpers
were invisible: every variable they set read as unassigned and every bug inside
them went unlinted at the call site, which is the opposite of what sharing them
was for. All twelve fail-open scenarios still pass, and all five hooks were
probed live against the instance — prior_art and after_write both still name
canon, autoinject returns context, session_context serves 11k chars of rules,
sync_processes stays silent. Plugin 0.1.46 (#2209).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0ab94b2a00 |
fix(plugin): write-path hooks say when Scribe did not answer — once per outage, shared marker, record nudge withheld on an unanswered call; check_plugin allows exactly that line when unreachable; plugin 0.1.43 (#2932)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 13s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 40s
CI & Build / Python tests (push) Failing after 49s
CI & Build / Build & push image (push) Skipped
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5925335ca0 |
feat(plugin): after-write hook — PostToolUse on Bash diffs the working tree and runs the prior-art + ledger arms on what was just written; shared scribe_defs.sh; plugin 0.1.39 (#2901, milestone 299 step 3)
CI & Build / Python lint (push) Successful in 4s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 25s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Failing after 40s
CI & Build / Build & push image (push) Skipped
Edits made through sed/heredocs/scripts never reached the PreToolUse Write|Edit hook, so a whole class of writes got no prior-art hint, no ledger feed and no duplicate-family warning. scribe_after_write.sh asks git what changed since it last looked (per-session path+blob snapshot; first call = files touched in the last minute), extracts the definitions in the added lines and calls /api/plugin/prior-art with the same three dedup channels the pre hook keeps. Never blocks; silent on any failure. The extractor, the prose/data skip list and the local by-name arm move to scribe_defs.sh, sourced by both hooks. Version bump covers the step-2 hook change too (run 4239 failed only on the bump check). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
841506b10c |
feat(plugin): every session says which plugin version is executing
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 23s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 59s
CI & Build / Build & push image (push) Successful in 19s
Twice a shipped fix failed to reach a live install, and both times the only detector was the operator saying "I don't think it updated" (#2198, #2209). The reason it is hard to see: an install has two halves and only one self-updates. The marketplace clone pulls on its own; the CACHE is what executes and refreshes only when the manifest version changes. So inspecting the clone shows the fix present while the broken copy keeps running — the obvious debugging move actively misleads. The SessionStart context now names the version it is running. That makes "what is actually executing?" answerable from the transcript rather than by archaeology in the cache directory. DELIBERATELY SMALLER THAN THE ISSUE PROPOSED. #2220 recommended reporting the version to the server, storing last-seen per user, and surfacing it in Settings. That is three surfaces and a migration to answer a question the session can answer about itself. Per the operator's framing on #2338 — "I'm afraid of building another integration between two more surfaces, you being able to notice is enough" — the visibility is the deliverable, not the plumbing. It also lands the issue's own caveat, which the server-side design could not: the state most needing diagnosis is the one where credentials never arrive, and there the dynamic tier does not run at all. This marker is keyless and networkless, so it still appears — verified against both the unconfigured and unreachable-instance paths. CI pins it, asserting WITHOUT credentials for the same reason. Manifest 0.1.22 -> 0.1.23, because a shipped hook changed and an unbumped manifest is precisely the failure this commit is about. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
6d01788326 |
test(plugin): pin both halves of the local prior-art arm
CI & Build / Plugin hooks (push) Successful in 9s
CI & Build / integration (push) Successful in 28s
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 34s
CI & Build / Python tests (push) Successful in 54s
CI & Build / Build & push image (push) Successful in 21s
CI caught the previous commit: the fail-open contract asserts a hook stays SILENT with no working instance, and the new local arm deliberately speaks there. The invariant is right and the hook is right — the smoke event was wrong. It used `def f`, which this repo really does define, so the hook found something and "silent" was asserting the wrong thing. Fixed by asserting the two properties separately: - SILENT for a symbol that genuinely does not exist. - SPEAKING, with NO credentials, for one that does. That is the point of the arm — the other arms ask Scribe what was RECORDED; this one asks the repo what EXISTS, which needs no instance. Were it to start depending on configuration it would stop covering the case it was built for, and only this assertion would notice. Second trap, hit while fixing the first: spelling the absent symbol out in full wrote `def <name>(` into check_plugin.py, so the smoke event DEFINED the very symbol it claimed was missing, and the hook found it again. The name is now assembled from fragments so the contiguous string never appears in the source. scribe_prior_art.sh joins scribe_session_context.sh as a hook that legitimately produces output without credentials — for the same reason, that it carries something needing neither network nor config. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
dd878bc498 |
ci(plugin): add shellcheck + jq, and the fail-open smoke test they unlock
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Failing after 7s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / integration (push) Successful in 39s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 21s
Per-job installs, not an image change. CI-runner's docs/process.md decision checkpoint is explicit: "If only one project needs the dep, prefer that project installing it per-job in their workflow — at least until a second consumer arrives." Scribe is the only consumer, and shellcheck is not a natural extension of a Python image's purpose. Promotion into ci-python is filed as an issue on CI-runner rather than assumed here — same doc, step 1: the maintainer's call goes in the issue, then the PR. This also corrects something I got wrong earlier in this work: I cited rule #5 as blocking a per-job install. Rule #5 is about language TOOLCHAINS via setup-* actions, not small lint utilities, and CI-runner's own process doc positively recommends per-job installs in exactly this case. jq is load-bearing rather than convenient. Every hook opens with `command -v jq || exit 0`, so without it a "runs and stays silent" smoke test passes while exercising nothing — a green tick proving less than no test at all. That is why the smoke test didn't ship with the first cut. The smoke test pins the fail-open contract: each hook, with no credentials and then against a refused connection, must exit 0. Three must also stay silent; scribe_session_context.sh must NOT, because its static behavioural floor is meant to survive having no credentials and no network — asserting silence there would encode the opposite of the design. Verified it can actually fail, rather than assuming: injected a non-zero exit and separately a stray stdout write, and confirmed each is caught. shellcheck and jq are both optional at runtime — missing either SKIPs its check loudly rather than passing. A check that quietly no-ops is the exact failure mode this file exists to prevent. ci-requirements.md updated: jq + shellcheck recorded under per-job installs (the input CI-runner's maintainer uses for the next promotion decision), plus two stale entries corrected — the sheet claimed four jobs when there are six, and listed `uv` as a per-job install when it has been in the image since the ci-python Dockerfile started pip-installing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
1feef179d2 |
ci(plugin): drop with: from the plugin job's checkout
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 3s
CI & Build / integration (push) Successful in 30s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 52s
CI & Build / Build & push image (push) Successful in 19s
Run 3027: the Plugin hooks job failed at checkout, before the script ran. Adding a `with: fetch-depth: 0` block made actions/checkout@v6 fail to extract on the act_runner — Cannot find module '/var/run/act/actions/<sha>/dist/index.js' — while every bare `uses: actions/checkout@v6` in the same run succeeded. The runner's action-cache handling is the difference, not git. No depth was needed in the first place. The version check compares two TREES, and a tree diff needs both trees, not a common ancestor. Verified against a real depth-1 clone: after `git fetch --depth=1 origin main:refs/remotes/origin/main`, both `git diff origin/main -- plugin` and `git show origin/main:plugin/.claude-plugin/plugin.json` work. So the explicit fetch already in the step is sufficient, and cheaper than the full history the `with:` block was asking for. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UaYUaouG9jjhATyuxCKrQs |
||
|
|
51e5c22818 |
ci(plugin): gate plugin/** — the path that shipped two live defects
CI & Build / Python lint (push) Successful in 8s
CI & Build / Python tests (push) Successful in 55s
CI & Build / Plugin hooks (push) Failing after 2s
CI & Build / integration (push) Successful in 31s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Build & push image (push) Successful in 20s
`plugin/` is not built into the image; installs fetch it from this repo via .claude-plugin/marketplace.json, so a push IS the release. It was absent from the workflow's `paths:` filter entirely, meaning plugin changes ran no CI at all. Two separate defects reached a live install through that gap: #2198 — all four hook scripts inert (lowercase userConfig env vars, line-oriented `jq -rR`, line-oriented `cut -c`) #2209 — the fix for #2198 couldn't reach an install because the manifest version wasn't bumped, so the installer never refreshed its cache Adds `plugin/**` + `.claude-plugin/**` to `paths:` and a `plugin` job running scripts/check_plugin.py: 1. `bash -n` on every hook. 2. The three known-bad patterns from #2198. Verified by replay against c569cdd^ — all three are caught. Narrow by design; see below. 3. Shipped plugin content differs from origin/main => the manifest version must differ too. Stated against the base branch, not per-commit, so a batch needs one bump rather than one per commit. Replayed against |