pi-gauntlet 5.23.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v6.0.0 - 2026-09-30
4
+
5
+ ### Changed
6
+
7
+ - **Breaking:** `skills/forge-skill/` replaces the former skill-authoring skill: one 68-line rules-first `SKILL.md` (pi and Claude Code conventions table, eleven authoring rules, a five-step edit procedure, an optional two-worker A/B test) and no `reference/` files; the three reference documents (Anthropic best-practices copy, persuasion research, subagent testing recipes) are removed. Consumers invoking the old `/skill:` name switch to `/skill:forge-skill`; AGENTS core `v8` routes skill, persona, and prompt edits to it. `forge-skill` is exposed to Claude Code as the sixth marketplace skill (`/gauntlet:forge-skill`). `scripts/model-literal-lint.mjs` drops its only path exclusion (`EXCLUDED_PREFIXES = []`).
8
+
9
+ ## v5.23.1 - 2026-09-27
10
+
11
+ ### Changed
12
+
13
+ - `gauntlet-spec-index` prints a one-line stderr note (`<corpus> corpus has <n> documents - too small for the confidence rule; no rows returned`) when a query returns no rows on a corpus under 10 documents; stdout and exit code are unchanged. The brainstorming scout carries the reason as `Predecessor: none (specs corpus too small: <n> documents)` / `Docs touched: none (docs corpus too small: <n> documents)`.
14
+
3
15
  ## v5.23.0 - 2026-09-27
4
16
 
5
17
  ### Added
@@ -110,7 +122,7 @@
110
122
 
111
123
  ## v5.16.0 - 2026-09-20
112
124
 
113
- - Skills never name a provider or model: `subagent-driven-development` drops its `## Model Selection` tier table for a one-place `## Model` rule (a dispatch's `model:` is omitted, or carries a `gauntlet_setting` value, a user-named model, or the main loop's own string); `doc/configuration.md` gains `### Dispatch model precedence`; `scripts/model-literal-lint.mjs` bans provider/model literals in `skills/`, `agents/`, `extensions/` from `scripts/ci.mjs`. `writing-skills` widens its trigger to personas and prompt templates and adds `## Authoring rules` (imperative voice, low conditionality, minimal diff, oversized-skill extraction); AGENTS core `v6` makes it binding for every skill, persona, and prompt edit. `shape-ticket` and `conformance-check.md` name the main-loop-string provenance explicitly. (#42)
125
+ - Skills never name a provider or model: `subagent-driven-development` drops its `## Model Selection` tier table for a one-place `## Model` rule (a dispatch's `model:` is omitted, or carries a `gauntlet_setting` value, a user-named model, or the main loop's own string); `doc/configuration.md` gains `### Dispatch model precedence`; `scripts/model-literal-lint.mjs` bans provider/model literals in `skills/`, `agents/`, `extensions/` from `scripts/ci.mjs`. `forge-skill` widens its trigger to personas and prompt templates and adds `## Authoring rules` (imperative voice, low conditionality, minimal diff, oversized-skill extraction); AGENTS core `v6` makes it binding for every skill, persona, and prompt edit. `shape-ticket` and `conformance-check.md` name the main-loop-string provenance explicitly. (#42)
114
126
 
115
127
  ## v5.15.0 - 2026-09-20
116
128
 
@@ -328,7 +340,7 @@
328
340
 
329
341
  ## v5.0.0 - 2026-08-23
330
342
 
331
- - **Breaking:** `skills/systematic-debugging/` removed (7 files) with active references cleaned (`dispatching-parallel-agents`, `writing-skills`, README); historical `doc/specs/**` and CHANGELOG references retained. Consumers referencing `/skill:systematic-debugging` must migrate to `/skill:chase-bug` for triage.
343
+ - **Breaking:** `skills/systematic-debugging/` removed (7 files) with active references cleaned (`dispatching-parallel-agents`, `forge-skill`, README); historical `doc/specs/**` and CHANGELOG references retained. Consumers referencing `/skill:systematic-debugging` must migrate to `/skill:chase-bug` for triage.
332
344
  - New skill `chase-bug` (explicit-invocation-only): human-driven bug triage - origin intake, prior-report search (open + closed, own resolution ladder), three-phase read-only root-cause discovery with a run-falsification evidence bar, an evidenced verdict menu (real bug: file ticket / brainstorm now / respond-only, exactly one `[recommended]`; five negative verdicts with named citation sources), then a gated response-to-origin (exact `send it` confirmation). Baseline-relative zero-mutation invariant (`git status --porcelain --untracked-files=no` at invocation / pre-menu / end); origin text fenced as untrusted data; exactly two human gates. Exposed via the Claude Code marketplace (4-skill allowlist).
333
345
  - AGENTS.md gold rule: agent-initiated writes to human-readable channels are gated on exact-text confirmation; obra coverage 12-of-14 -> 11-of-14 (total stays 16).
334
346
 
@@ -710,7 +722,7 @@ is deduplicated, and this repo now dogfoods its own workflow (GitHub #1 + folded
710
722
  - **Part D - self-hosting mandate.** `AGENTS.md` mandates the full gauntlet for this
711
723
  repo's own non-trivial changes and reframes the direct-edit recipes as implement-phase
712
724
  mechanics.
713
- - **Part E - file-placement convention.** `writing-skills` codifies dispatch-payload
725
+ - **Part E - file-placement convention.** `forge-skill` codifies dispatch-payload
714
726
  templates as `SKILL.md` siblings vs deep guidance in `reference/` (destination, not format).
715
727
 
716
728
  ## v4.1.0 - 2026-07-02
@@ -1012,7 +1024,7 @@ Root cause: `plan_tracker` is execution-only by intent, but nothing enforced it.
1012
1024
  - **writing-plans:** drop `plan-document-reviewer-prompt.md` subagent dispatch from the Self-Review step. Align with obra v5.0.6 — inline self-review only. The dispatch added ~25 min/run with no measured quality gain and contradicted the repo's no-belt-and-suspenders rule.
1013
1025
  - **using-git-worktrees:** port obra v5.1.0 Step 0 improvements — robust path resolution via `cd && pwd -P` wrapping, submodule guard via `git rev-parse --show-superproject-working-tree` (replaces fragile `.git is a file` heuristic), branch-state reporting after detection.
1014
1026
  - **using-git-worktrees:** port obra v5.1.0 Step 1a improvements — explicit native-tool name anchors (`EnterWorktree`, `WorktreeCreate`, `/worktree`, `--worktree`). Upstream TDD showed compliance jumped from 2/6 to 50/50 with explicit names.
1015
- - **writing-skills:** fix dead reference to `examples/CLAUDE_MD_TESTING.md` in `reference/testing-skills-with-subagents.md`. File was never ported; replaced with pointer to upstream `obra/superpowers` repo.
1027
+ - **forge-skill:** fix dead reference to `examples/CLAUDE_MD_TESTING.md` in `reference/testing-skills-with-subagents.md`. File was never ported; replaced with pointer to upstream `obra/superpowers` repo.
1016
1028
  - **README:** fix broken link — `mariozechner/pi` → `badlogic/pi-mono` (was 404).
1017
1029
  - **AGENTS.md:** improve verification grep example — replace meaningless `specific.company.name` placeholder with realistic patterns (`jjuraszek`, `/Users/[^/]+`, `<your-org-name>`).
1018
1030
  - **extensions:** add `phase-tracker.ts` — tracks workflow phase (brainstorm → plan → implement → verify → ship) with a TUI widget. Session-state only, no disk persistence.
@@ -1023,7 +1035,7 @@ Root cause: `plan_tracker` is execution-only by intent, but nothing enforced it.
1023
1035
  - **Agents:** add `thinking`, `defaultContext`, `inheritSkills` frontmatter to all three personas. Previously these knobs were documented in `AGENTS.md` but missing from the agent files, so dispatch fell back to pi-cohort defaults (typically `thinking: high`, no defaultContext override). Now: reviewers use `thinking: high` + `defaultContext: fresh`; implementer uses `thinking: medium` + `defaultContext: fork`. All three use `inheritSkills: false` to prevent recursive skill discovery in dispatched children.
1024
1036
  - **AGENTS.md:** correct false claim that `plan-tracker.ts` accepts settings — it has no configurable knobs. Tighten the package-conventions section. Document the divergence from `obra/superpowers` v5.1.0 (they dropped `agents/`, we keep them as pi-cohort profiles) and explain why `using-superpowers` is intentionally absent.
1025
1037
  - **README.md:** rewrite for human readability, expand the project-overrides section with a concrete example, fix the version pin from `v0.1.0` to `v0.1.1`.
1026
- - **skills/writing-skills/SKILL.md:** drop project-specific references; broaden the "Where Skills Live in Pi" table to include the package-distributed path; update extension references to reflect package distribution.
1038
+ - **skills/forge-skill/SKILL.md:** drop project-specific references; broaden the "Where Skills Live in Pi" table to include the package-distributed path; update extension references to reflect package distribution.
1027
1039
 
1028
1040
  ## v0.1.1 — 2026-05-28
1029
1041
 
package/README.md CHANGED
@@ -69,7 +69,7 @@ Everything between gate 1 and gate 2 - task breakdown, implementation, both revi
69
69
 
70
70
  pi-gauntlet ships three kinds of pieces, layered on top of pi-cohort's dispatch:
71
71
 
72
- - **20 skills** - the workflow logic. Thirteen activate automatically when pi sees the matching kind of task, and each one gates the next: `brainstorming`, `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `writing-skills`, `linear` (reads/searches/comments on/manages Linear tickets via the `linearis` CLI; owns all linearis mechanics and the `## Issue tracker` overrides schema; tracker-facing skills route to it). Seven more are explicit-invocation-only (`disable-model-invocation: true`): `shape-ticket` creates or repairs one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write - run it with `/skill:shape-ticket`. `gatekeep-pr` is consent-gated pre-merge verification of a PR against its issue - read-only gathering, verification evidence resolved CI-first (green checks on the exact assessed head count as evidence; the project's verification command runs only as fallback), a rubric-based review, then a deterministic authorship-aware menu with stable finding IDs (P#/L#/C#/F#) and numbered pre-composed courses (fixes execute as a single parallel-safe wave: one gate run, one re-review, one push); nothing mutates (fixes, pushes, reviews, merges) until you pick a row - run it with `/skill:gatekeep-pr <pr>`. `check-delivery` is a post-merge detective control: proves an issue actually shipped (default-branch landing, delivery target, per-AC evidence) before its tracker status advances; it never writes a terminal status - run it with `/skill:check-delivery <ref>`. `chase-bug` is human-only bug triage: read-only root-cause discovery to an evidenced verdict menu (real bug -> ticket/brainstorm/hotfix/respond; five negative verdicts), then a gated response to the reporter for addressable origins (GitHub issue / tracker ticket) and a rendered verdict summary otherwise - it never fixes during triage; the hotfix row hands off to `skills/chase-bug/hotfix.md` after the menu - run it with `/skill:chase-bug`. `gauntlet-performance` reads the committed run telemetry (current repo by default, `--dir <path>` adds others) through the parse-only `gauntlet-performance` CLI and answers with one example-led recommendation, 3-5 cornerstone numbers, a council assessment when available, and a menu of at most three actions (render a report file, open the recommendation as a ticket via `shape-ticket`, drill into one run); it writes nothing unless you pick render - run it with `/skill:gauntlet-performance [--dir <path>]... [--since <version>]`. `gauntlet-handoff` ends a session whose flow a fresh session will continue: it invokes pi-cohort's `handoff` skill (`--out <path>` or `--key <name>`, default key = the run worktree's branch with `/` flattened to `-`, default file `<tmpdir>/pi-handoff/<key>.md`), then appends the gauntlet process-state section (phase/plan tracker status) per the shared contract `skills/gauntlet-resume/reference/brief-contract.md` - run it with `/skill:gauntlet-handoff [--out <path> | --key <name>]`. `gauntlet-resume` is the only way back into an interrupted flow from a fresh session: it takes a `gauntlet-handoff` brief (a file path, pasted text, or - with no arguments - a pick from the briefs under `<tmpdir>/pi-handoff/`), a bare worktree that already holds a spec, or a spec tracked on main under a configured spec dir (a spec seed: it creates or reuses the worktree through `using-git-worktrees` and pins the spec), restores phase/plan tracker state through the legal arming sequence (`start brainstorm`, `skip` with `resume:` reasons, `plan_check` before implement-or-later), and never infers approval from artifacts - run it with `/skill:gauntlet-resume [<brief-file> | <spec>.md | <worktree-name-or-path>]`.
72
+ - **20 skills** - the workflow logic. Thirteen activate automatically when pi sees the matching kind of task, and each one gates the next: `brainstorming`, `writing-plans`, `roasting-the-spec`, `test-driven-development`, `subagent-driven-development`, `dispatching-parallel-agents`, `verification-before-completion`, `requesting-code-review`, `receiving-code-review`, `using-git-worktrees`, `finishing-a-development-branch`, `forge-skill`, `linear` (reads/searches/comments on/manages Linear tickets via the `linearis` CLI; owns all linearis mechanics and the `## Issue tracker` overrides schema; tracker-facing skills route to it). Seven more are explicit-invocation-only (`disable-model-invocation: true`): `shape-ticket` creates or repairs one tracker issue per run against a Context/Problem/Idea/Acceptance-Criteria template, gated by an AC integrity check, a cheap council roast, and a single human-confirmed write - run it with `/skill:shape-ticket`. `gatekeep-pr` is consent-gated pre-merge verification of a PR against its issue - read-only gathering, verification evidence resolved CI-first (green checks on the exact assessed head count as evidence; the project's verification command runs only as fallback), a rubric-based review, then a deterministic authorship-aware menu with stable finding IDs (P#/L#/C#/F#) and numbered pre-composed courses (fixes execute as a single parallel-safe wave: one gate run, one re-review, one push); nothing mutates (fixes, pushes, reviews, merges) until you pick a row - run it with `/skill:gatekeep-pr <pr>`. `check-delivery` is a post-merge detective control: proves an issue actually shipped (default-branch landing, delivery target, per-AC evidence) before its tracker status advances; it never writes a terminal status - run it with `/skill:check-delivery <ref>`. `chase-bug` is human-only bug triage: read-only root-cause discovery to an evidenced verdict menu (real bug -> ticket/brainstorm/hotfix/respond; five negative verdicts), then a gated response to the reporter for addressable origins (GitHub issue / tracker ticket) and a rendered verdict summary otherwise - it never fixes during triage; the hotfix row hands off to `skills/chase-bug/hotfix.md` after the menu - run it with `/skill:chase-bug`. `gauntlet-performance` reads the committed run telemetry (current repo by default, `--dir <path>` adds others) through the parse-only `gauntlet-performance` CLI and answers with one example-led recommendation, 3-5 cornerstone numbers, a council assessment when available, and a menu of at most three actions (render a report file, open the recommendation as a ticket via `shape-ticket`, drill into one run); it writes nothing unless you pick render - run it with `/skill:gauntlet-performance [--dir <path>]... [--since <version>]`. `gauntlet-handoff` ends a session whose flow a fresh session will continue: it invokes pi-cohort's `handoff` skill (`--out <path>` or `--key <name>`, default key = the run worktree's branch with `/` flattened to `-`, default file `<tmpdir>/pi-handoff/<key>.md`), then appends the gauntlet process-state section (phase/plan tracker status) per the shared contract `skills/gauntlet-resume/reference/brief-contract.md` - run it with `/skill:gauntlet-handoff [--out <path> | --key <name>]`. `gauntlet-resume` is the only way back into an interrupted flow from a fresh session: it takes a `gauntlet-handoff` brief (a file path, pasted text, or - with no arguments - a pick from the briefs under `<tmpdir>/pi-handoff/`), a bare worktree that already holds a spec, or a spec tracked on main under a configured spec dir (a spec seed: it creates or reuses the worktree through `using-git-worktrees` and pins the spec), restores phase/plan tracker state through the legal arming sequence (`start brainstorm`, `skip` with `resume:` reasons, `plan_check` before implement-or-later), and never infers approval from artifacts - run it with `/skill:gauntlet-resume [<brief-file> | <spec>.md | <worktree-name-or-path>]`.
73
73
  - **7 subagent personas** - the specialized child agents the skills dispatch via pi-cohort: `implementer`, `code-reviewer`, `spec-reviewer`, `conformance-reviewer`, `spec-summarizer`, `spec-council-member`, `spec-council-synthesizer`. See [doc/personas.md](./doc/personas.md) for what each one does and why its permissions are scoped the way they are.
74
74
  - **4 runtime extensions** - the enforcement layer. `plan-tracker` and `phase-tracker` are tools skills call to track progress (with a TUI widget); `verify-before-ship` is a hook that warns if you push or open a PR without a passing test run since your last edit; a phase-tracker flow guard reminds on implement-phase commits missing spec/code review. In a brainstorming-entered flow, phase-tracker rejects `implement` or `verify` completion while tracker tasks remain pending or in progress; see [its configuration reference](./doc/configuration.md#phase-tracker). phase-tracker also registers `plan_check`, which verifies a plan against its spec and against the grammar in [skills/writing-plans/reference/plan-contract.md](./skills/writing-plans/reference/plan-contract.md), including that each task's `Tests:` commands are selective and never the full suite; a pass stamps the plan for implementation. `telemetry` records one YAML record per gauntlet run (phase timing, models, personas, gate/fix rounds, diff at ship) that ships in the squash beside the spec - it is written only inside a brainstorming-entered flow, kept untracked and git-excluded until `finishing-a-development-branch` seals and commits it once with `gauntlet-telemetry-seal` - and blocks a brainstorm `write` into an already-shipped spec; see [its configuration reference](./doc/configuration.md#telemetry). See [doc/configuration.md](./doc/configuration.md) for the settings each one reads.
75
75
 
@@ -113,7 +113,7 @@ Run by a human against pi-cohort >= 7.1.0, before a pi-gauntlet release claims t
113
113
  ## Requirements
114
114
 
115
115
  - [pi-coding-agent](https://github.com/earendil-works/pi) ≥ 0.85.1 - tested minimum for sequential `phase_tracker` tool execution, which persists a tracker update before a later tool call in the same model message.
116
- - [pi-cohort](https://github.com/jjuraszek/pi-cohort) ≥ 1.4.5 - required peer package. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `writing-skills`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics.
116
+ - [pi-cohort](https://github.com/jjuraszek/pi-cohort) ≥ 1.4.5 - required peer package. Skills that dispatch agents (`requesting-code-review`, `subagent-driven-development`, `dispatching-parallel-agents`, `writing-plans`, `forge-skill`, `shape-ticket`, `roasting-the-spec`) call `subagent({})`, which pi-cohort provides. pi-gauntlet does not vendor the dispatch tool; without pi-cohort those skills have nothing to call. Gauntlet execution dispatches explicitly use `async: false`; do not enable pi-cohort's incompatible `forceTopLevelAsync` setting. See pi-cohort's [dispatch configuration](https://github.com/jjuraszek/pi-cohort/blob/main/doc/configuration.md) for that setting's owner and semantics.
117
117
 
118
118
  Both packages must be listed in your `.pi/settings.json#packages` array (pi adds them automatically when you `pi install`). pi-gauntlet and pi-cohort are versioned independently but release together whenever dispatch semantics change - pin compatible versions of both.
119
119
 
@@ -137,7 +137,7 @@ Pin an exact release with `npm:pi-gauntlet@X.Y.Z`. See [doc/install-internals.md
137
137
 
138
138
  ## Spec search index
139
139
 
140
- `gauntlet-spec-index` provides lexical search over two corpora in one per-worktree cache. From a repository worktree, run `node <pi-gauntlet-package>/bin/gauntlet-spec-index.mjs --query "<text>" [--corpus specs|docs] [--limit N] [--exclude <repo-relative path>]...`; it requires Node >=24.15.0 and refreshes its FTS5 index on every query. `--corpus specs` (the default) searches `doc/specs/*.md` at the repository root and one service level down and prints tab-separated `score`, `path`, `service`, `title`, `status`, `shipped_at`, `state`, `files`, and `snippet` columns. `--corpus docs` searches project documentation and prints `score`, `path`, `title`, and `snippet`; the snippet is the matched `##`/`###` heading line when one holds a query term, else a body fragment. Docs candidates are the tracked and untracked, non-gitignored `*.md` files (`git ls-files -co --exclude-standard`, so nested repositories and submodules are not indexed) matching an include list - default `**/doc/**/*.md`, `**/docs/**/*.md`, `README.md`, `AGENTS.md`, where a slash-free entry also matches one service level down - minus the fixed exclusions `**/doc/specs/**`, `**/docs/specs/**`, `**/doc/plans/**`, `**/docs/plans/**`, `**/node_modules/**`, `.pi/gauntlet/**`, `.worktrees/**`, symlinks, and files whose first line is the context-draft marker; `**` never matches a dot directory, so `.pi/*.md` or `.github/*.md` is indexed only through an explicit include. A project replaces the include list with `- docs: <glob>` bullets under a `## Spec index` heading in its gauntlet overrides file (see [Project-specific overrides](#project-specific-overrides)); the exclusions are not configurable. Both corpora pass the same confidence rule before `--limit` applies: a query term is evidence when it occurs in fewer than half the rows of the queried table (stem variants of one word count once); query tokens split on word boundaries, so `gauntlet-spec-index` searches as three words; a row is returned only when it matches at least two distinct evidence terms in the corpus's strong fields - `title` or `goal` for specs, `title` or `headings` for docs - and rows scoring below half the best live row's score are dropped (every docs row is live). A query with no real match therefore prints the header only and exits 0, and a corpus of two or fewer files never returns rows. `--exclude` (repeatable) removes a path before the rule runs, so the spec under review never sets the bar. `state` is `superseded` when a `> **Superseded by:** ... - fully` banner sits in the block directly under the title and `live` otherwise; named-section banners and consumer-defined banner syntaxes both read as `live`. Live rows sort before superseded rows, then by score. The `files` column is a `;`-separated list of repo-relative paths the spec's shipped change modified and that still exist in the repository, the literal `missing` when the spec's telemetry record has no `derived.modified_files` list, or blank when there is no readable record or no recorded path remains. The cache lives at `.pi/gauntlet/index.sqlite`, and its first creation adds `/.pi/gauntlet/index.sqlite*` to Git's `info/exclude` so the database and SQLite sidecars stay out of `git status`. `/skill:brainstorming` queries the specs corpus twice - the scout at gather time from the request, and the main loop at spec-writing from the finished spec's title, goal, and headings, surfacing new `live` candidates at the review gate - and the scout queries the docs corpus once to render `Docs touched:` lines that round 2 weighs as documentation-impact candidates.
140
+ `gauntlet-spec-index` provides lexical search over two corpora in one per-worktree cache. From a repository worktree, run `node <pi-gauntlet-package>/bin/gauntlet-spec-index.mjs --query "<text>" [--corpus specs|docs] [--limit N] [--exclude <repo-relative path>]...`; it requires Node >=24.15.0 and refreshes its FTS5 index on every query. `--corpus specs` (the default) searches `doc/specs/*.md` at the repository root and one service level down and prints tab-separated `score`, `path`, `service`, `title`, `status`, `shipped_at`, `state`, `files`, and `snippet` columns. `--corpus docs` searches project documentation and prints `score`, `path`, `title`, and `snippet`; the snippet is the matched `##`/`###` heading line when one holds a query term, else a body fragment. Docs candidates are the tracked and untracked, non-gitignored `*.md` files (`git ls-files -co --exclude-standard`, so nested repositories and submodules are not indexed) matching an include list - default `**/doc/**/*.md`, `**/docs/**/*.md`, `README.md`, `AGENTS.md`, where a slash-free entry also matches one service level down - minus the fixed exclusions `**/doc/specs/**`, `**/docs/specs/**`, `**/doc/plans/**`, `**/docs/plans/**`, `**/node_modules/**`, `.pi/gauntlet/**`, `.worktrees/**`, symlinks, and files whose first line is the context-draft marker; `**` never matches a dot directory, so `.pi/*.md` or `.github/*.md` is indexed only through an explicit include. A project replaces the include list with `- docs: <glob>` bullets under a `## Spec index` heading in its gauntlet overrides file (see [Project-specific overrides](#project-specific-overrides)); the exclusions are not configurable. Both corpora pass the same confidence rule before `--limit` applies: a query term is evidence when it occurs in fewer than half the rows of the queried table (stem variants of one word count once); query tokens split on word boundaries, so `gauntlet-spec-index` searches as three words; a row is returned only when it matches at least two distinct evidence terms in the corpus's strong fields - `title` or `goal` for specs, `title` or `headings` for docs - and rows scoring below half the best live row's score are dropped (every docs row is live). A query with no real match therefore prints the header only and exits 0 - and when the queried corpus has fewer than 10 documents, also writes one stderr line naming the corpus and its size (`gauntlet-spec-index: docs corpus has 5 documents - too small for the confidence rule; no rows returned`), so a small corpus and a genuine miss stay distinguishable; a corpus of two or fewer files never returns rows. `--exclude` (repeatable) removes a path before the rule runs, so the spec under review never sets the bar. `state` is `superseded` when a `> **Superseded by:** ... - fully` banner sits in the block directly under the title and `live` otherwise; named-section banners and consumer-defined banner syntaxes both read as `live`. Live rows sort before superseded rows, then by score. The `files` column is a `;`-separated list of repo-relative paths the spec's shipped change modified and that still exist in the repository, the literal `missing` when the spec's telemetry record has no `derived.modified_files` list, or blank when there is no readable record or no recorded path remains. The cache lives at `.pi/gauntlet/index.sqlite`, and its first creation adds `/.pi/gauntlet/index.sqlite*` to Git's `info/exclude` so the database and SQLite sidecars stay out of `git status`. `/skill:brainstorming` queries the specs corpus twice - the scout at gather time from the request, and the main loop at spec-writing from the finished spec's title, goal, and headings, surfacing new `live` candidates at the review gate - and the scout queries the docs corpus once to render `Docs touched:` lines that round 2 weighs as documentation-impact candidates.
141
141
 
142
142
  ## Performance digest
143
143
 
@@ -154,20 +154,27 @@ pi install -l ~/repos/pi-gauntlet
154
154
 
155
155
  ## Use from Claude Code
156
156
 
157
- Five skills are exposed to Claude Code via the plugin marketplace at
157
+ Six skills are exposed to Claude Code via the plugin marketplace at
158
158
  `.claude-plugin/marketplace.json`: **shape-ticket**, **gatekeep-pr**,
159
- **check-delivery**, **chase-bug**, and **linear**. They are harness-portable by design - every pi-specific
160
- mechanic they touch (`plan_tracker`, `gauntlet_setting`, `subagent()`) carries
161
- an inline fallback, so they run on Claude Code's native facilities. This is the
162
- supported set. Not exposed, in two classes: (a) genuinely pi-bound surface -
163
- the full gated pipeline (brainstorming -> writing-plans ->
164
- subagent-driven-development -> verify -> finish), the spec council, the
165
- conformance gate, flow guards, verify-before-ship, and all `piGauntlet.*`
166
- settings, which depend on pi extensions; (b) runtime-neutral skills
167
- (e.g. `receiving-code-review`, `using-git-worktrees`) that
168
- are simply out of scope for this channel, not incompatible - re-adding one is a
169
- one-line allowlist append. For Claude-Code-native equivalents of the
170
- methodology skills, see [obra/superpowers](https://github.com/obra/superpowers).
159
+ **check-delivery**, **chase-bug**, **linear**, and **forge-skill**. They are
160
+ harness-portable by design - every pi-specific mechanic they touch
161
+ (`plan_tracker`, `gauntlet_setting`, `subagent()`) carries an inline fallback,
162
+ so they run on Claude Code's native facilities. `forge-skill` is the authoring
163
+ skill for skills, personas, prompt templates, and slash commands under
164
+ gauntlet's conventions (imperative voice, minimal diff, low conditionality,
165
+ extraction over 500 lines); Claude Code namespaces plugin skills, so it lists
166
+ as `/gauntlet:forge-skill` and coexists with the skill-authoring skill of
167
+ [obra/superpowers](https://github.com/obra/superpowers) - invoke the one
168
+ whose conventions the repo follows. This is the supported set. Not exposed,
169
+ in two classes: (a) genuinely pi-bound surface - the full gated pipeline
170
+ (brainstorming -> writing-plans -> subagent-driven-development -> verify ->
171
+ finish), the spec council, the conformance gate, flow guards,
172
+ verify-before-ship, and all `piGauntlet.*` settings, which depend on pi
173
+ extensions; (b) runtime-neutral skills (e.g. `receiving-code-review`,
174
+ `using-git-worktrees`) that are simply out of scope for this channel, not
175
+ incompatible - re-adding one is a one-line allowlist append. For
176
+ Claude-Code-native equivalents of the methodology skills, see
177
+ [obra/superpowers](https://github.com/obra/superpowers).
171
178
 
172
179
  ### Setup
173
180
 
@@ -230,9 +237,9 @@ exact repo folder* in interactive Claude Code. Trusting a parent folder,
230
237
  4. Run `/plugin` and confirm: marketplace `pi-gauntlet` is listed, plugin
231
238
  `gauntlet` is enabled. If it shows as known but not installed, run
232
239
  `/plugin install gauntlet@pi-gauntlet` and re-check.
233
- 5. Confirm exactly five skills are registered under the plugin (via the
240
+ 5. Confirm exactly six skills are registered under the plugin (via the
234
241
  `/plugin` details view): shape-ticket, gatekeep-pr, check-delivery, chase-bug,
235
- linear.
242
+ linear, forge-skill.
236
243
  6. Invoke `/gauntlet:shape-ticket` with a deliberately two-concern ask (e.g.
237
244
  "shape a ticket: CSV import for operators, plus a partner-facing status
238
245
  API") so the skill deterministically consults its
@@ -10,6 +10,8 @@ const SCHEMA_VERSION = 3;
10
10
  const EVIDENCE_DF_FRACTION = 0.5;
11
11
  const MIN_EVIDENCE_TOKENS = 2;
12
12
  const SCORE_RATIO = 0.5;
13
+ // Policy floor, not derived from the rule: below it an empty result says little about coverage.
14
+ const MIN_CORPUS = 10;
13
15
  // Default banner grammar of skills/brainstorming/reference/superseding.md; a named-section
14
16
  // scope or anything after `- fully` keeps the spec live.
15
17
  const FULLY_BANNER = /^> \*\*Superseded by:\*\* \[.*\]\(.*\) - fully$/;
@@ -307,7 +309,7 @@ function query(db, corpus, toks, { limit, exclude }) {
307
309
  if (all.size === 0 || all.size >= n * EVIDENCE_DF_FRACTION) continue;
308
310
  evidence.push({ all, strong: rowids(corpus.strong.map((f) => `${f}:${quote(tok)}`).join(" OR ")) });
309
311
  }
310
- if (evidence.length < MIN_EVIDENCE_TOKENS) return [];
312
+ if (evidence.length < MIN_EVIDENCE_TOKENS) return { rows: [], n };
311
313
  const rows = db.prepare(
312
314
  `SELECT rowid, ${corpus.select}, bm25(${table}, ${corpus.weights.map((w) => w.toFixed(1)).join(", ")}) AS score
313
315
  FROM ${table} WHERE ${table} MATCH ?`,
@@ -317,7 +319,7 @@ function query(db, corpus, toks, { limit, exclude }) {
317
319
  kept.sort((a, b) => Number(!live(a)) - Number(!live(b)) || a.score - b.score);
318
320
  const best = kept.find(live);
319
321
  const cut = best ? kept.filter((r) => Math.abs(r.score) >= SCORE_RATIO * Math.abs(best.score)) : kept;
320
- return cut.slice(0, limit);
322
+ return { rows: cut.slice(0, limit), n };
321
323
  }
322
324
 
323
325
  function telemetry(root, specPath) {
@@ -394,10 +396,13 @@ async function main() {
394
396
  const corpus = CORPORA[name];
395
397
  const db = await openDb(root);
396
398
  refresh(db, root, corpus);
397
- const rows = query(db, corpus, toks, { limit, exclude });
399
+ const { rows, n } = query(db, corpus, toks, { limit, exclude });
398
400
  const out = [corpus.header.join("\t")];
399
401
  for (const r of rows) out.push(corpus.format(root, r).join("\t"));
400
402
  process.stdout.write(out.join("\n") + "\n");
403
+ if (rows.length === 0 && n < MIN_CORPUS) {
404
+ process.stderr.write(`gauntlet-spec-index: ${name} corpus has ${n} documents - too small for the confidence rule; no rows returned\n`);
405
+ }
401
406
  db.close();
402
407
  }
403
408
 
@@ -43,6 +43,16 @@ const commit = (root) => {
43
43
  };
44
44
  // Fillers keep the corpus large enough that two-document probe terms stay below N/2.
45
45
  const filler = (n) => `# Filler ${n}\n\n**Goal:** filler goal ${n}.\n\n## Design\n\nnothing of note here\n`;
46
+ const boundaryRepo = (fillers) => {
47
+ const root = gitRepo();
48
+ const common = (n) => `# Plain spec ${n}\n\n**Goal:** plain goal ${n}.\n\nThe body mentions the common shared vocabulary.\n`;
49
+ write(root, "doc/specs/strong.md", "# Strong heron ibis\n\n**Goal:** heron ibis jackal.\n\nbody\n");
50
+ for (let n = 1; n <= 5; n++) write(root, `doc/specs/p${n}.md`, common(n));
51
+ for (const f of fillers) write(root, `doc/specs/filler-${f}.md`, filler(f));
52
+ commit(root);
53
+ return root;
54
+ };
55
+ const NOTE = "too small for the confidence rule; no rows returned";
46
56
 
47
57
  const repo = () => {
48
58
  const root = gitRepo();
@@ -357,6 +367,50 @@ test("16: punctuation splits into distinct evidence terms and query order does n
357
367
  assert.deepEqual(paths(run(root, ["--query", "config config-file"])), paths(run(root, ["--query", "config-file config"])));
358
368
  });
359
369
 
370
+ test("21: three-spec corpus, no match - header only plus the small-corpus note", (t) => {
371
+ const root = gitRepo();
372
+ t.after(() => rmSync(root, { recursive: true, force: true }));
373
+ for (const n of ["one", "two", "three"]) write(root, `doc/specs/filler-${n}.md`, filler(n));
374
+ commit(root);
375
+ const r = run(root, ["--query", "yeti unicorn"]);
376
+ assert.equal(r.status, 0, r.stderr);
377
+ assert.deepEqual(r.header, HEADER);
378
+ assert.deepEqual(r.rows, []);
379
+ assert.equal(r.stderr, `gauntlet-spec-index: specs corpus has 3 documents - ${NOTE}\n`);
380
+ });
381
+
382
+ test("22: MIN_CORPUS boundary - note at N = 9, silent at N = 10, rows unaffected", (t) => {
383
+ const root = boundaryRepo(["one", "two", "three"]);
384
+ t.after(() => rmSync(root, { recursive: true, force: true }));
385
+ const nine = run(root, ["--query", "yeti unicorn"]);
386
+ assert.equal(nine.status, 0, nine.stderr);
387
+ assert.deepEqual(nine.rows, []);
388
+ assert.equal(nine.stderr, `gauntlet-spec-index: specs corpus has 9 documents - ${NOTE}\n`);
389
+ write(root, "doc/specs/filler-four.md", filler("four"));
390
+ commit(root);
391
+ for (const q of ["yeti unicorn", "common shared"]) {
392
+ const r = run(root, ["--query", q]);
393
+ assert.equal(r.status, 0, r.stderr);
394
+ assert.deepEqual(r.header, HEADER);
395
+ assert.deepEqual(r.rows, []);
396
+ assert.equal(r.stderr, "");
397
+ }
398
+ const strong = run(root, ["--query", "heron ibis jackal common", "--limit", "10"]);
399
+ assert.equal(strong.status, 0, strong.stderr);
400
+ assert.deepEqual(paths(strong), ["doc/specs/strong.md"]);
401
+ assert.equal(strong.stderr, "");
402
+ });
403
+
404
+ test("23: n is the table count before --exclude", (t) => {
405
+ const root = boundaryRepo(["one", "two", "three", "four"]);
406
+ t.after(() => rmSync(root, { recursive: true, force: true }));
407
+ const r = run(root, ["--query", "yeti unicorn", "--exclude", "doc/specs/p1.md"]);
408
+ assert.equal(r.status, 0, r.stderr);
409
+ assert.deepEqual(r.header, HEADER);
410
+ assert.deepEqual(r.rows, []);
411
+ assert.equal(r.stderr, "");
412
+ });
413
+
360
414
  test("D1: --corpus specs and omitted --corpus are byte-identical; nine columns", (t) => {
361
415
  const root = repo();
362
416
  t.after(() => rmSync(root, { recursive: true, force: true }));
@@ -420,6 +474,7 @@ test("D2: docs include defaults and fixed exclusions", (t) => {
420
474
  assert.equal(tiny.status, 0, tiny.stderr);
421
475
  assert.deepEqual(tiny.header, DOCS_HEADER);
422
476
  assert.deepEqual(tiny.rows, []);
477
+ assert.equal(tiny.stderr, `gauntlet-spec-index: docs corpus has 2 documents - ${NOTE}\n`);
423
478
  });
424
479
 
425
480
  test("D3: shared confidence rejects common body-only hits", (t) => {
@@ -558,3 +613,15 @@ test("D10: fenced headings and shorter nested fences are ignored", (t) => {
558
613
  run(root, ["--corpus", "docs", "--query", "ptarmigan gannet"]);
559
614
  for (const p of ["doc/f.md", "doc/nested.md"]) assert.equal(withDb(root, (db) => db.prepare("SELECT headings FROM docs WHERE path = ?").get(p).headings), "");
560
615
  });
616
+
617
+ test("D13: five-document docs corpus with rare heading terms returns rows and no note", (t) => {
618
+ const root = gitRepo();
619
+ t.after(() => rmSync(root, { recursive: true, force: true }));
620
+ write(root, "doc/strong.md", "# Strong doc\n\n## heron ibis\n\n## jackal\n\nbody\n");
621
+ for (let n = 1; n <= 4; n++) write(root, `doc/p${n}.md`, `# Plain doc ${n}\n\nThe body mentions the common shared vocabulary.\n`);
622
+ commit(root);
623
+ const r = run(root, ["--corpus", "docs", "--query", "heron ibis jackal common", "--limit", "10"]);
624
+ assert.equal(r.status, 0, r.stderr);
625
+ assert.deepEqual(paths(r), ["doc/strong.md"]);
626
+ assert.equal(r.stderr, "");
627
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-gauntlet",
3
- "version": "5.23.0",
3
+ "version": "6.0.0",
4
4
  "description": "Opinionated, gated workflow skills, subagent personas, and runtime extensions for the pi coding agent.",
5
5
  "author": "Jacek Juraszek",
6
6
  "type": "module",
@@ -58,7 +58,11 @@ Scout (always dispatched):
58
58
  > `node <SPEC_INDEX> --query '<keywords>' --limit 10` from the worktree root,
59
59
  > keeping the keywords inside single quotes, and treat its rows as the candidate
60
60
  > list; zero rows means the index found no evidence, not that no predecessor exists -
61
- > judge `Predecessor: none` from the code recon. If the command fails, fall back to
61
+ > judge `Predecessor: none` from the code recon. When the command output contains a line
62
+ > ending "too small for the confidence rule; no rows returned" (a diagnostic, not a row) and
63
+ > the code recon names no predecessor, write
64
+ > `Predecessor: none (specs corpus too small: <n> documents)`, copying <n> from that
65
+ > line. If the command fails, fall back to
62
66
  > listing the project's spec directory
63
67
  > and reading titles and `**Goal:**` lines, and write
64
68
  > `Spec index unavailable - predecessor check used directory listing.` in your
@@ -96,7 +100,10 @@ Scout (always dispatched):
96
100
  > document (the row's `snippet` column is a hint to it), or the document title when no single
97
101
  > section applies; or `Docs touched: none`. Judge by topic; a lexical hit alone is not
98
102
  > coverage. Zero rows means the index found no evidence, not that no document covers the
99
- > topic. This query supplements the code and documentation recon you already perform; it
103
+ > topic. When the docs command output contains that same diagnostic line and the recon
104
+ > found no covering document, write
105
+ > `Docs touched: none (docs corpus too small: <n> documents)` instead. This query
106
+ > supplements the code and documentation recon you already perform; it
100
107
  > never replaces it - keep reading the files the request touches. End with an
101
108
  > "Open questions that matter for the spec"
102
109
  > section. Compact handoff, not a dump.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: forge-skill
3
+ description: Use when creating or editing a SKILL.md, a skill's reference file, an agent persona, a prompt template, or a slash command in pi or Claude Code - including one-line edits - or when such a file exceeds 500 lines, gains if/else branching, or drifts from imperative voice; the authoring skill for repos that follow gauntlet's conventions.
4
+ ---
5
+
6
+ # Forge Skill
7
+
8
+ ## Overview
9
+
10
+ This skill governs every file a model reads as instructions: `SKILL.md` and its reference files, agent personas, prompt templates, and slash commands, in pi and in Claude Code. Every sentence below is a rule to apply or a fact to check.
11
+
12
+ ## Conventions
13
+
14
+ Sources: pi - `docs/skills` in `@earendil-works/pi-coding-agent`; Claude Code - `code.claude.com/docs/en/skills`.
15
+
16
+ | Item | pi | Claude Code |
17
+ |---|---|---|
18
+ | Frontmatter | `---` on line 1, `name` and `description`, closing `---` | Same; an unknown field is ignored silently; unparseable YAML loads the skill with no fields |
19
+ | `name` | Lowercase letters, digits, hyphens; no leading, trailing, or doubled hyphen; at most 64 chars; match the directory name (Agent Skills standard; pi does not enforce it) | Display label for a personal or project skill (the command is the directory name); the last command segment for a plugin skill |
20
+ | `description` | At most 1024 chars; a skill without one is not loaded | Recommended; `description` plus `when_to_use` is cut at 1536 chars in the listing |
21
+ | Harness-only fields | `license`, `compatibility`, `metadata`, `allowed-tools` (spec-listed, not enforced) | `when_to_use`, `argument-hint`, `arguments`, `user-invocable`, `allowed-tools` (enforced) |
22
+ | `disable-model-invocation: true` | Command-only skill | Same; also excluded from subagent preload |
23
+ | Invocation | `/skill:name [args]` | `/name [args]`, or `/plugin:name` for a plugin skill |
24
+ | Supporting files | `reference/<topic>.md` (gauntlet convention; pi docs show `references/`), read at the step that names it; dispatch payloads sit beside `SKILL.md` | Any file linked by relative path; `references/`, `scripts/`, `assets/` by convention; `scripts/` files are executed, not loaded |
25
+ | Substitutions | None; the body is read as written | Argument tokens and `CLAUDE_*` variables in the dollar form expand before the model reads the body. In prose, prefix an argument token with one backslash (backslash, dollar, ARGUMENTS) and name a variable bare (`CLAUDE_SKILL_DIR`) |
26
+ | Locations | `.pi/skills/`, `~/.pi/agent/skills/`, `.agents/skills/`, `~/.agents/skills/`, a package's `skills/` dir | `.claude/skills/`, `~/.claude/skills/`, a plugin's `skills/` dir; `.agents/skills/` is not read |
27
+ | Reload after edit | `/reload` | Watched for `.claude/skills/` and `~/.claude/skills/`; a plugin skill needs a new session |
28
+
29
+ ## Authoring rules
30
+
31
+ These rules bind the lines an edit adds or changes. Leave other lines alone.
32
+
33
+ - Imperative voice. Write instructions as commands. Never "should", "consider", "you may want to", "it is recommended".
34
+ - Low conditionality. One path per step: branch only on a runtime fact the model can observe - a tool result, a file's presence, a settings value - never on the reader's judgment. Two branches is the ceiling; a third goes into a table or a `reference/` file.
35
+ - Minimal diff. A rule change touches the sentence that owns the rule, not the section. Never restate a rule in a second place; link the owner.
36
+ - Oversized file. A change touching a file over 500 lines extracts the concern it touches - or the largest self-contained `##` section - into `reference/<topic>.md` in the same change, and leaves a one-line "read X now" pointer at the step that needs it.
37
+ - Recipe over prohibition. A shape or quality problem gets a statement of what the output is; a discipline problem gets one explicit rule. Never a list of things to avoid.
38
+ - No nuance clauses. "Unless it matters", "when appropriate", "if needed" reopen the decision; delete them or name the observable condition.
39
+ - Description is a trigger. State when to load the skill (symptoms, file types, situations); never summarize its steps.
40
+ - One example. One concrete example per rule, never three.
41
+ - Skip what the model knows. No explanations of git, shell, or the harness.
42
+ - Never force-load. Cross-reference a skill by its invocation name (pi `/skill:name`, Claude Code `/name` or `/plugin:name`); never with `@`.
43
+ - ASCII punctuation, compact `|---|` table separators, present tense.
44
+
45
+ ## Edit procedure
46
+
47
+ 1. Read the whole file.
48
+ 2. Locate and edit the sentence that owns the rule you are changing (see Minimal diff).
49
+ 3. Read the changed lines back against `## Authoring rules`.
50
+ 4. Run `wc -l` on the file; over 500 lines, extract per the oversized-file rule.
51
+ 5. pi: run `/reload`. Claude Code: start a new session.
52
+
53
+ ## Test (optional)
54
+
55
+ Run after adding a rule or rewording a discipline rule.
56
+
57
+ 1. Write one scenario prompt the rule governs.
58
+ 2. In one turn dispatch two fresh workers: the loaded worker's task is the scenario plus the full `SKILL.md` body; the baseline worker's task is the scenario alone.
59
+ 3. Compare the two outputs. Fold every excuse the baseline gives into the rule sentence it evades.
60
+
61
+ | Harness | Dispatch |
62
+ |---|---|
63
+ | pi | `subagent({ agent: "worker", context: "fresh", task })` twice, one task with the body appended |
64
+ | Claude Code | The harness's subagent dispatch tool, twice, same split |
65
+
66
+ ## Project overrides
67
+
68
+ If a gauntlet overrides file exists - checked in order: `.pi/gauntlet-overrides.md`, `<repo root>/gauntlet-overrides.md`, `<repo root>/doc/gauntlet-overrides.md`; first found wins - read it. Read and apply `## conventions` whenever present, without a relevance judgment. Give this skill's named section precedence over conflicting `## conventions` rules. Use other relevant sections - by name match, by topic (routing, verification, worktrees, etc.), or by workflow convention - to override or extend the instructions above. Project-local `AGENTS.md` is already in context - check it for project-specific routing tables, service paths, and verification commands.