Skip to content

The first release, v0.1.0 - #2

Open
jeffrey-aguilera wants to merge 108 commits into
mainfrom
1-the-first-release-v0-1-0
Open

jeffrey-aguilera wants to merge 108 commits into
mainfrom
1-the-first-release-v0-1-0

Conversation

@jeffrey-aguilera

@jeffrey-aguilera jeffrey-aguilera commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

The first release of the kit, as #1 describes it, with the review's changes, the module architecture #4 describes, and the portable shape #5 describes: the repository carries the text an agent reads, the release carries the machinery as modules, a file's rules are discovered through kit context rather than copied into any harness, and this repository uses its own kit.

What changed

  • .kit/1st.md, the entry: the integrity rule, the root checkout as the human's desk, the five-step loop, plans as issue prose, the ten-line worker contract, and the table of rules by file type. 78 lines.

  • .kit/rules/{markdown,bash,just,yaml,json,gitignore}.md: one rule file per kind, rules only.

  • kit issue, kit tidy, kit status, and kit rebase, spelled that way and no other: no justfile veneers, here or in a consumer. rebase refuses the root checkout, the human's desk, whatever branch it has, and the default branch anywhere, and otherwise rebases a worktree's branch onto origin/main and pushes it with --force-with-lease; 1st.md names these as the spellings an agent uses, and git rebase joins the deny list. issue links the branch under Development with gh issue develop, makes worktrees/<N>-<slug>, pushes a start commit, opens a draft pull request, assigns the issues, and sweeps uncommitted work out of the root checkout into the worktree, fast-forwarding the root only when it is on the default branch. It never switches the checkout it runs from.

  • No git hooks of the kit's own, and nothing that binds the human at the desk. .kit/lefthook.yml declares which linter runs over which paths before a commit, a repository's lefthook.yml takes it with extends and adds its own, and kit lint is lefthook run pre-commit, over the staged paths or with --all-files over every tracked path, which is also the gate's spelling.

  • Modules (Modules: an extensible kit, with context discovery in place of harness mirrors #4). Everything the release delivers is a module under modules/<name>/: module.yml (description, default, depends, answers), files/<base>/ mirrored at the base's root, templates/<base>/ rendered from the consumer's .kit/answers with @key@ keys, and docs/. Two bases: repo, the repository root, recorded in .kit/manifest, and home, the user's ~, recorded in ~/.kit/manifest, the desk's own record, which kit check reads where it exists and reports absent where it does not. A module naming another base is refused with the bases listed. No module delivers to home yet. One small YAML reader, bin/yaml.sh, serves module.yml and the rules' frontmatter alike. modules/module.schema.json is the schema for module.yml, validated in the gate with ajv and held to by kit install. docs/architecture.md draws the whole, in three SVG figures. kit install [<module>…] activates the defaults and the named ones with their dependencies; kit module list | add | remove changes the set, and remove refuses a module another active one depends on. The manifest records the set on a modules line. This release carries kit, lefthook, ci (a template, with ci_runner answered ubuntu-latest by default), and claude.

  • Context (Modules: an extensible kit, with context discovery in place of harness mirrors #4). A rule lives once, in .kit/rules/<kind>.md, and is copied nowhere. kit context <path>… names the rules governing each path, by the paths: globs and the content: first-line patterns in a rule's frontmatter, and appends every ask to .git/kit/context.log. The claude module places .claude/CLAUDE.md importing AGENTS.md, since Claude Code reads AGENTS.md only as a fallback when no CLAUDE.md is present, so AGENTS.md opens with the prose sentence every agent reads and no @ import; and it merges into .claude/settings.json the deny list, the spellings .kit/1st.md forbids and a PreToolUse hook on Write, Edit, and MultiEdit that runs kit context --claude, which answers in Claude Code's hook protocol with the rules' paths, the same answer the plain call prints, so reading a rule stays an act the agent takes. .claude/rules/kit-<kind>.md is gone. Cursor, Copilot, and Gemini (Targets for Cursor, Copilot, and Gemini #3) become adapters on this mechanism and stay open.

  • .github/workflows/kit-ci.yml, the ci module's template: one CI job whose steps are one kit command each, kit gate (the root justfile's ci recipe, refused when there is none, since a gate that cannot run clears nothing), kit check, and kit linked (the linked-open-issue refusal), on the runner .kit/answers names. The two new verbs are what a desk can run too.

  • bin/kit: install, update, check, module, context, gate, linked, version. update retires a path the new release no longer delivers, removing it when it is still the previous release's content, taking its entries out of a merged .json when the base is at hand, and leaving an edited one with a warning. The manifest records the release's digest per path, so an edited file reads as edited at every later update and is three-way merged with the previous release as the base. KIT_BASE names the base on a machine without mise.

  • .github/workflows/release.yml: only a release/* tag starts a run. A release, release/<major>.<minor>.<patch>, is tagged on main (any commit on it, not only its head); a pre-release, the same with -<suffix>, is tagged on a commit that is not on main and is published as a GitHub pre-release. The repository carries no version file: the workflow stamps the part after release/ into the packed kit's version, and a checkout is unreleased. The run refuses a tag that is not release/ and a version, a release tag off main, and a pre-release tag on main.

  • This repository: AGENTS.md opens with "Read .kit/1st.md first", and the installed copies (.kit/, .claude/, .github/workflows/kit-ci.yml) are committed, as in any consumer. A commit that changes a file under tree/ or targets/ rewrites them in the same commit: the pre-commit hook runs just refresh and stages what it rewrote, proven on two real commits. The copies stay committed rather than ignored so a fresh clone has its entry file, its deny rules, and its workflow before anyone runs anything. tests/ is a bats suite over install, update, check, both hooks, and the packed tarball.

  • This repository's own upkeep: .editorconfig, and Dependabot for the SHA-pinned actions. The markdown rule's linter reads a delivered .kit/markdownlint.jsonc, so a fresh repository lints its delivered markdown clean and this repository has no markdownlint config of its own.

The three open questions, decided here

  • A consumer may extend the deny list. The kit owns only the entries it writes: update removes the previous release's and adds the new one's, and leaves the rest.
  • No harness holds a copy of a rule. kit context is how a harness, and a person, finds the rules for a file, and the Claude Code hook makes the call for the agent.
  • The spelling is kit issue 42, from the release on PATH, with no justfile veneer anywhere: an alias is a habit that does not travel.

Checked

  • just ci passed on this desk through the mise-pinned toolchain: the linters lefthook.yml declares over every tracked path, 63 bats cases over install, module, context, templates, gate, update, check, targets, lint, rebase, tidy, status, and the packed tarball, and bin/kit check over this repository's installed copy.
  • just kit status, just kit tidy --dry-run, and just kit issue 1 ran from this worktree against this repository. issue took the reuse path: existing branch, worktree, start commit, and pull request, nothing duplicated.
  • The release workflow, end to end, on the pre-release tags v0.1.0-rc.1, v0.1.0-rc.2, and v0.1.0-rc.3, the last on the final layout: a fresh repository pinned to rc.3 installed exactly the delivered text and the Claude target's copies, passed kit check, and had kit rebase refuse main. For rc.2, a fresh repository pinned to it installed exactly .kit/1st.md, .kit/rules/, and .kit/manifest, wired core.hooksPath to the release under mise's install directory, and passed kit check. For rc.1: every step passed, the GitHub pre-release carries kit-0.1.0-rc.1.tar.gz and its checksum, and in a fresh repository "github:empowerite/kit" = "0.1.0-rc.1" in mise.toml installed it with no asset pattern. kit version printed 0.1.0-rc.1, kit install and kit check passed, and mod kit ".kit/kit.just" listed the three recipes. rc.1 and rc.2 are deleted; rc.3 and rc.4 stay until the branch-deletion workflow, once on main, finds them unreachable.
  • The CI check on this pull request is the delivered workflow running on itself, and it passed on ubuntu-latest: just ci (the same 14 bats cases), bin/kit check, and the linked-open-issue step. Three earlier runs failed on mise's npm backend for bats, which could not exec on Linux; bats now comes from the registry's aqua: backend, which fetches the release archive and needs no node.

From review

Copilot's four findings, answered in their threads: three fixed in 76f202c (the start commit no longer sweeps staged work, tidy keeps a detached worktree whose commit main lacks, the gate fails on an unreadable justfile rather than skipping), and one moot, since the pre-rebase hook it concerns was replaced by kit rebase.

From the thorough review

Six commits, one per group: four defects in bin/ (an update that overwrote a repository's own file at a path new to the release, a merged .json updated without its base, a dangling release link, a verb without --help) plus the retire workflow's thirty-release limit; git.sh without state passed through dynamic scope; the on-ramp as named steps; just refresh driven by the manifest and the delivered workflow without a special case for this repository; the bash and just rules brought in line with the code; and a documentation pass over banners, headers, the entry file, and the tests.

Install paths, and the picture of the work

  • The kit does not assume mise. The README gives three ways in: a mise pin, brew install empowerite/tap/kit from the tap infrastructure#459 provisions, and the release tarball by hand. bin/kit resolves the release through any symlink, as Homebrew links it, and kit update fetches the previous release for its merge base from the kit's cache, from mise if mise is there, or from the GitHub release itself; proven across rc.3 and rc.4 with mise off PATH.
  • kit status draws the work in flight in three groups, a blank line between blocks and no headings: the desk, the root checkout, with its branch, how far it has moved from where that branch left origin/main, the lines and the uncommitted paths of its working tree, and the commits it carries past that point; each worktree under worktrees/ drawn the same way, by issue number, labeled by its path from the current directory with the branch underlined; and each open pull request with its state (one of eight, each with its glyph, colored by family), its age, its checks (how many passed and how long they took, or which failed), its unresolved review threads when there are any, the branch a worktree here holds, and the issues it closes. --all, the default, shows every open pull request with its author before the state; --mine shows the caller's alone and no author; --owner=<login>, given once or more, the named authors'. Then the branches whose upstream is gone. Everything that is detail is dim. --json prints the same facts as one document, the pull requests chosen the same way. One request to the forge, and every other figure a local git read.
  • The README opens with why the kit exists and what it is for: verification that does not depend on anyone's word, with the rules as the text the checks hold the work against. Then how to take it, with the by-hand install through gh release download.

Not checked

  • kit update across two real releases. The pre-releases before v0.1.0 have a layout this release does not read, and no upgrade path from an unreleased version is kept; the update is tested with a synthetic later release and KIT_BASE, and a base fetched from a GitHub release was proven to land in the cache. The pre-release rc.5, tagged from this branch, installs a fresh repository by hand and through a mise pin, and every verb answers from it.
  • just kit issue on a fresh issue: only the reuse path ran here, to avoid filing a test issue.

Noted

  • mise warns that actionlint is deprecated in favor of jactionlint. The fork installs a binary named jactionlint, so it is not a drop-in for the hook and the workflows; actionlint stays pinned, and the switch is a change of its own.

Decided in review

  • No {{...}} interpolation in a justfile; the file's path is an exported variable, and the just rule says so.
  • A release tag goes on main, a pre-release tag off it; the tag is the only version, stamped into the packed kit.
  • Harness adapters are opt-in modules, above.
  • The committed .kit/ is 1st.md, rules/, lefthook.yml, and manifest, plus the repository's own answers where it has any. The verbs and the bash modules stay in the installed release; nothing in the repository reaches them except through kit.
  • Desk versus runner: mise.toml pins what both run, actionlint included, since it reads workflow files and needs no runner; mise.desk.toml, read when MISE_ENV=desk, which a desk exports, pins Lefthook. The delivered CI job sets MISE_ENV=ci so a consumer with a runner-only tool pins it in mise.ci.toml; this repository has none. just ci is the gate wherever it runs; the pre-commit hook is the narrower check the fragment declares, shellcheck and actionlint over the staged paths. The lint tests skip where Lefthook does not answer. MISE_PROFILE is an undocumented alias of MISE_ENV; a [profiles] table is not a mise feature. lockfile_platforms makes a lock written on a Mac carry the runner's platform.
  • No [kit.lint] table in mise.toml: mise warns on an unknown table on every invocation. The linters are declared where the hook manager reads them.
  • The source layout is the release layout: bin/ and modules/, with docs/ beside them. A checkout's kit version is unreleased; a release's is the stamped tag. This repository's .kit/, .claude/settings.json, and .github/workflows/kit-ci.yml are its installed copies, as in any consumer; README § Maintaining lists them.

Portable (#5)

  • .kit/settings.yml replaces .kit/answers: forge, gate, require_signing, and answers, read by the kit and never written.
  • Modules a repository defines under .kit/modules/<name>/, in the release's shape, validated, listed with their origin, and installed, checked, updated, and removed like a delivered one.
  • kit is the one default module; ci-github, lefthook, and claude are named.
  • kit ci runs the gate, kit check, and kit linked anywhere, every one reported. The ci-github workflow takes the release the manifest names from its public URL and runs kit ci; mise appears only where the repository has a mise.toml.
  • kit gate runs the gate setting, else the detected justfile recipe, Makefile target, or package.json script, and refuses when there is none.
  • The forge seam: bin/forge.sh names nineteen calls, bin/forge-github.sh answers them over gh, chosen by the forge setting or by origin's host. The core verbs never reach it; gh is the GitHub provider's requirement alone, and the base release is fetched with curl.
  • Regions: a third placement rule beside copy and JSON merge. A file under a module's regions/<base>/ is written between marker comments inside the file the repository owns, kit check holds only what is between them, update merges only that, and removal takes the region out. The kit module places the AGENTS.md sentence and the /worktrees/ ignore entry that way, so a repository's own AGENTS.md and .gitignore never fail kit check. Markers follow the file's comment syntax by extension, one file holds one region, two modules delivering a region to the same path are refused at render, a region that opens and never closes is refused by name, and a symlinked destination keeps its link. This repository's just refresh is kit update run over the index export with HEAD's release as the merge base, staged back through a temporary index, so a desk's unstaged text around a region is never staged with the kit's change and an entry the source dropped is retired.
  • Generate (Generate: a directive opens a region a command fills, and the kit's regions are made that way #8): a :generate:<command>:until:"<pattern>" directive, enchiridion's, in a file's own comment syntax opens a region its command fills; kit generate expands every one in the repository to a fixed point, kit generate --check names a stale one, kit check runs it over the whole repository, and the lefthook fragment over the staged paths. The command is one command and its arguments, no shell operator. The kit's own regions are made that way: install writes :generate:kit region <path>:until:"kit:end" and the content, kit region <path> prints what the active modules deliver there, update regenerates a region rather than merging it, and a hand edit is a stale region, refused by the gate.
  • Each rule names its linter; kit lint runs it over the paths the rule governs, with no hook manager; the lefthook fragment is one command calling it. The workflow rule is split out of the YAML rule.

Host decisions abstracted (#11, #12, #13)

  • Include placement (A module says what it expects of a file the repository owns, and kit check holds it: the include placement #11): a module says under expects: what line it needs in a file the repository owns, such as lefthook's extends: [.kit/lefthook.yml]; kit install names an unmet one as left to do, kit check refuses until it is there, and the kit writes nothing into the file. The placement rule is now: an include where the format composes, a region where it has comments, a merge by key where it has neither, a copy only where the file is the kit's own.
  • GitLab (A GitLab forge provider, the forge's noun, and a conformance suite every provider runs #12): bin/forge-gitlab.sh answers every call of the forge seam over glab, picked by an origin at gitlab.com or by forge: gitlab for a self-hosted one. The seam grows noun and pr-url, so kit status draws #3 or !3 in otherwise identical rows and kit linked and kit issue say pull request or merge request. tests/forge-conformance.bats drives both providers through a routed fake of their tool against one world and holds every answer to one shape.
  • Trackers (A tracker seam apart from the forge: GitHub issues, ADO work items, and references that carry their tracker #13): bin/tracker.sh separates the issue from the forge. A reference names its tracker, #7 for the forge's own and AB#4521 for an Azure DevOps work item declared in .kit/settings.yml. kit issue, kit linked, and kit tidy take references; a non-forge issue gets an ab4521-<slug> branch and a Closes AB#4521 line in the change's body. docs/trackers.md is the page.

Neither GitLab nor Azure DevOps has been run against a real instance; the fakes prove each provider's folding of its API's answers, not that the API answers as the fixtures do.

Toolchains and policy (#14, #15)

  • Toolchains (A toolchain seam: install, outdated, and update, for mise, Brewfile, nix, and devcontainers #14): bin/toolchain.sh asks mise, Homebrew, and docker the same questions, install, outdated, update, path, and run. A repository declares its toolchains or the kit detects them from mise.toml, Brewfile, and toolchain.Dockerfile. kit gate runs the gate through them, inside the image where docker is declared, and the delivered workflow installs with kit toolchain install, its mise action kept only to put the mise binary on the runner. All three providers ran against the real tools on a desk, and this repository's CI ran the new workflow.
  • Policy (Policy as data, checked by kit policy, with an engine behind the same verb when a pattern cannot say it #15): kit policy gathers one facts document, the branch, the commits past the fork, and the change's title, and an engine names the refusals: patterns by default, or Open Policy Agent over the Rego under .kit/policy/. The policy-opa module delivers the same rules in Rego, and tests/policy.bats holds the two engines to identical refusals. kit ci runs it as its fourth check. This repository turns the commit-subject rule off until this pull request merges, since its early commits predate the rule.

Closes #4
Closes #5
Closes #8
Closes #11
Closes #12
Closes #13
Closes #14
Closes #15

Left to a human

Review and merge. Then tag release/0.1.0 on the merge commit on main: the release workflow publishes kit-0.1.0.tar.gz with that version stamped in, as the release kit 0.1.0. A mise pin is { version = "0.1.0", version_prefix = "release/" }. Nothing in the tree needs to change first.

🤖 Generated with Claude Code

@jeffrey-aguilera jeffrey-aguilera linked an issue Oct 24, 2026 that may be closed by this pull request
jeffrey-aguilera and others added 5 commits October 23, 2026 22:02
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@jeffrey-aguilera
jeffrey-aguilera marked this pull request as ready for review October 24, 2026 05:02
jeffrey-aguilera and others added 7 commits October 23, 2026 22:03
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The npm backend's bats could not exec on a Linux runner; the aqua backend fetches the release archive and needs no node.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… release on it

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…exported variable

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A target is a harness that insists on files outside .kit/. kit install names them, the manifest records them, and update and check honor the same set. claude is the one target this release knows; kit target add and remove change the set later.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jeffrey-aguilera and others added 2 commits October 23, 2026 23:03
…op as a list

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…equisite gets its link

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved defects can corrupt update metadata, bypass validation, publish staged work unexpectedly, and remove references to unretained commits.

4 open issues
What changed in this PR

This PR delivers kit v0.1.0: reusable agent rules, issue-to-worktree tooling, installation and update commands, and release automation. It also configures this repository to use its own kit.

Changes:

  • Adds the delivered rules, Git hooks, worktree helpers, and optional Claude target.
  • Implements manifest-based installation, updates, and integrity checks.
  • Adds CI, release packaging, pinned tools, and Bats coverage.
File Description
tests/​release.bats Tests archive layout and fresh installation.
tests/​kit.bats Tests installation, targets, updates, and checks.
tests/​hooks.bats Tests rebase protection and commit linting.
README.md Documents adoption, setup, and releases.
mise.toml Pins the development tools.
mise.lock Records tool versions and platform downloads.
justfile Adds the gate, refresh recipe, and kit module.
bin/​kit Implements installation, updates, targets, and validation.
AGENTS.md Establishes repository-specific kit rules.
.kit/​version Sets the initial release version.
.kit/​settings.json Defines the delivered deny rules.
.kit/​rules/​yaml.md Defines workflow conventions.
.kit/​rules/​markdown.md Defines Markdown conventions.
.kit/​rules/​just.md Defines justfile conventions.
.kit/​rules/​json.md Defines JSON and shared-settings conventions.
.kit/​rules/​gitignore.md Defines ignore-file conventions.
.kit/​rules/​bash.md Defines Bash conventions.
.kit/​manifest Records installed targets and release digests.
.kit/​kit.just Exposes issue, tidy, and status recipes.
.kit/​hooks/​pre-rebase Guards default-branch rebases.
.kit/​hooks/​pre-commit Lints staged paths with available tools.
.kit/​ci.yml Delivers gate, integrity, and issue-link checks.
.kit/​bash/​git.sh Implements worktree status and cleanup.
.kit/​bash/​gh.sh Implements the issue-to-worktree flow.
.kit/​bash/​1st.sh Provides shared helpers and module loading.
.kit/​1st.md Defines the agent workflow and rules index.
.gitignore Excludes per-issue worktrees.
.github/​workflows/​release.yml Validates tags and publishes release archives.
.github/​workflows/​kit-ci.yml Installs the kit’s CI workflow locally.
.claude/​settings.json Installs the Claude deny rules.
.claude/​rules/​kit-yaml.md Installs path-scoped YAML rules.
.claude/​rules/​kit-markdown.md Installs path-scoped Markdown rules.
.claude/​rules/​kit-just.md Installs path-scoped justfile rules.
.claude/​rules/​kit-json.md Installs path-scoped JSON rules.
.claude/​rules/​kit-gitignore.md Installs path-scoped ignore-file rules.
.claude/​rules/​kit-bash.md Installs path-scoped Bash rules.

Review effort: Balanced

Comment thread bash/gh.sh Outdated
Comment thread bash/git.sh Outdated
Comment thread tree/.github/workflows/kit-ci.yml Outdated
Comment thread hooks/pre-rebase Outdated
jeffrey-aguilera and others added 10 commits October 23, 2026 23:07
…memory

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
kit install copies .kit/1st.md and .kit/rules/ and writes the manifest; the bash and the hooks stay in the installed release, reached through kit issue, kit tidy, kit status, and core.hooksPath, which kit setup wires per clone. .kit/kit.just is gone; a root justfile adds veneers, as this one does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e line

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s a merge for json

The release root holds tree/, mirrored verbatim at a consumer's root; targets/<name>/, mirrored when the target is taken; and bash/, hooks/, and version, the machinery. bin/kit walks those directories and copies what it finds, a symlink as the file it points at, so the Claude mirrors are links into tree/.kit/rules/ and a new rule is one file and one link. A .json destination is merged by key with one jq definition. Notices live in the delivered files themselves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… status

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…and kit rebase replace the hooks

bin/kit resolves the release and execs bin/kit-<verb>.bash. The hooks are gone: kit lint is the body of a pre-commit hook, run by Lefthook through the delivered .kit/lefthook.yml, which a repository takes with extends; kit rebase refuses the default branch and otherwise rebases a worktree's branch onto origin/main and pushes it, and 1st.md names it as the spelling an agent uses, with git rebase added to the deny list. The human's desk is no longer bound by a hook. bash/kit.sh holds the layout and the merge rule the four install verbs share.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… it; one way into a bash module

Lefthook moves to mise.desk.toml, read when MISE_ENV=desk, which a desk exports and a runner never sets; the Mac-written lock had no Linux entry for it and the runner has no use for it. lockfile_platforms makes the lock carry the runner's platform from now on. The loader's autoload stubs go: a verb says use <module>, and that is the one mechanism.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hine's default

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… reads them; kit lint runs that hook

The fragment names which linter runs over which paths, a repository's lefthook.yml extends it, and kit lint is lefthook run pre-commit, over the staged paths or with --all-files over every tracked path, which is also the gate's spelling. Lefthook is back in mise.toml, since a runner runs the same linters; nothing is desk-only today, and the README says where a desk-only tool would go.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jeffrey-aguilera and others added 3 commits October 26, 2026 00:02
… git defaults to master

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es, tools called bare

As enchiridion's justfile has it: mise bin-paths goes first on PATH when the file is parsed, so every
recipe calls its tools bare and the pinned tool wins on a desk and on the runner alike, and a bash
body is a [script] recipe under set script-interpreter := ['bash'] rather than a shebang recipe. The
just rule says so.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ed error

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical risks to remote commits, root-checkout safety, and staged changes, alongside command, module, and linting defects, block approval.

3 open issues
3 resolved since last review

Review effort: Balanced

Comment thread justfile Outdated
Comment thread justfile Outdated
Comment thread docs/modules.md Outdated
jeffrey-aguilera and others added 13 commits October 26, 2026 01:23
Region placement grows up: a destination that is a symlink keeps its link and the write
lands in its target; a directory at a destination is refused by name; a region that opens
and never closes is refused rather than treated as absent; two modules delivering a region
to the same path are refused at render; markers follow the file's comment syntax by
extension, and a kind of file the kit cannot mark is refused. Removing a region leaves no
leading blank line.

kit lint takes a named path relative to the directory the command was typed in and reads
git's lists NUL-separated. kit gate writes the failed gate to the step summary and detects
Justfile and .justfile too. The markdown rule's linter reads a delivered
.kit/markdownlint.jsonc, so a fresh repository lints its delivered markdown clean, and this
repository's own .markdownlint.jsonc goes. The GitHub provider parses the pull request
number from the URL gh prints, and the kit-ci template refuses an unreleased manifest with
no bin/kit. The YAML reader accepts keys with digits, underscores, and dashes.

Tests shim gh and the step summary wherever the real ones could be reached, the vacuous
inverted greps become real ones, and new cases cover each refusal above. Headers, docs,
figures, the entry file, and the README say regions, settings, and the delivered check the
same way the code does, and every prose line fits 120 columns.

Closes nothing on its own; part of #5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
just refresh ran kit update over the working tree and staged each delivered file whole, so
a desk's unstaged text outside a region, or outside the kit's entries in a merged JSON file,
was staged with the kit's change. It also took the index's release as its own base, so an
entry the source dropped survived the merge.

Now the sources and the copies are exported from the index, kit update runs over the export
with HEAD's release as the base of every merge, the release the copies were last made from,
and the export is staged back through a temporary index. The working tree then takes the
same step, so a desk's copies agree with the index inside the kit's parts and its own text
stays where it was. Proven by hand: an unstaged line after the AGENTS.md region stays
unstaged, and a deny entry removed from the claude module's source leaves both the index
and the working tree.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The header comments on release.yml and retire-pre-releases.yml read as jargon. They now
say, in sentences a reader new to the repository can follow, what event runs each
workflow, what each step checks or produces, and why a release is never changed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A release tag is release/0.1.0 and a pre-release tag release/0.1.0-rc.1, so the version
tags have a namespace of their own, and the glob that starts the release workflow,
release/*, says what it is for. The part after release/ is the version: it is stamped
into the packed kit, names the tarball kit-<version>.tar.gz, and names the release. The
workflow, kit release verify, the fetch URL in release:fetch and in the delivered kit-ci
job, the tests, and the README follow. A mise pin adds version_prefix = "release/", which
is how mise maps a plain version onto the tag; curl, gh, and Homebrew need nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
kit release publish refuses a release tag anywhere but on the runner, naming the push that
starts the release workflow, so a desk cannot publish a release by hand. A desk may still
publish a pre-release, and kit release pack still makes the tarball anywhere.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The worktree that holds a pull request's branch was a line of its own after the issues it
closes. It now ends the pull request's row, after the checks, so the lines under a pull
request are the issues it closes and nothing else.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s regions are made that way

A comment line that is `:generate:<command>:until:"<pattern>"`, or `:while:"<pattern>"`, and
nothing else opens a region: the command runs from the repository root with the running kit's
bin/ first on PATH, and its output replaces the lines after the directive up to the first line
matching the pattern, which stays, or every line matching it. The command is one command and
its arguments, with no shell operator, so what makes a region is always a verb, a recipe, a
target, or a script in the tree. A directive inside a fenced code block is not live.

kit generate expands the named files once, or every tracked and untracked-but-not-ignored file
to a fixed point, at most twenty passes; --check writes nothing and names every stale file. A
file is rewritten only on a clean expansion: a failing command, a region that never closes, a
command outside its shape, a conflict marker, or an empty result leaves it as it was and fails
the run. kit check runs the check over the whole repository, and the lefthook fragment over the
staged paths.

The kit's regions are made that way. Install writes the directive naming kit region <path>,
the content, and the end marker; kit region prints what the active modules deliver at a path;
update regenerates a region rather than merging it; a region there with other content is
regenerated rather than refused; and remove takes the block out. This repository's own
AGENTS.md, .gitignore, and .claude/CLAUDE.md carry the new form, which the refresh wrote.

docs/generate.md is the page; the README, the architecture and modules pages, the entry file,
the module pages, and the figure say region the same way. Six cases in tests/generate.bats
cover the semantics and each refusal, and the region tests in tests/kit.bats follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… that nest, and bash command lines

A region opens on a line that is kit:generate <name>, optionally with a backticked command, framed by nothing
but punctuation, and closes on kit:end <name> framed the same way, so it is read without knowing the host's
comment syntax and a line of prose is never one. The command is a bash command line with pipes; with none
it is kit region, reading the name. Every unit is balanced on its own, the file and each command's output,
and an output's regions expand in turn, depth first. while: is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… by kit region reading the name

Install writes kit:generate <path> and kit:end <path> in the file's comment syntax, with no command, and kit
region with no argument reads the region's name from KIT_GENERATE_REGION. A home region still spells its
command, since the bare form cannot say --home. This repository's own three regions are rewritten by hand,
since the old markers are no longer read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ml as context

A module's regions/<base>/<path> is rendered as templates/<base>/ is, before delivery, so kit region prints the
rendered content: a top-level setting as @key@, a map's entry as @map.name@, and a bare @key@ meaning the entry
under answers:, the module's defaults beneath. The rendered directory keeps the modules/<module>/<kind>/<base>
shape so a rendered region still reads as one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gion as a template

Every mention of the old form becomes the new one, and no line of prose opens with the marker, since such a
line is a directive or an error.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…by its output, and the bash rule's then

From review: a markdown output with an odd number of fence lines wrote a file the next pass refused, so a
fence open at the end of any unit is now refused naming its line; a nested command's failure named a line of
the output as the file's, so it is named by the unit it is in; the duplicate-name refusal names the line of
the expanded file; and every fence toggle takes the rule's then on its own line.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jeffrey-aguilera and others added 10 commits October 27, 2026 11:29
… row, and timed tests

kit status takes --mine, --all, and --owner=<login>, given once or more; --all is the default.
Every choice but --mine shows the author before the state, 👤 and the login, in a column of its
own. The pull request document carries the author, which the GitHub provider reads in the one
request it makes, and --json carries it too, the pull requests chosen the same way.

The worktree on a pull request's row is named by its branch, the name the worktree's own block
showed, not by its path relative to the current directory.

just ci runs bats with --timing, so every test's time is in the log. #10 holds the numbers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
lychee is pinned and runs in just ci over the README, AGENTS.md, the docs, the modules, and
the installed copies: offline, so the gate stays deterministic, with fragments, so a section
link no heading matches is a red run. A link to a web page is excluded offline, and the
module pages point at GitHub on purpose, since a release ships without docs/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…kit check holds it

Where a format composes, the kit delivers a whole file under .kit/ and the repository's file
takes it with one include line. A module now says so under expects:, a path, an extended
regular expression some line must match, and a remedy. kit install and kit module add name
each unmet one as left to do, kit check refuses until it is met, and the kit writes nothing
into the file. The lefthook module is the first: it expects lefthook.yml to extend
.kit/lefthook.yml, which the README asked for and nothing checked.

The YAML subset gains a list of records, and a single-quoted value is taken literally, so a
pattern keeps its backslashes. The module schema grows the key. docs/modules.md states the
placement rule: an include where the format composes, a region where it has comments, a merge
by key where it has neither, a copy only where the file is the kit's own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e every provider runs

bin/forge-gitlab.sh answers every call of the seam over glab and GitLab's REST API, folded with
jq in the provider so its logic is the kit's and a test can drive it. It is picked by an origin
at gitlab.com, or by forge: gitlab for a self-hosted GitLab. A merge request is the change, its
iid the number, its head pipeline's jobs the checks, its resolvable discussions the review
threads; GitLab has no command that makes a branch for an issue, so link-branch makes
<iid>-<slug> from the default branch, the name GitLab's issue page would give it.

The seam grows two calls. noun prints the change's sigil and name, "#" and "pull request" or
"!" and "merge request", and pr-url its browser URL, so kit status draws #3 or !3 in otherwise
identical rows, and kit linked and kit issue name the change in the forge's words. kit linked's
remedy is now a Closes #N line in the description, which both forges honor, in place of
GitHub's Development panel.

tests/forge-conformance.bats is one world seen through each forge's API: a routed fake stands
in for gh and glab, applying gh's --jq so the GitHub provider's own filters are what is
tested, and every call of both providers is held to the same answer. What only a real GitLab
can prove, that its API answers as the fixtures do, is not proven here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… work items, and references that carry their tracker

An issue lives in a tracker, and the tracker is not always the forge. bin/tracker.sh is the
seam: a reference names its tracker by its prefix, #7 or a bare 7 for the forge's own issues
and AB#4521 for an Azure DevOps work item, and .kit/settings.yml declares every tracker but
the forge's own as a record, a prefix, a provider, and the provider's fields. Two providers:
tracker-forge.sh, which hands each call to the forge seam, and tracker-ado.sh over az boards,
closed in Closed, Done, and Removed, the caller being the identity az is signed in as.

kit issue takes references. Each issue is assigned as its tracker knows the caller; the
branch opens with the reference's key, 7-<slug> or ab4521-<slug>, made from the default
branch when the forge cannot link one; the start commit's subject opens with the reference;
and a primary the forge cannot link, like every secondary, gets a Closes <reference> line in
the change's body. kit linked accepts a body's closing keyword naming an open item of a
declared tracker beside the forge's own closing issues, and kit tidy reads a branch's issue
back from its key in any tracker.

tests/tracker.bats covers parsing, keys, body references, the ADO provider through a fake
az, kit linked on a work item, and the settings check. docs/trackers.md is the page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecked by patterns or Open Policy Agent

The toolchain seam, bin/toolchain.sh, asks every tool manager the same questions: install,
outdated, update, path, and run. mise reads mise.toml and its lock; brew reads a Brewfile and
moves only its formulae; docker builds an image from toolchain.Dockerfile, tagged by the file's
digest, runs a command in it with the checkout at /work as the caller's user, and pins its FROM
bases by digest. A repository declares its toolchains in .kit/settings.yml or the kit detects
them from those files. kit toolchain is the verb, kit gate runs the gate through the toolchain,
inside the image where docker is declared, and the delivered workflow installs with kit
toolchain install and appends kit toolchain path to the runner's PATH, its mise action kept only
to put the mise binary there. All three ran against the real tools on a desk.

The policy seam, bin/policy.sh, gathers one facts document, the branch, the commits past the
fork, the change and its title, and the patterns with the kit's defaults filled in, and an
engine names the refusals. patterns, the default, matches in bash; opa evaluates the Rego under
.kit/policy, and the policy-opa module delivers the same rules in Rego, so tests/policy.bats
holds both engines to identical refusals. kit policy is the verb, and kit ci runs it as its
fourth check; the workflow fetches full history for it. The defaults are the loop's names: a
branch <key>-<slug>, a commit subject opening with its issue's reference, a one-line title.
This repository turns the commit-subject rule off until #1 merges, since its early commits
predate the rule.

The forge seam grows pr-title. ere:quote in the loader escapes a pattern in bash, in place of
three sed escapes BSD sed refused. The YAML reader unquotes single-quoted scalars and map values
literally. opa is pinned for the suite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ths, and the forge asked first

The first line says what the fetch moved: the commits new on the default branch, the branches updated, new, and
pruned, and the tags new. Each checkout lists its uncommitted paths, with git's status, before its commits. The
forge's query starts in the background before the fetch, so the checkouts are drawn while it answers, and its
warnings are held for the pull requests' place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e documentation in plain English

A rule now carries templates, one per variant of the file it governs, under .kit/templates/<rule>/<variant>. kit
new file <path> makes a file from the most specific rule's template, the path taken from the repository root, its
holes filled from the path and from flags, and refuses a hole left empty or a file already there. kit lint holds
every governed file to the shape of one of its rule's templates, naming the line where a file leaves it; a
template, a module's region, and a path .gitattributes marks generated are held to none. The check found four
modules with shellcheck directives above their banner, a fixture without its closing rule, and two mise lock files,
now marked generated.

kit update needs the base release only to merge, by key or over an edited file, and finds every such path before
writing anything, so an update is whole or not at all. kit generate passes over templates.

The documentation, the comments, and the rules are rewritten in plain English; the code is unchanged but for kit's
usage text and one step message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ENTS.md

Every row of kit status separates its cells with " · ": the checkout's label from its counts, a pull request's
title, author, state, checks, comments, and branch. A pull request's state and its age share a cell, and a failed
checks cell reads "<failing> failing <passed> / <total> <took>". AGENTS.md opens with its heading, above the kit's
region.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment