Repository navigation
feat(ownership): rebuild the ownership plugin - #9
Merged
Merged
Conversation
How the plugin meets its requirements specification, the rules the specification left open and how they are settled, and what changes in the repository around it: marketplace, toolchain, CI, dogfooding, and the fix of CLAUDE.md and CONTRIBUTING.md. Changelog: none — a design document changes nothing for whoever installs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changelog: none — the plan is internal to this repository Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… 15-26) Adds explain, init, the JSON Schema, the generated-guard hook, the slash command, the public surface and distribution tests, performance, the documentation, the root Spurfile and CI, dogfooding and the cleanup of CLAUDE.md and CONTRIBUTING.md. Corrects three earlier tasks found while running the plan's code: the deleted tracked files come from git diff-files, which keeps owner coverage inside its budget on Windows; a fixture of the drift test carried the marker on its own first line; a regex failed the lint. The git add of each commit step now runs from the plugin directory. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Translates the requirements of the rebuilt plugin and folds in the rules the original left open: the schema URL, the case-insensitive owner lookup in CODEOWNERS, the names and shapes of entriesForPlugin and ownersOf, init --stdout writing the summary to stderr, the init target counted as a repository file, and OWNERSHIP_HOST_ROOT as the hook's extra root. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Also formats the requirements tables with Prettier so format-check passes. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Every broken relation between owned, generated and ignore paths is collected in one run; an owned containment suggests the entries that replace the outer path. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…GitHub does Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…he marker Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…thub formats Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Three modes — a skeleton, a single owner, the CODEOWNERS — give a plan, and the plan is written line by line so the same tree gives the same bytes. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
It never overwrites one, never picks an owner nobody named, and counts the file it creates as a repository file, so that file is born covered. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A corpus of valid and invalid files goes through the schema and through the loader, and both must agree on each one. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The generated-guard hook runs before Edit, Write, MultiEdit and NotebookEdit. It denies a file declared in generated[], a file whose first line carries the marker, and a Write whose content carries it. It judges the path behind a link too, and stays silent whenever it cannot judge. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
It creates the file only by running ownership init, asks the mode and the owner of each path, and never touches the CODEOWNERS. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… ships The package exports exactly the names other plugins build on. Tests pack it, install the tarball, import those names from the compiled code and run the bin; copy the plugin directory alone, install it from its lockfile and run the hook from there; and check that nothing shipped points inside the repository that develops it. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…th it Changelog: none — the toolchain of this repository is not visible to whoever installs a plugin Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The root ownership file comes from ownership init --from-codeowners; the ownership plugin project declares its own paths in a file the root includes. CODEOWNERS loses the lines of paths that no longer exist, and the local Claude Code settings are ignored like any per-machine file. Changelog: none — who owns the paths of this repository is not visible to whoever installs a plugin Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Changelog: none — how this repository is developed is not visible to whoever installs a plugin Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d plugin aliases init --from-codeowners declares only an @handle as the owner and notes an email; generated-drift ignores the line-end style of a checkout; a YAML alias inside a plugin block is resolved before it reaches the plugin. Changelog: none — the plugin is not published yet Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… be read A declared generated path that named a directory, or a file that could not be read, threw out of the check: Node printed a stack trace and exited with 1, the same code as a run with findings, so CI could not tell a crash from drift. Both are now findings on the generated entry, naming the path and the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
explain added a trailing slash to anything that is a directory on disk, but Git lists a submodule, a nested repository and a link to a directory as one file. The path then missed its owned entry and was reported as excluded by a .gitignore. A path the repository lists as a file now keeps no slash, and a link is no longer followed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An alias was resolved only inside a plugin block. A block, a plugins map, an owned entry or a single value given by an alias failed the type checks with "must be a map; got *name". Every value now goes through one place that resolves the alias before its type is checked. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The requirements now say plainly that only the exact marker counts and that case is the only variation; a test pins that spacing variants are not the marker. The code already behaved this way. Changelog: none — the behaviour does not change; only the requirement wording and a test are added Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A generator that hung kept validate, and CI with it, waiting forever with no finding. A command still running after ten minutes is now stopped together with every process it started, and reported as a failed command that names it. The limit is fixed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hook ends itself with a timer, but a timer cannot fire during a synchronous git call, so on a machine without a timeout command a slow or hung git held every edit for as long as git took. git now gets the remaining budget as its timeout; when it runs out, the hook stays silent and lets the edit through, as it does for any defect of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntry owner An ownership file accepts only an owner that starts with @, so init declares the first such owner on the line and notes the others, emails included. The requirements now state that rule. Changelog: none — init already behaved this way; only the requirements change Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
init --from-codeowners dropped a path whose line lists only emails without a word: it was only counted among the files without an owner. It now appears in the commented "Left out" block and in the output, once at the level an entry would have had, with the reason that an owner must start with @. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stripping every CR before comparing hid real drift: a file kept with CRLF on purpose against an LF output, or a generator that starts printing CRLF where Git keeps it. The output and the work-tree file are now hashed with git hash-object at the declared path, so the .gitattributes and core.autocrlf conversions decide, and an uncommitted edit is still drift. A failure of Git to hash is a finding with the reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hangs The five-second timer could not fire while the hook blocked the event loop on synchronous file system work (realpath, loading the ownership files, reading the marker), so a slow or hung file system, such as a network drive, could hold an edit indefinitely. The judgement now runs in a worker thread; the main thread stays free, and past the budget the hook ends silent with 0 and lets the edit through. The git deadline stays inside the worker so a hung git does not outlive the hook. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
plural, the file:line location of an origin and the relative-to-root check each lived in several places, so a fix to one copy had to be repeated in the others. Each now lives in one module. isDirectory stays separate on purpose: explain must not follow a symbolic link, init must. Changelog: none — internal refactor that shares duplicated helpers; no behaviour changes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On POSIX the process group was signalled and the check returned at once. The signal is delivered asynchronously and the shell dies before the generator it started, so the generator could still be running when it was reported as stopped. Wait, bounded, until the group is gone. Changelog: none — the Unreleased entry on stopping a generator that never finishes already describes this behavior. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rebuilds the
ownershipplugin from its design and implementation plan (docs/superpowers/specs/2026-10-09-ownership-plugin-design.md,docs/superpowers/plans/2026-10-09-ownership-plugin.md, tasks 1–26), and makes this repository the first one it validates.The plugin (
plugins/ownership/)version: 1:owned,generated,ignore,includeandbasePath, with literal, case-sensitive and disjoint paths, plus a published JSON Schema for editor support.ownership validatewith three checks over the whole repository —ownership/owner-coverage,ownership/codeowners-matchandownership/generated-drift— intext,jsonorgithubformats.ownership explain <path>: every rule that reaches a path and where it was declared.ownership initand the/ownership:initcommand: a skeleton, a single owner, or a file derived fromCODEOWNERS.pluginsblocks for other plugins, and the package as a library (resolved configuration, checks,CODEOWNERSevaluation, the generated marker).docs/requirements/, README and CHANGELOG.The repository
Spurfiledelegating to each plugin, and a CI workflow that runs the plugin's checks and validates the repository with it..ownership.ymlat the root and in the plugin declare who owns every path;CODEOWNERSagrees with them.CLAUDE.mdandCONTRIBUTING.mdpoint at what exists instead of references that died; the plugin is listed in the marketplace.Fixes from the final review — generated paths that are directories or unreadable, generators that never finish (stopped after ten minutes), submodules and nested repositories in
explain, YAML aliases anywhere a value is read, line-end comparison as Git would store the file, andCODEOWNERSlines with only emails ininit.Test plan
./spur check(install, plugin checks, repository validation) exits 0ownership validateon this repository: no violations found🤖 Generated with Claude Code