Addresses both review findings on #993.
1. skill_plugin_emitter.py's module docstring still described the behaviour this
PR removed. Its tree diagram called plugin.json the "manifest with
./skills/<slug> + attribution", and the rights-gate paragraph said
cleared_for_distribution is recorded "in the manifest" -- both now the
sidecar. A docstring asserting the opposite of its own file's point is worse
than no docstring, and it is what `--help` readers see.
Also corrects a third instance the review did not name but which shares the
root cause: the tree diagram never listed authoring-notes.json at all, even
though this PR's fix is what creates it. Added, with the issue #954 reason
stated inline so the next reader knows why provenance cannot live in the
manifest.
2. CLAUDE.md said the resident core is 2,066 tokens while, twelve lines later,
the same file said 2,101. 2,066 predates the worked invocation block added
for write-a-skill checklist item 5; token_budget_estimator.py reports 2,101
on the current tree, matching CHANGELOG.md and the PR body. CLAUDE.md was
the only stale copy and it contradicted itself.
Docstring-only and prose-only; no behaviour change. Verified the emitter still
emits both plugin.json and authoring-notes.json via --sample after the edit.
Gates: compileall, check_plugin_json --all, check_paths, check_frontmatter,
check_dual_publish, check_model_freshness, smoke_scripts (696/696),
derive_counters --check, book_skill_validator --strict.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UySnyf5upm4y8xhYA3w6yw
dev moved: PR #994 landed engineering/deep-learning-book, which collides with
this branch on every headline-counter and registry surface.
Conflicts resolved in four files, keeping both sides' content:
- .claude-plugin/marketplace.json -- both plugin entries kept; the registry now
carries spinning-up-deep-rl and deep-learning-book. 99 plugins.
- CHANGELOG.md -- both Unreleased sections kept.
- CLAUDE.md, README.md -- dev's prose taken as the newer baseline, then this
branch's engineering-row entry restored and every counter re-derived rather
than hand-picked from either side.
Counters re-derived from the merged tree with derive_counters.py, which is the
ground truth, and trued up across all five surfaces: 388 skills, 99 plugins,
727 tools, 842 references, 118 agents, 150 commands.
Both changelog/CLAUDE.md delta lines are restated: each side was written against
its own base and both claimed 386 -> 387, which is no longer true of either now
that they land together. This branch's entry is now stated as the delta on top of
deep-learning-book.
Gates re-run on the resolved merge: no conflict markers left in the tree,
compileall, check_plugin_json --all, check_skill_names, check_paths,
check_frontmatter, check_dual_publish, check_model_freshness, smoke_scripts
(696/696), derive_counters --check, book_skill_validator --strict, and a
JSON/YAML parse of every file touched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UySnyf5upm4y8xhYA3w6yw
Ninth review on PR #994 found that --underparameterized-max and
--overparameterized-min, added two commits earlier, were never checked against each
other. Reproduced, and the consequence is sharper than a silent misclassification:
with --underparameterized-max 20 --overparameterized-min 5 and a ratio of 10, the
tool reported a model ten times overparameterized as "underparameterized" and exited
0. That verdict ranks "shrink the model" FIRST rather than last, inverting the exact
double-descent correction this tool exists to apply.
Added an argparse guard rejecting under-max >= over-min with a message naming both
values (exit 2, the documented usage-error code). Equal bands are rejected too,
since they leave the near-interpolation regime unreachable.
Verified: inverted and equal bands both exit 2; a valid override still moves the
regime (--overparameterized-min 500 gives near-interpolation); defaults unchanged at
overparameterized / OVERFIT / 240.0 with smaller-model last; the other exit codes
still 1 for an action, 0 for balanced, 4 for a leaky split.
Worth noting for the two flags' own history: they were added to close a consistency
nit, and introduced this defect in doing so. A new option is new surface, and its
interaction with existing options is part of it.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Eighth review on PR #994 found that _matches()'s (?:s|es)? suffix reintroduced a
smaller version of the substring bug it was written to fix: "rag" + "es" matches the
standalone word "rages", so --goal "why overfitting still rages in large models"
exited 3, refused as out-of-scope retrieval-augmented-generation work.
Reproduced before fixing, then checked whether the -es branch earns its keep at all.
It does not: every keyword in the table ending in s, x, z, ch or sh is already plural
or non-count (basics, features, foundations, prerequisites, statistics, speech,
mlops, fairness, mathematics, time series), so no token needs -es appended. Narrowed
the auto-plural to plain -s and documented that irregular forms belong in
SURFACE_FORMS, spelled out, the way fine-tuning, prompting and agentic already are.
Verified: "rages" no longer matches while standalone "rag" still does; transformers,
embeddings, autoencoders and agentic still match; the goal above is now correctly
unroutable (exit 4) rather than confidently misrouted. RLHF, LoRA fine-tuning, RAG
pipelines and prompt/agent goals still exit 3; sequence, vision, generative and
practitioner routing unchanged.
That makes two rounds of false out-of-scope refusals from the same root cause —
approximate string matching standing in for a word list. The refusal path is the
part of this tool users are least able to second-guess, so it is the wrong place to
approximate.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Seventh review on PR #994 noted that capacity_planner.py exposes --overfit-rel-gap
but hardcodes the two params-per-example regime bands, unlike its sibling scripts
where every threshold is overridable. The reviewer called it fine as shipped; the
inconsistency is small enough to close rather than carry.
The band matters more than a typical default: it decides whether "shrink the model"
is ranked last, which is the double-descent correction this tool exists to apply.
The interpolation point depends on task, architecture and label noise, so a user
whose model sits elsewhere had no way to say so.
Adds --underparameterized-max and --overparameterized-min, threaded through plan()
and classify_regime() as keyword arguments defaulting to the existing constants, and
reports all three thresholds in --output json the way training_diagnostics.py already
does. The help text repeats that these are heuristic bands, not a threshold theorem.
Defaults preserve behaviour exactly: the --sample fixture still reports
overparameterized / OVERFIT / 240.0 params-per-example with smaller-model ranked
last, identical to before the change. An override demonstrably moves the regime
(--overparameterized-min 500 reclassifies the same model as near-interpolation).
Exit codes unchanged: 1 for an action, 0 for balanced, 4 for a leaky split.
Not fixed here: CHANGELOG.md's duplicate [Unreleased] header, also noted in that
review. Verified pre-existing rather than introduced by this branch — the count is
2 at the merge-base with main and 2 at HEAD. Left for a separate changelog cleanup.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Sixth review on PR #994 noted that --include-intro appeared to have an observable
effect only on the complete lane. Checked it, and it was worse than reported: the
flag was inert on every lane, complete included.
Two reasons compounded. ch01 is not a prerequisite of any chapter, so it never
arrived through prerequisite closure; and the only lane that targets it, complete,
skipped the ch01 filter entirely. The filter therefore removed a chapter that was
never present, and the flag that controlled it could not change any output.
This also means my own verification of the parameter rename in c75500f was
inconclusive: it compared the complete lane with the flag against the vision lane
without it, and those differ for reasons unrelated to the flag. A test that cannot
fail proves nothing.
Inverted the logic so the flag adds ch01 rather than un-removing it, which gives it
a real effect on every lane while leaving complete unchanged (ch01 is already among
its targets). Help text now states what it does and that no lane pulls ch01 in on
its own.
Verified per lane: complete unchanged; vision, generative and foundations each gain
ch01 with the flag and are untouched without it; ch01 sorts first and never precedes
a chapter it would violate. Routing and both refusal paths are unchanged — sequence,
vision, generative and practitioner all route as before, out-of-scope still exits 3,
unroutable still exits 4.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Fifth review on PR #994 found the one numeric spec field I missed when adding typed
validation in the previous commit: embedding's seq_len still went through a bare
int(), so it bypassed the checks every sibling field had just been given.
Both failure modes reproduced before fixing:
seq_len "ten" -> uncaught ValueError, traceback, exit 1 (documented: exit 4)
seq_len 0 -> no error at all; reported a plausible "0x8" layer with zero
activation elements and exited 0, presenting a degenerate model
as a valid one
Routed through _positive_int with the existing default (the incoming sequence
length, or 1), so both now exit 4 naming the layer and field. Verified the default
fallback still resolves to the input shape (16x8) and an explicit valid seq_len is
still honoured (32x8).
Swept the file afterwards rather than fixing only the reported instance: all 13
numeric spec fields now go through _positive_int, and no raw int() on a spec field
remains. The reference specs are unchanged — convnet 545,098 parameters, transformer
asset 7,087,872.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Fourth review on PR #994 raised two findings against this plugin's scripts. Both
reproduced before fixing; the review's other findings are against marketing/linkedin,
which this branch carries from main but does not touch (see the PR comment).
1. reading_path_planner.py matched keywords by bare substring, so "rag" matched
inside "storage", "lora" inside "exploratory", "conv" inside "converge" and
"text" inside "context". Confirmed: --goal "train models for image storage and
retrieval" exited 3, confidently refused as out-of-scope RAG work, and
"an exploratory look at optimization" exited 3 citing LoRA. A tool whose stated
design is to refuse rather than guess was guessing, and doing it with certainty.
Matching is now word-boundary anchored with an optional plural, plus an explicit
surface-form table for the few tokens whose inflections a word-boundary match
would otherwise miss (fine-tuning, prompting, agentic). Verified: both goals above
now route correctly (exit 0 / the optimization lane), "converge" reaches the
optimization lane rather than vision, and the real refusals still refuse — RLHF,
LoRA fine-tuning, RAG pipelines and prompt/agent goals all still exit 3.
2. model_arithmetic.py documented exit 4 for a spec it cannot parse but only caught
SpecError and ShapeError, so malformed input escaped as a traceback with exit 1.
Confirmed across five cases: a non-dict top-level JSON, a non-dict layer entry,
stride 0, groups 0, and a non-numeric filters value. Numeric fields now go through
a checked accessor that rejects non-integer and non-positive values, the input
layer's shape is validated, and the top-level spec and every layer entry are
type-checked. All five now exit 4 with a message naming the layer and field.
Regression battery over eight goals routes exactly as before; the convnet sample
still reports 545,098 parameters and the transformer asset 7,087,872.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Third review on PR #994 found a real bug in the flagship example asset. Verified
before fixing: assets/example_layer_spec.json reported 1,207,962,624 parameters
for the feedforward up-projection instead of 2,362,368 — off by exactly 512x, the
sequence length — putting the block's total at ~1.21B instead of ~7.09M.
Root cause was in the tool, not only the asset. model_arithmetic.py's mha layer
emits (seq, d_model) but linear refused any 2-D input, so the only way to attach a
feedforward block was to flatten first. Flattening collapses all 512 positions into
one 393,216-element vector, which models a dense layer over the whole sequence — a
different layer, with seq_len times the parameters. A transformer FFN was therefore
not expressible at all, and the shipped example walked straight into it. Clean exit
is not correct numbers, which is why --sample exit-code testing never caught it.
Fixed the cause: linear on a 2-D (seq, features) input is now position-wise — one
weight matrix shared across positions, parameters independent of sequence length,
compute linear in it. Documented in the module docstring; the 3-D path still refuses
with an updated message pointing at flatten. Removed the flatten from the example
asset and recorded in its comment why it must not come back.
Verified: the corrected block reports 7,087,872 parameters, matching a hand-check of
2*(2*768) + (4*768^2+4*768) + (768*3072+3072) + (3072*768+768) exactly, and the size
of a BERT-base encoder layer. The convnet --sample is unchanged at 545,098, and
linear on 3-D input still exits 5.
Also adds the missing CHANGELOG.md [Unreleased] entry, which the same review noted:
CLAUDE.md, README.md and marketplace.json carried the new skill and its counter
deltas but CHANGELOG.md did not.
Gates green: compileall, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check,
check_skill_names, check_plugin_json, book_skill_validator, and --help +
--sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Second independent review on PR #994 observed that reading_path_planner.py's
score_lanes() broke equal-hit ties alphabetically by lane key, so SKILL.md's own
documented example "train a transformer" resolved to the practitioner lane rather
than sequence.
Reproduced: the goal hits practitioner on "train" and sequence on "transformer",
one keyword each, and alphabetical ordering picked practitioner.
Fixed the cause rather than the example. Ties now break on keyword specificity —
the lane whose longest matched keyword is longest wins — because an equal hit
count between a generic term and a discriminating one should not be settled by
luck. Lane key remains the final tie-break so ordering stays deterministic.
Regression battery over eight goals: "train a transformer" now routes to sequence;
vision, generative, foundations, practitioner, complete, representation and
sequence goals all route exactly as before. Refusal paths unchanged (out-of-scope
exit 3, unroutable exit 4, sample exit 0).
Gates green after the change: compileall, check_paths, check_frontmatter,
check_dual_publish, check_model_freshness, smoke_scripts (696 passed),
derive_counters --check, check_skill_names, check_plugin_json, book_skill_validator,
and --help + --sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Two small findings from the automated review on PR #994, both verified against the
source before fixing.
1. reading_path_planner.py: the plan() parameter was named include_optional while
the CLI flag and call site both use include_intro, and it only ever gates ch01.
Renamed the parameter and its use for consistency. Behavior unchanged, confirmed
both ways: --include-intro keeps ch01 first, the default drops it.
2. model_arithmetic.py: conv2d "same" padding computes ceil(H / stride), which is
the TensorFlow/Keras SAME convention, and the tool did not disclose which
framework it matches. Documented it in the module docstring — including that
PyTorch's padding='same' is symmetric-only and rejects a stride other than 1, so
a strided PyTorch layer will not match, with "valid" named as the exact-case
workaround — plus a pointer comment at the computation itself. No arithmetic
change; the sample stack reports identical parameters and FLOPs.
The reviewer's third point was that it could not execute the gates in a sandboxed
environment. No code change: those gates were run locally and are green.
Verified after the change: compileall, check_paths, check_frontmatter,
check_dual_publish, check_model_freshness, smoke_scripts (696 passed),
derive_counters --check, check_skill_names, check_plugin_json, the book-skill
validator, and --sample --output json on all four tools.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
CI gate G1 (scripts/check_paths.py) failed on the previous commit: the agent and
command files live at engineering/deep-learning-book/{agents,commands}/, so bare
references/*.md and assets/*.md tokens resolved against neither the plugin root,
the file's own directory, nor the repo root — the three bases the linter accepts.
The files they point at live under skills/deep-learning-book/.
Prefix the nine offending links with skills/deep-learning-book/ so they resolve
from the plugin root. Content unchanged otherwise; SKILL.md's own relative links
were already correct and were not touched.
Reproduced the failure locally (9 unresolvable references across 4 files), then
confirmed the same check clean, plus every other blocking gate: compileall,
check_plugin_json, check_skill_names, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (696 passed), derive_counters --check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Addresses the review observation on #993: the compiled skill's
authoring-notes.json carried only a `source` block (how it was built) even
though its content is derived from an external MIT-licensed work, where the
rest of the repo uses an `attribution` block for that. check_plugin_json.py's
NOTES_ALLOWED permits both keys, so the two coexist.
Adds `attribution` to engineering/spinning-up-deep-rl following the shape used
by book-to-skill and skillopt-sleep: derived_from, upstream_docs, upstream_path,
original_author, original_license, original_copyright, derivation_note.
The emitter is deliberately NOT changed to synthesise this. It knows only
`--source-note` free text and a rights basis -- not an upstream URL, author or
licence -- and a half-filled attribution block is worse than none. Instead
Step 11 of conversion_workflow.md now says attribution is added by hand
whenever `--rights` is anything but internal-docs, names the field shape, and
restates that the actual obligation is the LICENSE notice and README credit --
authoring-notes.json is metadata Claude Code never reads, and a sidecar JSON
file is not a licence notice.
Gates re-run clean: check_plugin_json --all, check_paths, check_frontmatter,
check_dual_publish, check_model_freshness, smoke_scripts (692/692),
derive_counters --check, book_skill_validator --strict.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UySnyf5upm4y8xhYA3w6yw
Requested as "convert deeplearningbook.org into a skill". Built as a companion
rather than a compilation, because the repo's own rights gate forbids the latter
for a public plugin.
Why not book-to-skill: its emitter refuses a shareable package without
public-domain / open-license / internal-docs / author-permission, none of which
applies to an MIT Press title whose site states its HTML-only format exists as a
friction against copying under the authors' contract. Its rights reference lists
publishing a compiled skill of a copyrighted book to a public marketplace under
"Do not", and its hard rule 1 forbids scraping a book from the web, so the
pipeline could not have run against a URL either.
What shipped instead: the compiled-skill shape (master SKILL.md ~2.0k tokens with
chapter and topic indexes, chapters/ch01..ch20, glossary, patterns, cheatsheet)
filled with original synthesis and linking to the official free chapters. No
passages, figures, or per-paragraph paraphrase. Passes book-to-skill's own
book_skill_validator.py clean, with every file inside token_budget_estimator's caps.
The differentiator is the delta layer. A compilation freezes a source at its
publication date; this one dates it. Every chapter carries "What changed after
2016", and references/book_to_2026_delta.md gives five corrections with primary
citations and per-claim confidence levels: double descent qualifying Ch 5's
U-curve, AdamW splitting weight decay from L2, transformers displacing Ch 10's
recurrence, diffusion growing out of Ch 18's score matching, and self-supervised
learning vindicating Ch 15 while replacing its methods. Two claims are marked
contested rather than propagated, two named as folklore.
Four stdlib-only tools, each with a real refusal:
- reading_path_planner.py — prerequisite closure over the book's actual dependency
graph; exit 3 for a goal the book does not cover, exit 4 with forcing questions
- training_diagnostics.py — Ch 11's rules in priority order, so a NaN is never
reported as overfitting; exit 4 rather than diagnosing with no instruments
- capacity_planner.py — regularization ladder in cost order, "shrink the model"
ranked last in the overparameterized regime; exit 4 on a val-below-train split
- model_arithmetic.py — params/FLOPs/activation memory for conv, linear, MHA and
LSTM/GRU stacks; exit 5 naming the layer whose shapes do not connect
Also: 4 references citing 7-8 sources each, 3 assets, cs-deep-learning-tutor
agent, /cs:deep-learning + /cs:dl-reading-path + /cs:dl-diagnose.
Counters: skills 386 to 387, tools 723 to 727, refs 838 to 842, agents 116 to 117,
commands 146 to 149, plugins 97 to 98 (verified by derive_counters.py --check).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BswsZp5zrJWFAGU6KWNA1s
Runs engineering/book-to-skill end to end on its first real source: OpenAI's
Spinning Up in Deep RL (MIT, (c) 2018 OpenAI; primarily developed by Joshua
Achiam). Cloned openai/spinningup and compiled its docs/ reStructuredText tree
(38 files, ~37k words, ~49K tokens) through the full pipeline -- extract
--mode technical, analysis, 20 chapter files, glossary/patterns/cheatsheet,
master SKILL.md, validator, plugin emitter.
The compiled skill passes book_skill_validator.py in --strict mode with every
file inside budget: a 2,101-token resident core (cap 4,000) plus 20 on-demand
chapters averaging ~1,256 tokens each.
Chapter structure follows the source's own toctree rather than a heading scan:
user documentation (ch01-06), Introduction to RL Parts 1-3 (ch07-09), the
researcher essay / key papers / exercises / benchmarks (ch10-13), one chapter
per algorithm in lineage order (ch14-19: VPG to TRPO to PPO, DDPG to TD3 and
SAC), and the logger/MPI/ExperimentGrid utilities (ch20).
Rights basis is open-license, not fair use -- the emitter's Step-11 gate
refuses a shareable package without one. Upstream's MIT notice is reproduced
in full in the plugin's LICENSE beside this package's own, and README.md names
the source, the author and the source's frozen version; a sidecar JSON is not
a license notice.
Also fixes a defect the emitter only reveals at its final step:
skill_plugin_emitter.py wrote its whole `source` provenance block into
plugin.json, on a stale inline claim that `source`/`attribution` were approved
extension fields. Claude Code rejects an entire manifest on any unrecognized
key (issue #954) and scripts/check_plugin_json.py hard-fails such a manifest,
so every package the emitter produced failed the blocking CI gate on commit.
_plugin_manifest() now emits spec fields only and a new _authoring_notes()
writes .claude-plugin/authoring-notes.json. Recorded as deviation 26 in
engineering/book-to-skill/README.md; the printed marketplace.json snippet is
unchanged, since `source` is a valid key there.
Counters: skills 386 -> 387, agents 116 -> 117, commands 146 -> 147, plugins
97 -> 98. Tools and references unchanged -- a compiled knowledge base ships
notes, not scripts.
All blocking CI gates verified locally: compileall, check_plugin_json --all,
check_skill_names, check_paths, check_frontmatter, check_dual_publish,
check_model_freshness, smoke_scripts (692/692), derive_counters --check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UySnyf5upm4y8xhYA3w6yw
The Skill Security Audit failed PR #984 with 2 CRITICAL CMD-INJECT findings
on hivemind's Node scripts — pre-existing since #979 merged while Actions
wasn't triggering; touching the skill's agent files pulled it into audit
scope. Spawning headless opencode worker processes is this skill's core,
documented function (SKILL.md Prerequisites + the PR #979 dependency
disclosure), so the imports carry the auditor's own suppression directive
with the justification inline. Re-audit: PASS, 0 critical / 0 high.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
- CHANGELOG.md gains the [2.12.0] entry (first tagged release since v2.9.0):
consolidates the previously documented but untagged v2.10.0-v2.11.2 work,
all post-2.11.2 merges, and the full 17-issue triage sweep; the ten stacked
[Unreleased] sections are demoted into the 2.12.0 body so the Release
workflow tags and publishes the whole span. Verified parseable with
scripts/extract_release_notes.py (version 2.12.0, 554-line body).
- Version markers bumped to 2.12.0: marketplace.json metadata,
CLAUDE.md current-version header + footer.
- Counters trued to the derived values (380 skills / 96 plugins / 20 domains /
706 tools / 823 refs / 114 agents / 138 commands) in README badges + prose,
CLAUDE.md, marketplace.json, and the long-stale mkdocs.yml/docs/index.md
site description (was still claiming 345/78/17).
- Docs site regenerated via scripts/generate-docs.py (568 generated pages;
new pages for the recently merged plugins); codex/gemini mirrors resynced;
mkdocs build verified locally with the same plugin set static.yml uses
(670 HTML pages, no errors).
- Fix: the three hivemind worker personas (assets/agents/{coder,scout,tester}.md,
merged via #979 while Actions was not triggering) lacked the frontmatter
`name:` field and hard-failed the blocking G10 gate — named
hive-coder/hive-scout/hive-tester; 645 files now scan with 0 errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
- collect_sessions.py: name-only repo matches (worktree/basename fallback,
which can match an unrelated same-named repo) are now recorded per session
(repo_match), counted in inventory.json, called out in the summary, and
excludable via a new --strict-repo flag
- score_aggregator.py: never-scored detection tracks session ids in a set
instead of substring-matching error text, so a prefix-style id can no
longer suppress another session's never-scored error
- redact_secrets(): broader patterns no longer stack markers on top of an
already-redacted value
- session files read through the MAX_FILE_BYTES cap instead of slurped whole
- README deviations 21-22 recorded; count trued to 22 in CLAUDE.md and
authoring-notes; prefix-id regression + single-marker redaction verified
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017yFdbrdBnFL8Gw7DLrdg19
Five of six findings were real. Verified each against the code rather than
taking them at face value; one was wrong and is noted below.
1. validate_examples.py carried a stale header from the spec-only phase --
"PARKED AS AN ASSET (deliberately not .py yet)... ON IMPLEMENTATION: rename
to skills/agent-memory/scripts/validate_examples.py". The file is already at
exactly that path. Rewritten to describe what it now is, keeping the
substantive part: why it exists, the seven check families, why it compares
the doc's algorithm by source text rather than exec()-ing a code fence, and
that nothing runs it automatically.
2. Missing shebang -- added. The same finding also claimed mode 100644; that
half is wrong, `git ls-files -s` shows 100755 for all five scripts.
3. hooks.json's description still opened "CONTRACT ONLY -- the referenced
scripts are not yet implemented", true of none of them now. Trimmed to keep
only the UserPromptSubmit provisionality (9.5 is genuinely still open) and
to record the measured latency alongside it.
4. The session-id fallback was the finding worth the most. `session_id` is the
right key -- engineering/security-guidance's shipped hook reads the same one
-- but the fallback was the CONSTANT "unknown-session", and sessions dedupe
by value. Had the key ever been absent, every session would collapse onto
one id, len(set(sessions)) would plateau at 1, and every claim would cap at
L1 forever with no error anywhere. Now falls back to the transcript's own
basename, which IS the session id. Verified end-to-end with session_id
omitted from the payload: the atom records the real session UUID.
5. Dead `now` parameter on _eligible_l1 -- removed.
6. marketplace.json metadata still said 104 agents / 120 slash commands,
pre-existing drift on a line this branch already edits. Trued up to 110/130.
Re-verified after: 69 checks 0 failures, SKILL.md 6/6 PASS, both blocking gates
still fire by name, check_paths 620 files clean, counters and plugin-json pass,
all 5 scripts --help, all 3 hooks parse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Turns DESIGN.md from a spec into a working plugin. Five stdlib scripts, three
hooks, agent, command, three references, plugin manifests.
The gates are the design:
L1 -> L2 >= 3 distinct sessions spanning >= 2 distinct calendar days
(`stated` = 2 sessions, day rule still applies; `verified` = 1
observation and is the only day-exempt path)
L2 -> L3 >= 2 distinct projects, >= 30 days, uncontested
Two gates refuse rather than guess. `redacted: true` blocks promotion on any
volume of evidence -- a durability-independent barrier, since a secret restated
across five sessions passes every recurrence gate; the flag firing means the
text was altered, a lexical filter finding one secret is not proof it found all
of them, and L2/L3 are committed to git. An open contradiction freezes both
claims, found by reverse join because the newer atom carries no flag.
All three hooks fail open: a broken memory system costs memory, never a session.
SessionEnd stages promotions to .memory/staged/ and never touches a CLAUDE.md;
only an explicit human adopt does, after backing both files up.
Verified, not asserted:
- all three pinned atom ids from DESIGN.md reproduce exactly
- both blocking gates demonstrated on sample input, named in the output
- end-to-end: two transcripts across two calendar days -> merged L1 atom ->
staged L2 promotion with the path prefix stripped
- reverse join blocks the unflagged newer atom
- cross-tier L2/L3 collision marked at injection time
- recall p50 29ms / p95 31ms / max 35ms spawn-to-exit, scoring itself 2-3ms
over 500 atoms -- interpreter cold start is the entire cost
- validate_examples.py 69 checks 0 failures; SKILL.md 6/6 PASS
- derive_counters --check, check_plugin_json --all, check_paths all clean
DESIGN.md 10.1's "+6" tool estimate corrected to +8 -- the delivered surface is
5 scripts + 3 hooks. README.md's deviations list is authoritative for that and
five other divergences from the pre-implementation spec.
Concept from TencentCloud/TencentDB-Agent-Memory (MIT). No upstream code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
- tighten the SKILL.md description from 390 to 300 chars, core trigger first
- validate HIVEMIND_SERVER_URL and spawn opencode serve with an args array and
shell:false, so no environment value is shell-interpolated
- add LICENSE.txt (Apache 2.0); frontmatter stays name + description per CONVENTIONS
Hivemind delegates grunt work to headless opencode workers (scout/coder/tester)
while the orchestrating agent stays the planner, reviewer, and merger.
- placed under engineering/ per repo layout; targets dev per CONTRIBUTING
- frontmatter is name + description only; SKILL.md is 165 lines
- anti-patterns and cross-references sections included
- external opencode CLI dependency and free-model caveats documented up front
- runtime state (.runs/) gitignored, not committed
Round-44 review, three findings, all verified before fixing.
1. The schema's `redacted` description has always asserted "never
promoted to a committed tier without human review" -- a real gate that
DESIGN.md stated nowhere. Confirmed by grep. Added to 4.1 with the
reason the flag carries: redacted:true means the pass ALTERED the
claim, which is positive evidence the source was sensitive, and
redaction is lexical so finding one thing is not proof of finding
everything. Recurrence cannot substitute -- three sightings of a
scrubbed claim are three sightings of the same unresolved risk.
2. hooks.json did not quote ${CLAUDE_PLUGIN_ROOT} while both precedents
this PR cites do (handoff: python3 "${...}/hooks/session_start.py";
skillopt-sleep: "${...}/hooks/on-session-end.sh"). A path with a space
would break the command. Quoted all three.
3. 4.2.1 says "both sit at L1" but the schema marks only the older atom
(contested, contested_by), so the promotion gate cannot be a field
read on both sides. Specified the reverse join: blocked if own
`contested` is set OR own id appears in another atom's contested_by.
Deliberately not a mirrored `contests` field -- same fact in two
places, needing sync, with nothing able to say which copy is right.
Cheap by construction: 5.2 caps the store at 500 atoms and measured a
full pass at 2-3ms.
Did NOT add a schema->doc dangling-section-ref check. Wrote one, it
reported 3.1.1 and 4.1.2 as dangling, and both exist -- my heading regex
required a trailing period that sub-sections do not carry. Re-ran
correctly: zero dangling refs. A brittle checker for an empty class,
which I got wrong twice inside two minutes, is worse than no checker.
Verified: hooks.json parses; 69 checks, 0 failures; derive_counters.py
--check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-43 review found the Windows gap that 42 rounds missed. Verified it
is a hard break, not cosmetic: the schema's L1 pattern is
^~/\.claude/projects/[^/]+/[A-Za-z0-9._-]+\.jsonl#L[0-9]+$ -- literal ~/
and forward slashes -- so a path built from %USERPROFILE%\.claude\...
matches nothing and an extractor recording the OS path verbatim emits an
atom the schema rejects outright.
Resolved rather than added to 9, because it is mechanical: `source` and
`first_source` store a canonical form, not an observed one. The extractor
derives the ~/-relative forward-slash form from whatever the platform
handed it, the same way 3.1.1's promotion derives the stripped form
rather than storing what it saw -- recording is already a transform here.
Worth noting the blast radius: L2 and L3 are already platform-neutral,
since the stripped form is a bare <session>.jsonl#L<n> with no path.
Nothing committed is affected, only the gitignored L1. 3.1.1's
de-identification stripping bought portability for free.
Also stated first-run behaviour, which 5.4 skipped while specifying
concurrent writers in detail: a missing .memory/atoms.jsonl is the normal
initial state, read as an empty store and created on first write, with
recall returning nothing rather than failing.
Verified: 69 checks, 0 failures; derive_counters.py --check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-42 review: the "run the validator before editing" requirement lived
in 10.1 and the PR body. Someone opening DESIGN.md six months from now
reads neither -- and "a future editor who does not know the file exists"
is the exact failure the doc names. Now a callout at the top with the
command in it. Fourth time this review series has turned up a rule
stated somewhere other than where the person who needs it is looking.
Also sharpened what round 41 recorded about 9. The reviewer read "~700
lines of settled contract downstream of an unmeasured question" as a
reason to trim 4-5 until the extraction spike reports. Worth being
precise instead: a "no" on (2) does not make the promotion machinery
wrong -- recurrence counting, the tier caps and the contradiction
detector operate on atoms however they were produced, and none reference
the extraction method. It makes them unused. Content that would need
rework is worth deferring; content that would go unread is not, since
deleting reviewed text to re-derive it later costs more than leaving it.
Recorded as a sequencing lesson for the next spec this size rather than
a call to cut.
Verified: 69 checks, 0 failures; derive_counters.py --check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-41 review, two findings.
The 1 size claim went stale in a way worth being precise about. 85,875
bytes was correct at 67a1228, but merging dev moved this branch's base
and the file is now 90,312 -- and the parenthetical says "measured at
this branch's base", so it was false as written. Updated to 88 KB /
90,312, and noted that it grew 4,437 bytes while this PR was open, which
is 1's own thesis demonstrating itself on the file it is about. Round
29's principle still holds: the checker verifies the sentence's internal
consistency (method named, units agree), never a live comparison -- both
checks pass on the new figures.
Second, and the better finding: 9 listed six decisions as a flat set,
which understated that two of them decide whether there is a system at
all. (2) extraction-without-an-LLM gates everything downstream -- the
session gate, the tier caps, the contradiction detector are only as good
as what the extractor produces -- and (3) is answered by (2), since
9.3's 2-week trial IS the test of it. The other four are local: each
changes one mechanism and leaves the rest standing, including (5), whose
worst case deletes one hook while the tiering survives on SessionStart.
Now a table, with the cost stated plainly: ~700 lines of settled contract
sit downstream of a question nobody has measured.
Verified: 69 checks, 0 failures; derive_counters.py --check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-40 review made a point the spec had left to inference: the
recurrence gates are a durability filter, not a secrets filter. A secret
seen in three sessions across three days is exactly as much a secret as
one seen once, so the gates were never protecting CLAUDE.md from a leak
-- they only made one slower. The `verified` fast path removes even that,
taking a claim from a single sighting to a committed marker block with
6 rule 1's redaction pass as the sole barrier.
Stated in the fast-path block itself rather than left to a reader
combining 4.1 with 6 -- the third time this review has turned up a rule
living in one section while the surface an implementer builds from lives
in another. Two consequences named: redaction must not be shortcut on the
`verified` path ("a script confirmed it" says nothing about whether the
text holds a credential -- "the staging key sk-... works" is a plausible
verified claim), and a verified 1-observation atom carrying a secret is
the first behavioural test memory_promote.py should have, since it is the
shortest path in the system from raw transcript to committed file.
Verified: 69 checks, 0 failures; derive_counters.py --check passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
The decision-driving §2 predated engineering/memory-engineering landing on
dev (#947) and never mentioned it. Adds the missing subsection: layer
distinction (it audits memory systems; this would be one), the namespace
fence both SKILL.mds must carry, a commitment that §5.1's eviction and
contradiction rules stay expressible in forgetting_policy_linter.py's F1/F4
form, and memory_cost_profiler.py as the §7 budget framework. Conclusion
renumbered 2.5 -> 2.6. Counters untouched by design (spec-only folder).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Bzm6Pafyxja6g4jUDPcei
Round-2 sweep after re-auditing all 15 reported issues against the merged dev:
- #885 generalized: the original fix only renamed self-improving-agent's
status/review, but three more plugins shipped skills whose bare names
shadow Claude Code built-ins. Renamed with the same convention:
playwright-pro init/review -> pw-init/pw-review, agenthub init/status ->
hub-init/hub-status, autoresearch-agent status/resume -> ar-status/
ar-resume. All command references (/pw: /hub: /ar:), docs, audit records,
harness manifests, and mirror trees/indexes updated; the flat mirror
namespace no longer collides on 'status'. New scripts/check_skill_names.py
gate (wired into ci-quality-gate.yml as blocking) fails CI on any future
bare reserved name; rule added to SKILL-AUTHORING-STANDARD.md.
- #969 follow-through: five more scripts print box-drawing characters that
cannot exist in cp1252 (api_scorecard, api_linter,
breaking_change_detector, humanizer_scorer, content_scorer) — same
guarded UTF-8 reconfigure applied; all smoke-tested under a forced
legacy encoding.
Verified: check_skill_names (incl. negative test), check_plugin_json,
check_paths, derive_counters, check_dual_publish, smoke_scripts (634/634),
0 broken mirror symlinks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
Review follow-up on PR #972: the Windows Notes section now sits between
Verification & Testing and Troubleshooting, matching the Table of Contents
order, and the four scripts patched for #969 are back to exactly two blank
lines after the reconfigure block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
- #954: strip non-spec source/attribution keys from all 39 plugin.json
manifests so Claude Code's validator accepts them; metadata preserved in
new .claude-plugin/authoring-notes.json sidecars; check_plugin_json.py now
hard-fails manifests carrying those keys and sanity-checks the sidecar;
CLAUDE.md ClawHub schema section updated to the new rule.
- #949: move the c-level-agents plugin out of c-level-advisor/ to a
top-level directory so the two marketplace sources no longer overlap;
updated marketplace.json source, homepage, descriptions, all
cross-references, docs, harness manifest, mirror-tree symlinks/indexes,
and rebased the moved files' relative links; domain counters trued up
(18 -> 19 domains).
- #933: replace dead links to the gitignored maintainer-local megaprompts/
tree with annotated plain-text references (44 files: SKILL.md, READMEs,
agents, commands).
- #931: DynamoDB on-demand pricing updated to post-Nov-2024 rates
($0.625/M writes, $0.125/M strongly consistent reads).
- #969: skill_security_auditor.py and the three dossier scripts reconfigure
stdout/stderr to UTF-8 (errors=replace) so legacy Windows codepages no
longer crash at print time; PYTHONUTF8=1 documented.
- #968: Windows Notes section in INSTALLATION.md + README pointer for the
core.symlinks mirror-tree checkout caveat.
- #924/#885 residuals: hook commands quote "${CLAUDE_PLUGIN_ROOT}" paths in
all plugin hooks.json/settings.json (space-safe roots); removed the stale
pre-rename status/review mirror symlinks and index entries left over from
the memory-status/memory-review rename.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qgc6RYXWJPr5oW9DHU7zR4
Round-38 review noticed that the committed contract files already encode
an answer to some of 9's open decisions -- hooks.json wires
UserPromptSubmit even though 9.5's option (c) is to delete that hook.
The doc knew: 9.5 already says "if (c) wins, hooks.json must shrink too."
But that only helps a reader holding both files. Read on its own a
hooks.json says "these three hooks exist", which is precisely the wrong
impression, and a contract file is exactly the artifact someone reads on
its own before implementing. Same lesson as round 36's contested-tag gap,
one level up: a cross-reference is not a contract, and that applies
between files as much as between sections.
hooks.json's own description now marks the entry PROVISIONAL, names the
open decision, gives the reason (the budget is dominated by interpreter
cold-start, not by the script's work), and says plainly that listing the
hook is a contract for the shape it would take IF it survives -- not
evidence the decision was made. SessionStart and SessionEnd are marked
not provisional so the warning stays scoped.
Verified: hooks.json parses; 69 checks, 0 failures; derive_counters.py
--check unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-37 review. The substantive finding: 9.5's options (a) fits /
(b) raise the budget / (c) drop the hook all treat spawn cost as a
constant to tolerate -- while last round's measurement showed spawn cost
is the ONLY cost that matters (scan 2-3ms, interpreter ~20ms). None of
the three attacks the measured bottleneck. That follows directly from a
number this doc already carried, and I should have drawn it rather than
waiting for a reviewer to.
Added (d): a warm resident process holding the store in memory, with the
hook reduced to a socket round-trip. Named as a real option because it is
the only one that removes the dominant term, explicitly NOT as the
recommendation -- its costs land on this design's own properties. A
"never blocks, exit 0" hook gains a liveness dependency and must keep the
cold path anyway, so complexity is added to rather than swapped for what
(a)-(c) need; lifecycle and stale-socket handling are a second heuristic
alongside 5.4's; a resident process holding memory contents in RAM is a
different security surface; and stdlib-scripts-that-exit is this repo's
shape. Sequenced behind the busy-machine measurement: if 100ms holds
under load, a daemon buys latency nobody needed.
Also trimmed the three passages the review named as archive-not-spec.
One of them ("the sentence went stale twice (53 -> 57 -> 67)") was itself
stale at 69, which is the argument for cutting it rather than updating it.
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-36 review, three real findings.
1. Session-id uniqueness was the one load-bearing claim in 3.1.1 asserted
rather than evidenced, while everything around it cites line numbers or
arithmetic. Measured it: transcript filenames on a live install are
RFC-4122 UUIDs (version 5, RFC-4122 variant), shown with the commands
that produced them. One install is enough to establish shape, not to
promise the scheme is stable -- so the more useful half of the fix is
the fallback the reviewer noted was missing. The glob now has defined
behaviour for all three outcomes, and the >= 2 row is the one that
matters: without it a naive implementation takes the first match and
attributes a claim to the wrong session, which is a *wrong* citation
rather than a missing one, and 6 rule 6 cares about that distinction.
2. 4.2 states the contested-rendering rule tier-agnostically, but 5.2 is
the contract user_prompt_submit.py actually gets built from and never
mentioned it. An implementer working strictly from 5.2 ships a recall
path that surfaces a contested claim as plain fact. Stated in both
places now -- cross-references are not a contract.
3. L1 had a stored cap (500 atoms); L2/L3 had only injection budgets, so
a marker block could grow without bound while every session saw a
silently truncated view -- the 1 failure this design exists to prevent,
one layer down. L2 caps at 60 atoms with overflow demoted to L1
(recoverable, re-promotable); L3 caps at 30 and refuses further
promotions instead of deleting, since "never auto-demoted" means the
cap blocks inflow rather than choosing what to lose.
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-35 review called the 100ms recall budget the highest-risk
unverified assumption, and estimated interpreter cold start at 20-40ms.
9.5 has prescribed the measurement since it was written -- "time a no-op
python3 -c pass plus a 500-atom scoring pass at p50/p95" -- so I ran it
rather than citing an estimate.
n=40, Linux container, otherwise idle:
python3 -c pass p50 12.4 p95 30.8 max 36.0
spawn + 500 atoms + score + top-5 p50 23.2 p95 30.1 max 50.6
...of which in-script work p50 2.1 p95 3.0
This reframes the risk rather than settling it. The scoring pass is
~2-3ms, so the 500-atom cap is not the binding constraint and never was
-- cold start is essentially the whole cost, which makes the budget a
process-spawn question rather than an algorithmic one. 100ms holds with
~3x headroom here; the 50.6ms max shows a real tail that would widen
under load. Recorded as a floor, not the answer: 9.5 asks for a busy
machine and this was an idle container.
Also cut two asides the review named as review-thread artifact rather
than spec: the 3.1 paragraph on why a rationale cannot be a $comment
inside a fixture (5 lines -> 3, keeping the reason), and the validator's
8-line comment on not whitelisting $comment (-> 4, keeping the warning a
future editor needs).
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-34 review, two actionable items.
5.1 had no internal-budget discussion while 5.2 argues at length that its
1s hook timeout is a backstop rather than the target -- an asymmetry a
reader can only resolve by guessing. Stated why it is deliberate: this
hook runs once per session rather than once per prompt, so a slow run
costs one startup instead of compounding; and its work is bounded by the
2KB/4KB byte caps rather than by a scan that grows with history, where
recall scores up to 500 atoms. Also noted that 9.5's cold-start finding
lands here too if it comes back bad.
Addressed the .py.txt parking as a *precedent* rather than defending this
instance. What makes it legitimate is not intent, which is unfalsifiable,
but that the file is not a tool: no plugin ships it, no skill owns it,
nothing invokes it, and counting it would make python_tools less accurate
rather than more. The abuse it could be mistaken for -- a real tool
renamed to keep a headline number down -- is separable by one question a
reviewer can ask: is anything supposed to run this? Pointed at 11's
audit/ option as the way to avoid the pattern entirely.
Not changed: hooks.json naming three scripts that do not exist. Checked
whether the hypothetical lint exists -- nothing in scripts/ or
.github/workflows/ references hooks.json at all -- so the file's own
"CONTRACT ONLY" description is the whole mitigation needed today.
Verified: 69 checks, 0 failures; derive_counters.py --check unchanged
(363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-33 review. 4.1's L1->L2 gate has two clauses -- >= 3 sessions AND
>= 2 distinct calendar days (UTC) -- and family 4 only checked the count.
The days clause is the half that stops one long working day from minting
an L2 claim, which is the exact case 4.1 calls out for the `stated` fast
path, and it had neither a check nor a fixture exercising it.
Added it for L2 atoms whose confidence is not `verified` (4.1 names
`verified` as the only exempt path). Worth stating why the test is
sound rather than approximate: first_seen and last_seen bound every
observation, so date(first) != date(last) is equivalent to ">= 2 distinct
days", not a proxy for it. Same date means every observation fell inside
it; different dates means at least two were touched.
Verified by shrinking the L2 fixture's window to one day:
FAILED: atm_961f033d spans >= 2 distinct calendar days
FAILED: atm_961f033d/L2 promoted_at falls inside its own observation window
The second is family 7 catching a side effect of the same injection,
which is the intended overlap.
Also fixed the placement section's framing. It claimed "neither option is
endorsed here" and then introduced its bullets with "both cut against
staying here" -- a lean, contradicting the sentence above it, and the
third reviewer in a row to read it as one. Now two symmetric cost lists
with nothing weighed.
Verified: 68 -> 69 checks, 0 failures; derive_counters.py --check
unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
Round-32 review. 4.1.1 step 3 enumerated nine of the schema's thirteen
required fields, so a memory_promote.py written literally against it
would emit a schema-invalid atom. The reviewer also caught that 4.1.3's
confidence-merge rule is scoped to "(5.3)" -- the same-tier SessionEnd
merge -- so it does not cover this cross-project one by implication.
Added a table for the four: `claim` from the earliest contributor (the
group normalizes identically; earliest is for determinism, since
normalization is lossy on case and punctuation), `confidence` = max()
across all contributors (4.1.3's principle, restated rather than
assumed), `redacted` = true if any is (over-claiming costs nothing,
under-claiming loses the signal), and `kind` must agree or the group is
ineligible -- `kind` is not in the hash key, so two projects classifying
the same sentence differently means the "same claim" premise is what is
shaky. Refusing keeps this section's one-directional property: L3
under-fires, it never mis-fires.
This step has now shipped three omissions (`source`,
`promoted_from_projects`, and these four), each caught by a human
reading the list against the schema. Added a check for the class: every
schema-required field must appear in 4.1.1. Re-injecting the omission
fails on ['kind', 'redacted'].
Two notes on that check, both found by verifying it rather than trusting
it. It first reported first_seen/last_seen/first_source as missing --
false, because norm_prose strips `_` as an italic marker, so the needles
had to be normalized the same way. And it is a substring test over the
section, so `confidence` and `claim` satisfy it via incidental mentions
elsewhere; that limit is now stated in the code rather than implied.
Verified: 67 -> 68 checks, 0 failures; derive_counters.py --check
unchanged (363/89/663).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym