From dd4fe39fe073f9f57fac8af99009c1dbad2ff652 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:03:54 +0200 Subject: [PATCH 01/12] docs: design spec for converting forge commands to skills Skills are the single representation that runs natively on both Claude Code (where skills are a superset of commands) and Pi (where skills are the native unit). Replaces the command-to-skill bridge with a full conversion, no drift. --- ...26-07-11-forge-skills-conversion-design.md | 172 ++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 docs/specs/2026-07-11-forge-skills-conversion-design.md diff --git a/docs/specs/2026-07-11-forge-skills-conversion-design.md b/docs/specs/2026-07-11-forge-skills-conversion-design.md new file mode 100644 index 0000000..3795a7e --- /dev/null +++ b/docs/specs/2026-07-11-forge-skills-conversion-design.md @@ -0,0 +1,172 @@ +# Design: convert forge commands to skills (cross-platform) + +Date: 2026-07-11 +Status: approved design, pending spec review + +## Problem + +Forge ships as a Claude Code plugin whose three workflows are **commands** +(`commands/interview.md`, `commands/planning.md`, `commands/building.md`), +invoked as `/forge:building #3`. Commands are a Claude Code construct. Pi, and +the Agent Skills ecosystem generally, has no commands; its native unit is the +**skill**. To run forge on Pi we were about to bridge command-format to +skill-format, which reintroduces the two-copies-drift problem the harness +neutralization work spent its whole effort eliminating. + +## Key enabling fact + +Claude Code has merged skills and commands. A skill can be invoked explicitly +as `/forge:building #3`, supports the `$ARGUMENTS` placeholder exactly like a +command, and with `disable-model-invocation: true` is explicit-only (the user +invokes it; Claude never auto-fires it). So on Claude Code a skill is a strict +**superset** of a command: converting loses nothing. On Pi the skill is the +native unit. Therefore the skill is the single representation that runs on both +platforms with no bridge. + +## Goal + +Convert the three commands into skills so that one source tree runs natively on +Claude Code and Pi, maximizing compatibility across agent platforms, with no +regression to the current Claude Code invocation (`/forge:building #3` still +works, still explicit, still staged). + +## Design + +### Structure + +Replace `commands/` with a `skills/` tree, one skill per workflow, each holding +the existing neutralized body verbatim: + +``` +skills/ + forge-interview/SKILL.md + forge-planning/SKILL.md + forge-building/SKILL.md +docs/harness-bindings/ # unchanged: claude-code.md, pi.md, README.md +package.json # new: Pi manifest +.claude-plugin/plugin.json # updated: wording only +.claude-plugin/marketplace.json # updated: wording only +``` + +One representation, two consumers: Claude Code auto-discovers `skills/` from the +plugin; Pi discovers the same directory via `package.json`. + +### Frontmatter + +Each `SKILL.md` replaces command frontmatter with skill frontmatter: + +```yaml +--- +name: forge:building +description: +disable-model-invocation: true +--- +``` + +- `name` carries the `forge:` namespace so the Claude Code invocation stays + `/forge:building`. (Exact namespacing for a plugin skill is an open item, see + below.) +- `description` is copied unchanged from the current command frontmatter. +- `disable-model-invocation: true` makes the skill explicit-only on Claude Code, + which is required for a human-gated staged pipeline (you do not want Claude + auto-running `/forge:building` because code "looks ready"). +- `argument-hint` is dropped; it is not a skill frontmatter field. + +### Arguments + +`$ARGUMENTS` stays in each body where the current commands use it; Claude Code +substitutes it. Because a harness may not substitute `$ARGUMENTS`, each body +gets one robustness line near the top of its interpret-the-request step: + +> If arguments were not substituted into `$ARGUMENTS`, take the issue +> references and flags from the user's request instead. + +This makes argument handling correct on Claude Code (substitution) and on any +harness that instead passes the request as context (extraction). + +### Manifests + +- **Claude Code**: keep `.claude-plugin/plugin.json` and `marketplace.json`. + Skills ship inside the plugin and are auto-discovered; `/plugin install` + continues to work. Only descriptive wording changes ("commands" -> "skills"). +- **Pi**: add `package.json`: + ```json + { + "name": "forge", + "version": "", + "description": "", + "keywords": ["pi-package", "workflow", "planning", "github"], + "pi": { "skills": ["./skills"] } + } + ``` + No `.pi/extensions/*.ts` in this round. The skill body's Harness-binding + preamble already directs the agent to read `docs/harness-bindings/pi.md`, so + binding resolution needs no extension. + +### What does not change + +The neutralized bodies, the harness-agnostic binding preamble, the binding +layer (`docs/harness-bindings/*`), and the `gh` usage are all untouched. This +change is purely the packaging unit (command -> skill). The harness-neutrality +work sits underneath it, unchanged. + +## Invocation model + +| Platform | Invocation | Arguments | Auto-trigger | +| --- | --- | --- | --- | +| Claude Code | `/forge:building #3` | `$ARGUMENTS` substituted | disabled via `disable-model-invocation` | +| Pi | `/skill:forge:building` (or by request) | from request; `$ARGUMENTS` if supported | per Pi's skill model (verify) | + +## Open items to verify during implementation (do not assume) + +1. **Pi argument passing.** Does Pi substitute or otherwise pass arguments to an + invoked skill? If not, the extraction fallback covers it, but confirm. +2. **`disable-model-invocation` on Pi.** Does Pi read, ignore, or error on this + frontmatter field? It must at least not break skill loading. +3. **Claude Code plugin skill discovery and naming.** Confirm plugin skills in + `skills/` are auto-discovered, and confirm the exact invocation name for a + namespaced plugin skill (`forge:building` vs `forge-building` vs + `plugin-name:skill`). Adjust `name` and directory names to whatever yields + `/forge:building`. +4. **agentskills.io spec constraints.** Check `agentskills.io/specification` for + any constraint on frontmatter fields or skill directory naming that affects + the above. + +## Out of scope (YAGNI) + +- A `.pi/extensions/forge.ts` that auto-injects the binding into context. The + preamble covers binding-loading without it; revisit only if the manual read + proves unreliable on Pi. +- Any change to the pipeline behavior, the bindings, or the neutralized prose. + +## Testing / verification + +- **Claude Code (no regression):** after conversion, invoke each of the three + skills with `/forge:` and confirm identical behavior to the current + commands (explicit invocation, `$ARGUMENTS` received, no auto-trigger). A + round-trip is not applicable here (this is a packaging change, not a prose + edit); the check is behavioral parity of invocation. +- **Pi:** install the package on a Pi environment, invoke each skill, confirm it + loads, reads `pi.md`, resolves the neutral verbs, and runs. This is the same + Pi validation already done manually for the command form; repeat it for the + skill form. +- **Structural checks:** every `SKILL.md` has valid `name` + `description` + frontmatter; `package.json` `pi.skills` points at the real directory; no + `commands/` remains; `plugin.json`/`marketplace.json`/README no longer say + "commands"; the three neutralized bodies are byte-identical to their + `commands/*.md` source except for the frontmatter block and the one + argument-fallback line. + +## Risks + +- **Namespacing/invocation-name drift.** If a plugin skill cannot reproduce the + `/forge:building` name exactly, existing Claude Code users' muscle memory + changes. Mitigation: verify item 3 before finalizing names; if `forge:` is not + achievable, decide the closest acceptable name explicitly. +- **Pi arg model weaker than expected.** If Pi neither substitutes `$ARGUMENTS` + nor reliably lets the model extract flags, complex invocations + (`--no-gate --max-rounds 3`) may be fragile on Pi. Mitigation: the extraction + fallback plus explicit instruction; document the limitation if it persists. +- **Marketplace listing.** Converting may affect the Claude Code marketplace + entry. Mitigation: keep `.claude-plugin/*`; verify `/plugin install` still + resolves the plugin with skills. From 72187d5f0f25e82db9071eedd897701b409ea270 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:09:11 +0200 Subject: [PATCH 02/12] docs: implementation plan for forge skills conversion --- .../2026-07-11-forge-skills-conversion.md | 443 ++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 docs/plans/2026-07-11-forge-skills-conversion.md diff --git a/docs/plans/2026-07-11-forge-skills-conversion.md b/docs/plans/2026-07-11-forge-skills-conversion.md new file mode 100644 index 0000000..34594a7 --- /dev/null +++ b/docs/plans/2026-07-11-forge-skills-conversion.md @@ -0,0 +1,443 @@ +# Forge Skills Conversion Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Convert forge's three Claude Code commands into skills so one source tree runs natively on both Claude Code and Pi. + +**Architecture:** Each `commands/.md` becomes `skills/forge-/SKILL.md` with skill frontmatter (`name`, `description`, `disable-model-invocation`). The neutralized body is preserved verbatim plus one argument-fallback line. Claude Code keeps its plugin manifest; Pi gets a `package.json` declaring the same `skills/` directory. The binding layer (`docs/harness-bindings/*`) is untouched. + +**Tech Stack:** Markdown skill files, JSON manifests, `gh`/`git`, bash verification. No application code, no test framework; "tests" are structural checks (grep/diff/JSON-parse) and, for the two behavioral tasks, live invocation. + +## Global Constraints + +- Never use the em dash symbol (`—`) in any file. Rewrite the sentence. +- Neutralized bodies stay byte-identical to their `commands/*.md` source except: (a) the frontmatter block is replaced, and (b) exactly one argument-fallback line is inserted. +- No forbidden Claude tokens reintroduced into skill bodies: `opus`, `sonnet`, `AskUserQuestion`, `EnterWorktree`, whole-word `Explore`, `scratchpad`, `code-review` must stay absent from `skills/`. +- Explicit-only invocation on Claude Code: every skill sets `disable-model-invocation: true`. +- Single source: after conversion no `commands/` directory remains; the bodies are not duplicated anywhere. +- Base branch for this work: `feature/skills-conversion` (already checked out in this worktree, forked from `building/issue-3`). + +--- + +### Task 1: Confirm Claude Code plugin skill naming and discovery + +Resolve the naming/discovery unknowns before converting, so the skill directory names and `name` frontmatter are correct on the first try. + +**Files:** +- Create: `docs/plans/task1-naming-findings.md` (records the resolved naming decision; kept) + +**Interfaces:** +- Produces: `SKILL_NAME_PATTERN` (the exact `name:` value that yields `/forge:` invocation, e.g. `forge:building` or `building`) and `SKILL_DIR_PATTERN` (e.g. `forge-building`), consumed by Tasks 2-4. + +- [ ] **Step 1: Gather the facts** + +Confirm, from current Claude Code docs (use the claude-code-guide agent or `https://code.claude.com/docs/en/skills.md`) and `https://agentskills.io/specification`: +1. Are plugin skills in `skills//SKILL.md` auto-discovered (no manifest listing needed)? +2. What exact `name:` value produces the invocation `/forge:building` for a skill shipped by a plugin named `forge`? (Is it `name: building` namespaced to `forge:building`, or `name: forge:building`, or `forge-building`?) +3. Does `agentskills.io` constrain the `name` field charset (does it permit `:`), and does it require any field beyond `name` + `description`? + +- [ ] **Step 2: Record the decision** + +Write the two resolved values to `docs/plans/task1-naming-findings.md`: +``` +SKILL_NAME_PATTERN= # yields /forge:building +SKILL_DIR_PATTERN=forge- # or the resolved dir convention +AUTO_DISCOVERED= +``` +Default to assume if docs are silent: dir `forge-`, `name: ` (plugin namespaces it), auto-discovered. Note explicitly if you had to fall back to the default. + +- [ ] **Step 3: Commit the finding** + +```bash +git add docs/plans/task1-naming-findings.md +git commit -m "docs: resolve Claude Code plugin skill naming for conversion" +``` + +--- + +### Task 2: Convert the interview command to a skill + +**Files:** +- Create: `skills/forge-interview/SKILL.md` +- Delete: `commands/interview.md` + +**Interfaces:** +- Consumes: `SKILL_NAME_PATTERN`, `SKILL_DIR_PATTERN` from Task 1. + +- [ ] **Step 1: Write the verification check (expect failure now)** + +Run: +```bash +test -f skills/forge-interview/SKILL.md && echo EXISTS || echo MISSING +``` +Expected: `MISSING`. + +- [ ] **Step 2: Create the skill file with new frontmatter + preserved body** + +```bash +mkdir -p skills/forge-interview +# new frontmatter (name value per Task 1; shown with the default) +cat > skills/forge-interview/SKILL.md <<'EOF' +--- +name: interview +description: Relentless one-question-at-a-time grilling interview about an idea, then synthesis into a PM-level spec document ready to pass to /forge:planning. The human-in-the-loop alignment phase; produces no code and no issues. +disable-model-invocation: true +--- +EOF +# append the body of the old command with its frontmatter stripped (first --- block only) +python3 -c "import re; s=open('commands/interview.md').read(); import sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',s,count=1,flags=re.S))" >> skills/forge-interview/SKILL.md +``` + +- [ ] **Step 3: Insert the argument-fallback line** + +Insert immediately after the `Input: $ARGUMENTS` line: +```bash +python3 - <<'PY' +p='skills/forge-interview/SKILL.md'; s=open(p).read() +anchor='Input: $ARGUMENTS\n' +add=anchor+'\nIf `$ARGUMENTS` is empty or was not substituted, treat the user\'s request itself as the arguments.\n' +assert anchor in s, 'anchor not found' +open(p,'w').write(s.replace(anchor,add,1)) +PY +``` + +- [ ] **Step 4: Verify frontmatter + body parity + no forbidden tokens** + +```bash +head -5 skills/forge-interview/SKILL.md # expect name/description/disable-model-invocation +# body parity: strip frontmatter from both, diff; expect ONLY the one added fallback line +diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('commands/interview.md').read(),count=1,flags=re.S),end='')") \ + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-interview/SKILL.md').read(),count=1,flags=re.S),end='')") +# expected: exactly one added line (the fallback), nothing else +grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/forge-interview/SKILL.md || echo "clean" +grep -nw Explore skills/forge-interview/SKILL.md || echo "clean" +``` +Expected: frontmatter correct; diff shows only the fallback insertion; `clean` printed twice. + +- [ ] **Step 5: Delete the old command and commit** + +```bash +git rm commands/interview.md +git add skills/forge-interview/SKILL.md +git commit -m "feat: convert interview command to a skill" +``` + +--- + +### Task 3: Convert the planning command to a skill + +**Files:** +- Create: `skills/forge-planning/SKILL.md` +- Delete: `commands/planning.md` + +- [ ] **Step 1: Verify absence (expect failure)** + +Run: +```bash +test -f skills/forge-planning/SKILL.md && echo EXISTS || echo MISSING +``` +Expected: `MISSING`. + +- [ ] **Step 2: Create the skill file** + +```bash +mkdir -p skills/forge-planning +cat > skills/forge-planning/SKILL.md <<'EOF' +--- +name: planning +description: Adversarial PM-level planning. A planner drafts an epic with user stories, a critic attacks it, they iterate, then everything lands on GitHub immediately as issues for async human review. Technical contracts are deliberately excluded; those get negotiated at /forge:building time. +disable-model-invocation: true +--- +EOF +python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open('commands/planning.md').read(),count=1,flags=re.S))" >> skills/forge-planning/SKILL.md +``` + +- [ ] **Step 3: Insert the argument-fallback line** + +Insert immediately after the `Requested input:\n$ARGUMENTS` block: +```bash +python3 - <<'PY' +p='skills/forge-planning/SKILL.md'; s=open(p).read() +anchor='Requested input:\n$ARGUMENTS\n' +add=anchor+'\nIf `$ARGUMENTS` is empty or was not substituted, treat the user\'s request itself as the arguments.\n' +assert anchor in s, 'anchor not found' +open(p,'w').write(s.replace(anchor,add,1)) +PY +``` + +- [ ] **Step 4: Verify** + +```bash +head -5 skills/forge-planning/SKILL.md +diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('commands/planning.md').read(),count=1,flags=re.S),end='')") \ + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-planning/SKILL.md').read(),count=1,flags=re.S),end='')") +grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/forge-planning/SKILL.md || echo "clean" +grep -nw Explore skills/forge-planning/SKILL.md || echo "clean" +``` +Expected: correct frontmatter; diff shows only the fallback insertion; `clean` twice. + +- [ ] **Step 5: Delete old command and commit** + +```bash +git rm commands/planning.md +git add skills/forge-planning/SKILL.md +git commit -m "feat: convert planning command to a skill" +``` + +--- + +### Task 4: Convert the building command to a skill + +**Files:** +- Create: `skills/forge-building/SKILL.md` +- Delete: `commands/building.md` + +- [ ] **Step 1: Verify absence (expect failure)** + +Run: +```bash +test -f skills/forge-building/SKILL.md && echo EXISTS || echo MISSING +``` +Expected: `MISSING`. + +- [ ] **Step 2: Create the skill file** + +```bash +mkdir -p skills/forge-building +cat > skills/forge-building/SKILL.md <<'EOF' +--- +name: building +description: Implement signed-off issues end to end with a team of agents in an isolated worktree. Before coding, a generator and an adversarial evaluator negotiate a granular contract of what "done" means; after coding, the evaluator black-box tests the running artifact against that contract until it passes. Ships as a PR. +disable-model-invocation: true +--- +EOF +python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open('commands/building.md').read(),count=1,flags=re.S))" >> skills/forge-building/SKILL.md +``` + +- [ ] **Step 3: Insert the argument-fallback line** + +Insert immediately after the `Requested work:\n$ARGUMENTS` block: +```bash +python3 - <<'PY' +p='skills/forge-building/SKILL.md'; s=open(p).read() +anchor='Requested work:\n$ARGUMENTS\n' +add=anchor+'\nIf `$ARGUMENTS` is empty or was not substituted, treat the user\'s request itself as the arguments.\n' +assert anchor in s, 'anchor not found' +open(p,'w').write(s.replace(anchor,add,1)) +PY +``` + +- [ ] **Step 4: Verify** + +```bash +head -5 skills/forge-building/SKILL.md +diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('commands/building.md').read(),count=1,flags=re.S),end='')") \ + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-building/SKILL.md').read(),count=1,flags=re.S),end='')") +grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/forge-building/SKILL.md || echo "clean" +grep -nw Explore skills/forge-building/SKILL.md || echo "clean" +``` +Expected: correct frontmatter; diff shows only the fallback insertion; `clean` twice. + +- [ ] **Step 5: Delete old command and commit** + +```bash +git rm commands/building.md +git add skills/forge-building/SKILL.md +git commit -m "feat: convert building command to a skill" +``` + +--- + +### Task 5: Add the Pi package manifest + +**Files:** +- Create: `package.json` + +- [ ] **Step 1: Verify absence (expect failure)** + +Run: +```bash +test -f package.json && echo EXISTS || echo MISSING +``` +Expected: `MISSING`. + +- [ ] **Step 2: Read the current plugin metadata to copy values** + +```bash +cat .claude-plugin/plugin.json # copy name, version, description verbatim +``` + +- [ ] **Step 3: Write package.json** + +Use the `name`, `version`, `description` from `.claude-plugin/plugin.json`: +```bash +cat > package.json <<'EOF' +{ + "name": "forge", + "version": "1.3.0", + "description": "Idea-to-PR pipeline with a human gate at each seam: grill an idea into a spec, plan it adversarially into GitHub issues, then implement it against a negotiated contract and ship a PR.", + "keywords": ["pi-package", "workflow", "planning", "github", "issues", "pull-request", "agents"], + "pi": { "skills": ["./skills"] } +} +EOF +``` +(If `plugin.json` version differs from `1.3.0`, use the actual value.) + +- [ ] **Step 4: Verify it parses and points at real skills** + +```bash +python3 -c "import json;d=json.load(open('package.json'));print('pi.skills:',d['pi']['skills']); import os;print('dir exists:',os.path.isdir('skills'))" +``` +Expected: `pi.skills: ['./skills']` and `dir exists: True`. + +- [ ] **Step 5: Commit** + +```bash +git add package.json +git commit -m "feat: add Pi package manifest declaring the skills directory" +``` + +--- + +### Task 6: Update Claude Code manifests and README wording + +Change "commands" to "skills" in human-facing text; do not change install mechanics. + +**Files:** +- Modify: `.claude-plugin/plugin.json` +- Modify: `.claude-plugin/marketplace.json` +- Modify: `README.md` + +- [ ] **Step 1: Find every "command" mention to review** + +```bash +grep -rniE 'command' .claude-plugin/ README.md +``` +Expected: a list of lines to review (descriptions, the requirements/usage sections, the commands table). + +- [ ] **Step 2: Edit each to say "skills"** + +For each hit, reword "command"/"commands" to "skill"/"skills" where it refers to forge's three workflows. Update the README usage/table so it shows the three skills and their invocation (`/forge:interview`, `/forge:planning`, `/forge:building`). Do NOT change the `/plugin marketplace add` / `/plugin install` lines. Do NOT introduce an em dash. + +- [ ] **Step 3: Verify no stale "command" references to the workflows remain, and JSON still parses** + +```bash +python3 -c "import json;json.load(open('.claude-plugin/plugin.json'));json.load(open('.claude-plugin/marketplace.json'));print('json ok')" +grep -rniE 'command' .claude-plugin/ README.md || echo "no command references remain" +grep -c '—' README.md .claude-plugin/*.json # expect 0 each +``` +Expected: `json ok`; either no command references or only ones you deliberately kept (e.g. a `gh` shell command reference), and zero em dashes. + +- [ ] **Step 4: Commit** + +```bash +git add .claude-plugin/plugin.json .claude-plugin/marketplace.json README.md +git commit -m "docs: describe forge as skills, not commands" +``` + +--- + +### Task 7: Full structural verification pass + +Prove the conversion is complete and consistent before any live test. + +**Files:** none (verification only) + +- [ ] **Step 1: Assert no commands directory and three valid skills exist** + +```bash +test -d commands && echo "FAIL: commands/ still present" || echo "OK: no commands/" +for w in interview planning building; do + f=skills/forge-$w/SKILL.md + test -f "$f" && echo "OK: $f" || echo "FAIL: $f missing" + head -4 "$f" | grep -q 'name:' && head -4 "$f" | grep -q 'description:' && head -4 "$f" | grep -q 'disable-model-invocation:' && echo " frontmatter OK" || echo " FAIL frontmatter" +done +``` +Expected: `OK: no commands/`, and each skill file present with `frontmatter OK`. + +- [ ] **Step 2: Assert no forbidden tokens anywhere in skills/** + +```bash +grep -rnIE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/ | grep -v disable-model-invocation || echo "clean" +grep -rnIw Explore skills/ || echo "clean" +``` +Expected: `clean` twice. + +- [ ] **Step 3: Assert bodies match the pre-conversion source** + +```bash +git fetch -q origin building/issue-3 2>/dev/null || true +for w in interview planning building; do + echo "=== $w ===" + diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('/dev/stdin').read(),count=1,flags=re.S),end='')" < <(git show building/issue-3:commands/$w.md)) \ + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-$w/SKILL.md').read(),count=1,flags=re.S),end='')") \ + && echo "identical" || echo "only-fallback-diff (inspect: must be exactly the one added line)" +done +``` +Expected: each shows only the single added fallback line versus the `building/issue-3` source. + +- [ ] **Step 4: Commit a marker if any fixups were needed** + +If steps 1-3 revealed a defect, fix it in the offending task's file and commit `fix: ...`. If all passed, no commit needed. + +--- + +### Task 8: Claude Code behavioral parity check + +Confirm the three skills invoke and behave like the old commands on Claude Code. + +**Files:** none (live check) + +- [ ] **Step 1: Confirm discovery** + +In a Claude Code session with this plugin/worktree active, list available skills and confirm `forge:interview`, `forge:planning`, `forge:building` appear and are invocable. + +- [ ] **Step 2: Invoke the lightest skill with an argument** + +Run `/forge:interview a CLI that renames photos by EXIF date` and confirm: it is invoked explicitly (not auto-fired), `$ARGUMENTS` arrived (it interviews about that idea), and it follows the phases. Do NOT complete a full run; confirming correct start is enough. + +- [ ] **Step 3: Confirm explicit-only** + +In normal conversation, mention "I might build something" WITHOUT invoking a skill; confirm no forge skill auto-fires (validates `disable-model-invocation`). + +- [ ] **Step 4: Record the result** + +Note pass/fail per skill in the PR description when opened. No commit. + +--- + +### Task 9: Pi behavioral validation + +Resolve the two Pi open items by running on a real Pi environment. + +**Files:** none (live check on Pi) + +- [ ] **Step 1: Install the package on Pi** + +In the Pi environment, install/point at this package so Pi discovers `pi.skills`. Confirm the three skills are discoverable via `/skill:` invocation. + +- [ ] **Step 2: Invoke a skill and observe argument handling (open item 1)** + +Invoke the building skill on a small issue with a flag, e.g. request "run the forge building skill on issue 3 with --no-gate". Observe whether Pi passed `$ARGUMENTS` (substituted) or the model extracted the issue/flag from the request via the fallback line. Record which path worked. + +- [ ] **Step 3: Confirm frontmatter tolerance (open item 2)** + +Confirm the skill loaded without error despite the `disable-model-invocation` field (i.e. Pi ignores unknown fields rather than failing). Record the behavior. + +- [ ] **Step 4: Confirm binding resolution** + +Confirm the skill read `docs/harness-bindings/pi.md` and resolved the neutral verbs (used GLM-5.2 for the roles, git worktree, etc.). This repeats the manual command-form validation for the skill form. + +- [ ] **Step 5: Record results** + +Write the observed answers to open items 1 and 2, plus pass/fail, into the PR description. If argument handling or field tolerance failed, open a follow-up note; do not block the Claude Code side. + +--- + +## Self-Review + +**Spec coverage:** structure (Tasks 2-5), frontmatter (Tasks 2-4), arguments + fallback (Tasks 2-4), manifests (Tasks 5-6), what-does-not-change enforced by the body-parity checks (Tasks 2-4, 7), invocation model (Tasks 8-9), all four open items (Task 1 covers naming + agentskills; Task 9 covers Pi args + `disable-model-invocation`), out-of-scope respected (no `.pi/extensions/*` task), testing (Tasks 7-9), risks (naming risk gated by Task 1; Pi-arg risk gated by Task 9; marketplace risk checked in Task 6/8). No gaps found. + +**Placeholder scan:** the only deferred value is `SKILL_NAME_PATTERN` from Task 1, which is a real task-produced interface with a concrete default, not a placeholder. All commands and file contents are literal. + +**Type consistency:** skill directory names `forge-`, `name:` values `` (or Task 1's resolved value), and invocation `/forge:` are used consistently across Tasks 1-9. From 042b3d66a1230c0e85b15471bbce36f1451ef971 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:17:58 +0200 Subject: [PATCH 03/12] docs: resolve Claude Code plugin skill naming for conversion --- docs/plans/task1-naming-findings.md | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 docs/plans/task1-naming-findings.md diff --git a/docs/plans/task1-naming-findings.md b/docs/plans/task1-naming-findings.md new file mode 100644 index 0000000..5a8e020 --- /dev/null +++ b/docs/plans/task1-naming-findings.md @@ -0,0 +1,73 @@ +# Task 1 findings: Claude Code plugin skill naming and discovery + +Sources fetched and verified live on 2026-07-11: +- https://code.claude.com/docs/en/skills.md +- https://agentskills.io/specification + +## Resolved values + +``` +SKILL_NAME_PATTERN= # e.g. name: building, name: planning, name: interview +SKILL_DIR_PATTERN=skills// # e.g. skills/building/, skills/planning/, skills/interview/ +AUTO_DISCOVERED=yes +``` + +**`SKILL_NAME_PATTERN` does NOT default to `forge-`. It is `skills//`, i.e. no `forge-` prefix on the directory.** This did NOT fall back to the brief's stated default (`forge-` dir / `` name) — the docs directly answer the question and the directory part of that default is wrong. See "Discrepancy" below. + +## Evidence + +### 1. Auto-discovery (brief Q1) — CONFIRMED, no fallback needed + +The Claude Code skills doc's "Where skills live" table lists: + +> | Plugin | `/skills//SKILL.md` | Where plugin is enabled | + +alongside Enterprise/Personal/Project rows, with no mention of a manifest entry required for plugin skills. `plugin.json` needs no `skills` field; presence of the `skills/` directory in the plugin is sufficient. → `AUTO_DISCOVERED=yes`. + +### 2. What `name:`/directory combination yields `/forge:building` (brief Q2) — CONFIRMED, differs from brief's default + +The doc's "How a skill gets its command name" table states, exactly: + +> | Plugin `skills/` subdirectory | Directory name, namespaced by plugin | `my-plugin/skills/review/SKILL.md` → `/my-plugin:review` | + +and separately: + +> The frontmatter `name` field sets the display label shown in skill listings and, except for a plugin-root `SKILL.md`, does not change what you type after `/`. + +Applied to this repo: the plugin is named `forge` (per `.claude-plugin/plugin.json`, `"name": "forge"`). For a non-root plugin skill, the **directory name itself becomes the part after the colon** — Claude Code prepends the plugin's own name as the namespace automatically. It does not concatenate a `forge-` prefix already baked into the directory name. + +So to get `/forge:building`: +- Directory must be `skills/building/` (bare workflow name), **not** `skills/forge-building/`. +- If the directory were `skills/forge-building/`, the resulting invocation would be `/forge:forge-building`, not `/forge:building`. +- The `name:` frontmatter value is not what drives the invocation here (only plugin-root `SKILL.md` uses `name:` for that); but agentskills.io requires `name:` to equal the directory name anyway (see below), so `name: building` in `skills/building/SKILL.md` is both spec-compliant and consistent with the intended `/forge:building` invocation. + +### 3. agentskills.io `name` field constraints (brief Q3) — CONFIRMED + +From the specification page's frontmatter table and `name` field section: + +> `name` — Required. Max 64 characters. Lowercase letters, numbers, and hyphens only. Must not start or end with a hyphen. +> +> The required `name` field: +> * Must be 1-64 characters +> * May only contain unicode lowercase alphanumeric characters (`a-z`, `0-9`) and hyphens (`-`) +> * Must not start or end with a hyphen (`-`) +> * Must not contain consecutive hyphens (`--`) +> * **Must match the parent directory name** + +So a colon (`:`) is **not permitted** in `name:` — `name: forge:building` would be spec-invalid. And the only two required fields are `name` and `description`; everything else (`license`, `compatibility`, `metadata`, `allowed-tools`) is optional. + +## Discrepancy flagged for Tasks 2-9 + +`docs/plans/2026-07-11-forge-skills-conversion.md` (Tasks 2-9, already committed) hardcodes `skills/forge-/SKILL.md` throughout (e.g. `skills/forge-interview/`, `skills/forge-building/`). Based on the evidence above, that directory convention produces `/forge:forge-interview`, `/forge:forge-building`, etc. — not the intended `/forge:interview`, `/forge:building`. The correct directory convention per current Claude Code docs is the bare workflow name: `skills/interview/`, `skills/planning/`, `skills/building/`, with `name: interview` / `name: planning` / `name: building` in each `SKILL.md` (matching the directory, per agentskills.io). + +This finding is recorded here per Task 1's scope (research + decision only). Whoever executes Tasks 2-9 should use `SKILL_DIR_PATTERN=skills//` (no `forge-` prefix) instead of the plan's literal `skills/forge-/` paths, and update the plan document's Task 2-9 file paths accordingly before or during execution. + +## Summary + +``` +SKILL_NAME_PATTERN= # name: building | name: planning | name: interview +SKILL_DIR_PATTERN=skills// # skills/building/ | skills/planning/ | skills/interview/ +AUTO_DISCOVERED=yes +``` + +Fallback to brief's stated default: NOT used. Docs answered all three questions directly; the brief's default directory convention (`forge-`) was checked against the docs and found incorrect, so the docs-derived value is recorded instead. From 7a5c0720204e678a7048385c3b2297c8233d1ac8 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:20:28 +0200 Subject: [PATCH 04/12] fix: correct skill dir pattern to skills// per Task 1 findings Task 1 verified against Claude Code docs: a plugin named forge namespaces its skills as /forge:, so skills/building/ yields /forge:building. The plan's skills/forge-building/ would have produced /forge:forge-building. --- .../2026-07-11-forge-skills-conversion.md | 76 +++++++++---------- ...26-07-11-forge-skills-conversion-design.md | 6 +- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/docs/plans/2026-07-11-forge-skills-conversion.md b/docs/plans/2026-07-11-forge-skills-conversion.md index 34594a7..8da817c 100644 --- a/docs/plans/2026-07-11-forge-skills-conversion.md +++ b/docs/plans/2026-07-11-forge-skills-conversion.md @@ -4,7 +4,7 @@ **Goal:** Convert forge's three Claude Code commands into skills so one source tree runs natively on both Claude Code and Pi. -**Architecture:** Each `commands/.md` becomes `skills/forge-/SKILL.md` with skill frontmatter (`name`, `description`, `disable-model-invocation`). The neutralized body is preserved verbatim plus one argument-fallback line. Claude Code keeps its plugin manifest; Pi gets a `package.json` declaring the same `skills/` directory. The binding layer (`docs/harness-bindings/*`) is untouched. +**Architecture:** Each `commands/.md` becomes `skills//SKILL.md` with skill frontmatter (`name`, `description`, `disable-model-invocation`). The neutralized body is preserved verbatim plus one argument-fallback line. Claude Code keeps its plugin manifest; Pi gets a `package.json` declaring the same `skills/` directory. The binding layer (`docs/harness-bindings/*`) is untouched. **Tech Stack:** Markdown skill files, JSON manifests, `gh`/`git`, bash verification. No application code, no test framework; "tests" are structural checks (grep/diff/JSON-parse) and, for the two behavioral tasks, live invocation. @@ -27,7 +27,7 @@ Resolve the naming/discovery unknowns before converting, so the skill directory - Create: `docs/plans/task1-naming-findings.md` (records the resolved naming decision; kept) **Interfaces:** -- Produces: `SKILL_NAME_PATTERN` (the exact `name:` value that yields `/forge:` invocation, e.g. `forge:building` or `building`) and `SKILL_DIR_PATTERN` (e.g. `forge-building`), consumed by Tasks 2-4. +- Produces: `SKILL_NAME_PATTERN` (the exact `name:` value that yields `/forge:` invocation, e.g. `forge:building` or `building`) and `SKILL_DIR_PATTERN` (resolved by Task 1: `skills/building/`, bare workflow name), consumed by Tasks 2-4. - [ ] **Step 1: Gather the facts** @@ -58,7 +58,7 @@ git commit -m "docs: resolve Claude Code plugin skill naming for conversion" ### Task 2: Convert the interview command to a skill **Files:** -- Create: `skills/forge-interview/SKILL.md` +- Create: `skills/interview/SKILL.md` - Delete: `commands/interview.md` **Interfaces:** @@ -68,16 +68,16 @@ git commit -m "docs: resolve Claude Code plugin skill naming for conversion" Run: ```bash -test -f skills/forge-interview/SKILL.md && echo EXISTS || echo MISSING +test -f skills/interview/SKILL.md && echo EXISTS || echo MISSING ``` Expected: `MISSING`. - [ ] **Step 2: Create the skill file with new frontmatter + preserved body** ```bash -mkdir -p skills/forge-interview +mkdir -p skills/interview # new frontmatter (name value per Task 1; shown with the default) -cat > skills/forge-interview/SKILL.md <<'EOF' +cat > skills/interview/SKILL.md <<'EOF' --- name: interview description: Relentless one-question-at-a-time grilling interview about an idea, then synthesis into a PM-level spec document ready to pass to /forge:planning. The human-in-the-loop alignment phase; produces no code and no issues. @@ -85,7 +85,7 @@ disable-model-invocation: true --- EOF # append the body of the old command with its frontmatter stripped (first --- block only) -python3 -c "import re; s=open('commands/interview.md').read(); import sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',s,count=1,flags=re.S))" >> skills/forge-interview/SKILL.md +python3 -c "import re; s=open('commands/interview.md').read(); import sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',s,count=1,flags=re.S))" >> skills/interview/SKILL.md ``` - [ ] **Step 3: Insert the argument-fallback line** @@ -93,7 +93,7 @@ python3 -c "import re; s=open('commands/interview.md').read(); import sys; sys.s Insert immediately after the `Input: $ARGUMENTS` line: ```bash python3 - <<'PY' -p='skills/forge-interview/SKILL.md'; s=open(p).read() +p='skills/interview/SKILL.md'; s=open(p).read() anchor='Input: $ARGUMENTS\n' add=anchor+'\nIf `$ARGUMENTS` is empty or was not substituted, treat the user\'s request itself as the arguments.\n' assert anchor in s, 'anchor not found' @@ -104,13 +104,13 @@ PY - [ ] **Step 4: Verify frontmatter + body parity + no forbidden tokens** ```bash -head -5 skills/forge-interview/SKILL.md # expect name/description/disable-model-invocation +head -5 skills/interview/SKILL.md # expect name/description/disable-model-invocation # body parity: strip frontmatter from both, diff; expect ONLY the one added fallback line diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('commands/interview.md').read(),count=1,flags=re.S),end='')") \ - <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-interview/SKILL.md').read(),count=1,flags=re.S),end='')") + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/interview/SKILL.md').read(),count=1,flags=re.S),end='')") # expected: exactly one added line (the fallback), nothing else -grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/forge-interview/SKILL.md || echo "clean" -grep -nw Explore skills/forge-interview/SKILL.md || echo "clean" +grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/interview/SKILL.md || echo "clean" +grep -nw Explore skills/interview/SKILL.md || echo "clean" ``` Expected: frontmatter correct; diff shows only the fallback insertion; `clean` printed twice. @@ -118,7 +118,7 @@ Expected: frontmatter correct; diff shows only the fallback insertion; `clean` p ```bash git rm commands/interview.md -git add skills/forge-interview/SKILL.md +git add skills/interview/SKILL.md git commit -m "feat: convert interview command to a skill" ``` @@ -127,29 +127,29 @@ git commit -m "feat: convert interview command to a skill" ### Task 3: Convert the planning command to a skill **Files:** -- Create: `skills/forge-planning/SKILL.md` +- Create: `skills/planning/SKILL.md` - Delete: `commands/planning.md` - [ ] **Step 1: Verify absence (expect failure)** Run: ```bash -test -f skills/forge-planning/SKILL.md && echo EXISTS || echo MISSING +test -f skills/planning/SKILL.md && echo EXISTS || echo MISSING ``` Expected: `MISSING`. - [ ] **Step 2: Create the skill file** ```bash -mkdir -p skills/forge-planning -cat > skills/forge-planning/SKILL.md <<'EOF' +mkdir -p skills/planning +cat > skills/planning/SKILL.md <<'EOF' --- name: planning description: Adversarial PM-level planning. A planner drafts an epic with user stories, a critic attacks it, they iterate, then everything lands on GitHub immediately as issues for async human review. Technical contracts are deliberately excluded; those get negotiated at /forge:building time. disable-model-invocation: true --- EOF -python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open('commands/planning.md').read(),count=1,flags=re.S))" >> skills/forge-planning/SKILL.md +python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open('commands/planning.md').read(),count=1,flags=re.S))" >> skills/planning/SKILL.md ``` - [ ] **Step 3: Insert the argument-fallback line** @@ -157,7 +157,7 @@ python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open(' Insert immediately after the `Requested input:\n$ARGUMENTS` block: ```bash python3 - <<'PY' -p='skills/forge-planning/SKILL.md'; s=open(p).read() +p='skills/planning/SKILL.md'; s=open(p).read() anchor='Requested input:\n$ARGUMENTS\n' add=anchor+'\nIf `$ARGUMENTS` is empty or was not substituted, treat the user\'s request itself as the arguments.\n' assert anchor in s, 'anchor not found' @@ -168,11 +168,11 @@ PY - [ ] **Step 4: Verify** ```bash -head -5 skills/forge-planning/SKILL.md +head -5 skills/planning/SKILL.md diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('commands/planning.md').read(),count=1,flags=re.S),end='')") \ - <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-planning/SKILL.md').read(),count=1,flags=re.S),end='')") -grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/forge-planning/SKILL.md || echo "clean" -grep -nw Explore skills/forge-planning/SKILL.md || echo "clean" + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/planning/SKILL.md').read(),count=1,flags=re.S),end='')") +grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/planning/SKILL.md || echo "clean" +grep -nw Explore skills/planning/SKILL.md || echo "clean" ``` Expected: correct frontmatter; diff shows only the fallback insertion; `clean` twice. @@ -180,7 +180,7 @@ Expected: correct frontmatter; diff shows only the fallback insertion; `clean` t ```bash git rm commands/planning.md -git add skills/forge-planning/SKILL.md +git add skills/planning/SKILL.md git commit -m "feat: convert planning command to a skill" ``` @@ -189,29 +189,29 @@ git commit -m "feat: convert planning command to a skill" ### Task 4: Convert the building command to a skill **Files:** -- Create: `skills/forge-building/SKILL.md` +- Create: `skills/building/SKILL.md` - Delete: `commands/building.md` - [ ] **Step 1: Verify absence (expect failure)** Run: ```bash -test -f skills/forge-building/SKILL.md && echo EXISTS || echo MISSING +test -f skills/building/SKILL.md && echo EXISTS || echo MISSING ``` Expected: `MISSING`. - [ ] **Step 2: Create the skill file** ```bash -mkdir -p skills/forge-building -cat > skills/forge-building/SKILL.md <<'EOF' +mkdir -p skills/building +cat > skills/building/SKILL.md <<'EOF' --- name: building description: Implement signed-off issues end to end with a team of agents in an isolated worktree. Before coding, a generator and an adversarial evaluator negotiate a granular contract of what "done" means; after coding, the evaluator black-box tests the running artifact against that contract until it passes. Ships as a PR. disable-model-invocation: true --- EOF -python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open('commands/building.md').read(),count=1,flags=re.S))" >> skills/forge-building/SKILL.md +python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open('commands/building.md').read(),count=1,flags=re.S))" >> skills/building/SKILL.md ``` - [ ] **Step 3: Insert the argument-fallback line** @@ -219,7 +219,7 @@ python3 -c "import re,sys; sys.stdout.write(re.sub(r'^---\n.*?\n---\n','',open(' Insert immediately after the `Requested work:\n$ARGUMENTS` block: ```bash python3 - <<'PY' -p='skills/forge-building/SKILL.md'; s=open(p).read() +p='skills/building/SKILL.md'; s=open(p).read() anchor='Requested work:\n$ARGUMENTS\n' add=anchor+'\nIf `$ARGUMENTS` is empty or was not substituted, treat the user\'s request itself as the arguments.\n' assert anchor in s, 'anchor not found' @@ -230,11 +230,11 @@ PY - [ ] **Step 4: Verify** ```bash -head -5 skills/forge-building/SKILL.md +head -5 skills/building/SKILL.md diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('commands/building.md').read(),count=1,flags=re.S),end='')") \ - <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-building/SKILL.md').read(),count=1,flags=re.S),end='')") -grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/forge-building/SKILL.md || echo "clean" -grep -nw Explore skills/forge-building/SKILL.md || echo "clean" + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/building/SKILL.md').read(),count=1,flags=re.S),end='')") +grep -nE '\b(opus|sonnet)\b|AskUserQuestion|EnterWorktree|scratchpad|code-review' skills/building/SKILL.md || echo "clean" +grep -nw Explore skills/building/SKILL.md || echo "clean" ``` Expected: correct frontmatter; diff shows only the fallback insertion; `clean` twice. @@ -242,7 +242,7 @@ Expected: correct frontmatter; diff shows only the fallback insertion; `clean` t ```bash git rm commands/building.md -git add skills/forge-building/SKILL.md +git add skills/building/SKILL.md git commit -m "feat: convert building command to a skill" ``` @@ -348,7 +348,7 @@ Prove the conversion is complete and consistent before any live test. ```bash test -d commands && echo "FAIL: commands/ still present" || echo "OK: no commands/" for w in interview planning building; do - f=skills/forge-$w/SKILL.md + f=skills/$w/SKILL.md test -f "$f" && echo "OK: $f" || echo "FAIL: $f missing" head -4 "$f" | grep -q 'name:' && head -4 "$f" | grep -q 'description:' && head -4 "$f" | grep -q 'disable-model-invocation:' && echo " frontmatter OK" || echo " FAIL frontmatter" done @@ -370,7 +370,7 @@ git fetch -q origin building/issue-3 2>/dev/null || true for w in interview planning building; do echo "=== $w ===" diff <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('/dev/stdin').read(),count=1,flags=re.S),end='')" < <(git show building/issue-3:commands/$w.md)) \ - <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/forge-$w/SKILL.md').read(),count=1,flags=re.S),end='')") \ + <(python3 -c "import re;print(re.sub(r'^---\n.*?\n---\n','',open('skills/$w/SKILL.md').read(),count=1,flags=re.S),end='')") \ && echo "identical" || echo "only-fallback-diff (inspect: must be exactly the one added line)" done ``` @@ -440,4 +440,4 @@ Write the observed answers to open items 1 and 2, plus pass/fail, into the PR de **Placeholder scan:** the only deferred value is `SKILL_NAME_PATTERN` from Task 1, which is a real task-produced interface with a concrete default, not a placeholder. All commands and file contents are literal. -**Type consistency:** skill directory names `forge-`, `name:` values `` (or Task 1's resolved value), and invocation `/forge:` are used consistently across Tasks 1-9. +**Type consistency:** skill directory names `skills//` (bare, per Task 1), `name:` values `` (or Task 1's resolved value), and invocation `/forge:` are used consistently across Tasks 1-9. diff --git a/docs/specs/2026-07-11-forge-skills-conversion-design.md b/docs/specs/2026-07-11-forge-skills-conversion-design.md index 3795a7e..cb00e6d 100644 --- a/docs/specs/2026-07-11-forge-skills-conversion-design.md +++ b/docs/specs/2026-07-11-forge-skills-conversion-design.md @@ -39,9 +39,9 @@ the existing neutralized body verbatim: ``` skills/ - forge-interview/SKILL.md - forge-planning/SKILL.md - forge-building/SKILL.md + interview/SKILL.md + planning/SKILL.md + building/SKILL.md docs/harness-bindings/ # unchanged: claude-code.md, pi.md, README.md package.json # new: Pi manifest .claude-plugin/plugin.json # updated: wording only From c52ee3b490532644520df8ec34c51acdc9353f37 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:21:36 +0200 Subject: [PATCH 05/12] feat: convert interview command to a skill --- commands/interview.md => skills/interview/SKILL.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename commands/interview.md => skills/interview/SKILL.md (97%) diff --git a/commands/interview.md b/skills/interview/SKILL.md similarity index 97% rename from commands/interview.md rename to skills/interview/SKILL.md index 7314384..1e726d2 100644 --- a/commands/interview.md +++ b/skills/interview/SKILL.md @@ -1,6 +1,7 @@ --- +name: interview description: Relentless one-question-at-a-time grilling interview about an idea, then synthesis into a PM-level spec document ready to pass to /forge:planning. The human-in-the-loop alignment phase; produces no code and no issues. -argument-hint: "[idea text | path/to/brief.md | empty = interview about the current conversation]" +disable-model-invocation: true --- # /forge:interview @@ -20,6 +21,8 @@ architecture, write code, create issues, or run /forge:planning yourself. Input: $ARGUMENTS +If `$ARGUMENTS` is empty or was not substituted, treat the user's request itself as the arguments. + Interpret the input: 1. **Empty** -> the idea is whatever is under discussion in this conversation. 2. **A path to a .md file** -> read it; that brief is the idea. From c77c696d89cd8f1aacd5f87ec7188e0d557281b3 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:26:38 +0200 Subject: [PATCH 06/12] feat: convert planning command to a skill --- commands/planning.md => skills/planning/SKILL.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename commands/planning.md => skills/planning/SKILL.md (98%) diff --git a/commands/planning.md b/skills/planning/SKILL.md similarity index 98% rename from commands/planning.md rename to skills/planning/SKILL.md index 490dd13..b4a340d 100644 --- a/commands/planning.md +++ b/skills/planning/SKILL.md @@ -1,6 +1,7 @@ --- +name: planning description: Adversarial PM-level planning. A planner drafts an epic with user stories, a critic attacks it, they iterate, then everything lands on GitHub immediately as issues for async human review. Technical contracts are deliberately excluded; those get negotiated at /forge:building time. -argument-hint: "[empty = use this conversation] | path/to/spec.md | \"free-form feature description\"" +disable-model-invocation: true --- # /forge:planning @@ -23,6 +24,8 @@ and file the outcome. Requested input: $ARGUMENTS +If `$ARGUMENTS` is empty or was not substituted, treat the user's request itself as the arguments. + ## Operating principles - **PM altitude only.** Issues contain user stories, goals, human-readable From 02e4658124aee10eb163444cc3909f25b317b197 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:29:32 +0200 Subject: [PATCH 07/12] feat: convert building command to a skill --- commands/building.md => skills/building/SKILL.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename commands/building.md => skills/building/SKILL.md (99%) diff --git a/commands/building.md b/skills/building/SKILL.md similarity index 99% rename from commands/building.md rename to skills/building/SKILL.md index 983785a..234c917 100644 --- a/commands/building.md +++ b/skills/building/SKILL.md @@ -1,6 +1,7 @@ --- +name: building description: Implement signed-off issues end to end with a team of agents in an isolated worktree. Before coding, a generator and an adversarial evaluator negotiate a granular contract of what "done" means; after coding, the evaluator black-box tests the running artifact against that contract until it passes. Ships as a PR. -argument-hint: <#issue | #issue #issue ... | "description of the work"> [--no-gate] [--max-rounds N] [--base ] +disable-model-invocation: true --- # /forge:building @@ -22,6 +23,8 @@ relay artifacts, verify, and ship. Requested work: $ARGUMENTS +If `$ARGUMENTS` is empty or was not substituted, treat the user's request itself as the arguments. + ## Roles and models (non-negotiable) | Role | What it does | Model | From 53e297f60d0769a8d031b0ba9a1a7f94d018f09a Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:32:56 +0200 Subject: [PATCH 08/12] feat: add Pi package manifest declaring the skills directory --- package.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 package.json diff --git a/package.json b/package.json new file mode 100644 index 0000000..6f69561 --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "forge", + "version": "1.3.0", + "description": "Idea-to-PR pipeline with a human gate at each seam: grill an idea into a spec, plan it adversarially into GitHub issues, then implement it against a negotiated contract and ship a PR.", + "keywords": ["pi-package", "workflow", "planning", "github", "issues", "pull-request", "agents"], + "pi": { "skills": ["./skills"] } +} From ae4db921a2fdb588ee19e14b4afd1bf994a5dd31 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:35:05 +0200 Subject: [PATCH 09/12] docs: describe forge as skills, not commands --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c500004..3acff09 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Stars](https://img.shields.io/github/stars/dasirra/cc-forge?style=flat-square&color=B0680F)](https://github.com/dasirra/cc-forge/stargazers) [![Last commit](https://img.shields.io/github/last-commit/dasirra/cc-forge?style=flat-square&color=3D5A9E)](https://github.com/dasirra/cc-forge/commits/main) -An opinionated idea-to-PR workflow for [Claude Code](https://claude.com/claude-code), with a human gate at each seam. Three commands, run in order. +An opinionated idea-to-PR workflow for [Claude Code](https://claude.com/claude-code), with a human gate at each seam. Three skills, run in order. ``` PM /forge:interview vague idea -> docs/specs/YYYY-MM-DD-.md @@ -49,9 +49,9 @@ The other two need: - **A way to run your project and observe it from outside.** `/forge:building` resolves an *evaluation surface* up front (`web`, `library`, `cli`, `service`, or `native`) and black-box tests the contract against it. Only `web` needs an extra dependency: a browser automation MCP server, Claude in Chrome or Playwright. - **`/code-review`**, invoked by `/forge:building` for the final review of the integrated diff. -## Commands +## Skills -| Command | Altitude | Description | +| Skill | Altitude | Description | |---------|----------|-------------| | `/forge:interview [idea \| path/to/brief.md]` | PM | Relentless one-question-at-a-time grilling until you and Claude share an understanding of the idea, then synthesis into a PM-level spec. No code, no issues. | | `/forge:planning [path/to/spec.md \| description]` | PM | A planner drafts an epic with user stories, a critic attacks it in a separate context, they iterate up to 3 rounds. Files the result as a GitHub epic with native sub-issues for async human review. No technical content: no files, no schemas, no architecture. | @@ -59,7 +59,7 @@ The other two need: ## Pipeline -Adversarial pairs (⚔) never share context. They exchange files, relayed by the orchestrator. Every 👤 is a stop: the command hands you an artifact and prints the next step rather than running it. +Adversarial pairs (⚔) never share context. They exchange files, relayed by the orchestrator. Every 👤 is a stop: the skill hands you an artifact and prints the next step rather than running it. ```mermaid flowchart TD @@ -115,11 +115,11 @@ Every phase, agent, artifact, and loop is laid out in the [full pipeline referen ## Design -Three ideas run through all three commands. +Three ideas run through all three skills. **Separate contexts, artifacts only.** Every adversarial pair (planner/critic, generator/evaluator) communicates through files, never through summarized reasoning. A critic that sees the planner's rationale rubber-stamps it. -**Altitude discipline.** PM commands describe behavior, the DEV command decides implementation, and the contract that binds them is negotiated against the codebase as it exists at build time, so it cannot go stale between planning and building. See [Two altitudes](#two-altitudes). +**Altitude discipline.** PM skills describe behavior, the DEV skill decides implementation, and the contract that binds them is negotiated against the codebase as it exists at build time, so it cannot go stale between planning and building. See [Two altitudes](#two-altitudes). **Observed behavior beats claims.** `/forge:building` will not accept "mostly works". Each contract criterion passes or fails, judged by an evaluator driving the running artifact, not by reading the diff and not by running the builder's own tests. Those tests encode the builder's understanding, so a green suite certifies whatever misunderstanding produced the bug. @@ -131,7 +131,7 @@ Forge is Claude Code specific, and not incidentally so. It depends on subagents Forge is an opinionated idea-to-PR workflow. It assembles ideas that are not mine, and the opinions and the mistakes in assembling them are. -Mine are the three-command shape with a human gate at each seam, `/forge:planning` as an adversarial PM-level pass that files straight to GitHub and leaves contested items for a human to arbitrate, the altitude discipline that bans technical content until `/forge:building` negotiates it against the live codebase, the evaluation surfaces and the preflight that resolves one before anything expensive happens, and the rule that the evaluator never runs the builders' own tests. +Mine are the three-skill shape with a human gate at each seam, `/forge:planning` as an adversarial PM-level pass that files straight to GitHub and leaves contested items for a human to arbitrate, the altitude discipline that bans technical content until `/forge:building` negotiates it against the live codebase, the evaluation surfaces and the preflight that resolves one before anything expensive happens, and the rule that the evaluator never runs the builders' own tests. **[Full Walkthrough: Workflow for AI Coding](https://www.youtube.com/watch?v=-QFHIoCo-Ko)**, Matt Pocock, at [AI Engineer](https://www.ai.engineer/). The grilling session that became `/forge:interview`, the smart zone and dumb zone, slicing work into vertical issues an agent can pick up independently, and the distinction between running an agent human-in-the-loop and running it AFK, unattended and away from the keyboard. From b2a74a84e4ea24b66e1e74ac1180d0fb1704f35a Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:44:55 +0200 Subject: [PATCH 10/12] fix: correct binding link depth and remove em dashes (final review) --- docs/plans/task1-naming-findings.md | 16 ++++++++-------- skills/building/SKILL.md | 6 +++--- skills/interview/SKILL.md | 6 +++--- skills/planning/SKILL.md | 6 +++--- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/plans/task1-naming-findings.md b/docs/plans/task1-naming-findings.md index 5a8e020..dec5dd4 100644 --- a/docs/plans/task1-naming-findings.md +++ b/docs/plans/task1-naming-findings.md @@ -12,11 +12,11 @@ SKILL_DIR_PATTERN=skills// # e.g. skills/building/, skills/planning/, AUTO_DISCOVERED=yes ``` -**`SKILL_NAME_PATTERN` does NOT default to `forge-`. It is `skills//`, i.e. no `forge-` prefix on the directory.** This did NOT fall back to the brief's stated default (`forge-` dir / `` name) — the docs directly answer the question and the directory part of that default is wrong. See "Discrepancy" below. +**`SKILL_NAME_PATTERN` does NOT default to `forge-`. It is `skills//`, i.e. no `forge-` prefix on the directory.** This did NOT fall back to the brief's stated default (`forge-` dir / `` name). The docs directly answer the question and the directory part of that default is wrong. See "Discrepancy" below. ## Evidence -### 1. Auto-discovery (brief Q1) — CONFIRMED, no fallback needed +### 1. Auto-discovery (brief Q1): CONFIRMED, no fallback needed The Claude Code skills doc's "Where skills live" table lists: @@ -24,7 +24,7 @@ The Claude Code skills doc's "Where skills live" table lists: alongside Enterprise/Personal/Project rows, with no mention of a manifest entry required for plugin skills. `plugin.json` needs no `skills` field; presence of the `skills/` directory in the plugin is sufficient. → `AUTO_DISCOVERED=yes`. -### 2. What `name:`/directory combination yields `/forge:building` (brief Q2) — CONFIRMED, differs from brief's default +### 2. What `name:`/directory combination yields `/forge:building` (brief Q2): CONFIRMED, differs from brief's default The doc's "How a skill gets its command name" table states, exactly: @@ -34,18 +34,18 @@ and separately: > The frontmatter `name` field sets the display label shown in skill listings and, except for a plugin-root `SKILL.md`, does not change what you type after `/`. -Applied to this repo: the plugin is named `forge` (per `.claude-plugin/plugin.json`, `"name": "forge"`). For a non-root plugin skill, the **directory name itself becomes the part after the colon** — Claude Code prepends the plugin's own name as the namespace automatically. It does not concatenate a `forge-` prefix already baked into the directory name. +Applied to this repo: the plugin is named `forge` (per `.claude-plugin/plugin.json`, `"name": "forge"`). For a non-root plugin skill, the **directory name itself becomes the part after the colon**. Claude Code prepends the plugin's own name as the namespace automatically. It does not concatenate a `forge-` prefix already baked into the directory name. So to get `/forge:building`: - Directory must be `skills/building/` (bare workflow name), **not** `skills/forge-building/`. - If the directory were `skills/forge-building/`, the resulting invocation would be `/forge:forge-building`, not `/forge:building`. - The `name:` frontmatter value is not what drives the invocation here (only plugin-root `SKILL.md` uses `name:` for that); but agentskills.io requires `name:` to equal the directory name anyway (see below), so `name: building` in `skills/building/SKILL.md` is both spec-compliant and consistent with the intended `/forge:building` invocation. -### 3. agentskills.io `name` field constraints (brief Q3) — CONFIRMED +### 3. agentskills.io `name` field constraints (brief Q3): CONFIRMED From the specification page's frontmatter table and `name` field section: -> `name` — Required. Max 64 characters. Lowercase letters, numbers, and hyphens only. Must not start or end with a hyphen. +> `name`: Required. Max 64 characters. Lowercase letters, numbers, and hyphens only. Must not start or end with a hyphen. > > The required `name` field: > * Must be 1-64 characters @@ -54,11 +54,11 @@ From the specification page's frontmatter table and `name` field section: > * Must not contain consecutive hyphens (`--`) > * **Must match the parent directory name** -So a colon (`:`) is **not permitted** in `name:` — `name: forge:building` would be spec-invalid. And the only two required fields are `name` and `description`; everything else (`license`, `compatibility`, `metadata`, `allowed-tools`) is optional. +So a colon (`:`) is **not permitted** in `name:`. `name: forge:building` would be spec-invalid. And the only two required fields are `name` and `description`; everything else (`license`, `compatibility`, `metadata`, `allowed-tools`) is optional. ## Discrepancy flagged for Tasks 2-9 -`docs/plans/2026-07-11-forge-skills-conversion.md` (Tasks 2-9, already committed) hardcodes `skills/forge-/SKILL.md` throughout (e.g. `skills/forge-interview/`, `skills/forge-building/`). Based on the evidence above, that directory convention produces `/forge:forge-interview`, `/forge:forge-building`, etc. — not the intended `/forge:interview`, `/forge:building`. The correct directory convention per current Claude Code docs is the bare workflow name: `skills/interview/`, `skills/planning/`, `skills/building/`, with `name: interview` / `name: planning` / `name: building` in each `SKILL.md` (matching the directory, per agentskills.io). +`docs/plans/2026-07-11-forge-skills-conversion.md` (Tasks 2-9, already committed) hardcodes `skills/forge-/SKILL.md` throughout (e.g. `skills/forge-interview/`, `skills/forge-building/`). Based on the evidence above, that directory convention produces `/forge:forge-interview`, `/forge:forge-building`, etc., not the intended `/forge:interview`, `/forge:building`. The correct directory convention per current Claude Code docs is the bare workflow name: `skills/interview/`, `skills/planning/`, `skills/building/`, with `name: interview` / `name: planning` / `name: building` in each `SKILL.md` (matching the directory, per agentskills.io). This finding is recorded here per Task 1's scope (research + decision only). Whoever executes Tasks 2-9 should use `SKILL_DIR_PATTERN=skills//` (no `forge-` prefix) instead of the plan's literal `skills/forge-/` paths, and update the plan document's Task 2-9 file paths accordingly before or during execution. diff --git a/skills/building/SKILL.md b/skills/building/SKILL.md index 234c917..874e7c2 100644 --- a/skills/building/SKILL.md +++ b/skills/building/SKILL.md @@ -6,10 +6,10 @@ disable-model-invocation: true # /forge:building -> **Harness binding.** This command is written in harness-neutral terms — role -> tiers (e.g. `judgment-tier`, `labor-tier`) and generic verbs — instead of one +> **Harness binding.** This command is written in harness-neutral terms: role +> tiers (e.g. `judgment-tier`, `labor-tier`) and generic verbs, instead of one > agent's tool and model names. Before acting, load the binding for your -> environment from [`docs/harness-bindings/`](../docs/harness-bindings/README.md) +> environment from [`docs/harness-bindings/`](../../docs/harness-bindings/README.md) > and resolve every neutral term to the concrete tool or model it names; when > the prose spawns a subagent of a given tier, use the model the binding maps > that tier to. diff --git a/skills/interview/SKILL.md b/skills/interview/SKILL.md index 1e726d2..b40c24f 100644 --- a/skills/interview/SKILL.md +++ b/skills/interview/SKILL.md @@ -6,10 +6,10 @@ disable-model-invocation: true # /forge:interview -> **Harness binding.** This command is written in harness-neutral terms — role -> tiers (e.g. `judgment-tier`, `labor-tier`) and generic verbs — instead of one +> **Harness binding.** This command is written in harness-neutral terms: role +> tiers (e.g. `judgment-tier`, `labor-tier`) and generic verbs, instead of one > agent's tool and model names. Before acting, load the binding for your -> environment from [`docs/harness-bindings/`](../docs/harness-bindings/README.md) +> environment from [`docs/harness-bindings/`](../../docs/harness-bindings/README.md) > and resolve every neutral term to the concrete tool or model it names; when > the prose spawns a subagent of a given tier, use the model the binding maps > that tier to. diff --git a/skills/planning/SKILL.md b/skills/planning/SKILL.md index b4a340d..be9068b 100644 --- a/skills/planning/SKILL.md +++ b/skills/planning/SKILL.md @@ -6,10 +6,10 @@ disable-model-invocation: true # /forge:planning -> **Harness binding.** This command is written in harness-neutral terms — role -> tiers (e.g. `judgment-tier`, `labor-tier`) and generic verbs — instead of one +> **Harness binding.** This command is written in harness-neutral terms: role +> tiers (e.g. `judgment-tier`, `labor-tier`) and generic verbs, instead of one > agent's tool and model names. Before acting, load the binding for your -> environment from [`docs/harness-bindings/`](../docs/harness-bindings/README.md) +> environment from [`docs/harness-bindings/`](../../docs/harness-bindings/README.md) > and resolve every neutral term to the concrete tool or model it names; when > the prose spawns a subagent of a given tier, use the model the binding maps > that tier to. From 1173b03e2f7888c4ce0b509ca7dd4cd7a227a928 Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:49:34 +0200 Subject: [PATCH 11/12] docs: update harness bindings to reference skills, not commands The command->skill conversion renamed commands/*.md to skills/*/SKILL.md; the binding docs still pointed at the old command paths. Retarget them at the skills and reword 'command' to 'skill' throughout. --- docs/harness-bindings/README.md | 6 +++--- docs/harness-bindings/claude-code.md | 8 ++++---- docs/harness-bindings/pi.md | 12 ++++++------ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/harness-bindings/README.md b/docs/harness-bindings/README.md index 7fccb88..e16131d 100644 --- a/docs/harness-bindings/README.md +++ b/docs/harness-bindings/README.md @@ -1,10 +1,10 @@ # Harness bindings -Forge's commands (`commands/*.md`) are written in harness-neutral prose: role +Forge's skills (`skills/*/SKILL.md`) are written in harness-neutral prose: role tiers (`judgment-tier`, `labor-tier`) and generic verbs, instead of any one agent's tool and model names. Each supported harness has a binding doc in this directory that maps those neutral terms to its concrete tools and models. A -command's **Harness binding** preamble points the running agent at the binding +skill's **Harness binding** preamble points the running agent at the binding for its environment; the agent resolves every neutral term through it before acting. @@ -16,7 +16,7 @@ compare harnesses, read this file. - [`claude-code.md`](claude-code.md) — Claude Code. Judgment/labor tiers map to distinct models (opus / sonnet); binding is an exact-string substitution - (a round-trip check verifies it reconstructs the original command text). + (a round-trip check verifies it reconstructs the original skill text). - [`pi.md`](pi.md) — Pi with open-weight models. Every tier maps to one model (GLM-5.2); binding is semantic (the agent interprets each phrase). Requires forked-context subagents (`pi-subagents`); the `native` evaluation surface is diff --git a/docs/harness-bindings/claude-code.md b/docs/harness-bindings/claude-code.md index a05c0d0..6488ea8 100644 --- a/docs/harness-bindings/claude-code.md +++ b/docs/harness-bindings/claude-code.md @@ -1,13 +1,13 @@ # Claude Code binding -Forge's commands (`commands/interview.md`, `commands/planning.md`, -`commands/building.md`) are written in harness-neutral prose: instead of +Forge's skills (`skills/interview/SKILL.md`, `skills/planning/SKILL.md`, +`skills/building/SKILL.md`) are written in harness-neutral prose: instead of naming a specific coding agent's tools or models, they use neutral verbs and tier markers that each harness binds to its own equivalents. This doc is the Claude Code binding: it maps every neutral phrase used in the prose to the concrete Claude Code tool or model tier that realizes it. -| Neutral phrase (in commands/) | Claude Code binding | +| Neutral phrase (in skills/) | Claude Code binding | | --- | --- | | judgment-tier | opus | | labor-tier | sonnet | @@ -16,7 +16,7 @@ the concrete Claude Code tool or model tier that realizes it. | exploratory | Explore | | a working scratch location | the session scratchpad directory | -Every left-hand phrase is the exact string as it appears in `commands/`; +Every left-hand phrase is the exact string as it appears in the skills; binding is a literal substitution of that phrase by its right-hand value. The `exploratory` row is a single-word swap: it marks Claude Code's read-only **Explore** subagent, and substitution replaces only the word diff --git a/docs/harness-bindings/pi.md b/docs/harness-bindings/pi.md index 83810e2..07da8bb 100644 --- a/docs/harness-bindings/pi.md +++ b/docs/harness-bindings/pi.md @@ -1,20 +1,20 @@ # Pi binding -Forge's commands (`commands/interview.md`, `commands/planning.md`, -`commands/building.md`) are written in harness-neutral prose: instead of naming +Forge's skills (`skills/interview/SKILL.md`, `skills/planning/SKILL.md`, +`skills/building/SKILL.md`) are written in harness-neutral prose: instead of naming a specific coding agent's tools or models, they use neutral verbs and tier markers that each harness binds to its own equivalents. This doc is the Pi binding: it maps every neutral phrase used in the prose to the Pi coding agent's concrete tool or, for tiers, the open-weight model that realizes it. -| Neutral phrase (in commands/) | Pi binding | +| Neutral phrase (in skills/) | Pi binding | | --- | --- | | judgment-tier | GLM-5.2 | | labor-tier | GLM-5.2 | | an interactive question | a plain chat question to the user, options listed with the recommended one first | | the worktree helper | `git worktree add` | | exploratory | a read-only subagent (the `subagent` tool from `pi-subagents`, forked context) | -| a working scratch location | a `harness/` directory inside the worktree, git-excluded (per the building command's `.git/info/exclude` step) | +| a working scratch location | a `harness/` directory inside the worktree, git-excluded (per the building skill's `.git/info/exclude` step) | Read each neutral phrase in the prose as the Pi tool, model, or behaviour its right-hand value describes, and realise that behaviour: this is a semantic @@ -50,8 +50,8 @@ if the package is absent, stop and say so rather than faking the separation. ## Evaluation surfaces (not yet neutralized) -The `web` / `native` / browser-MCP lines in `commands/building.md` were not -neutralized (that was out of scope for the command neutralization), so they +The `web` / `native` / browser-MCP lines in `skills/building/SKILL.md` were not +neutralized (that was out of scope for the neutralization), so they still name tools that may not exist on Pi. Read them as follows until that follow-up lands: From d09d0d5a25584bb13223bd414a5cff4da49fa30c Mon Sep 17 00:00:00 2001 From: Daniel Sierra Date: Sat, 11 Jul 2026 14:49:58 +0200 Subject: [PATCH 12/12] docs: remove em dashes from pi and bindings README --- docs/harness-bindings/README.md | 4 ++-- docs/harness-bindings/pi.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/harness-bindings/README.md b/docs/harness-bindings/README.md index e16131d..b9aa876 100644 --- a/docs/harness-bindings/README.md +++ b/docs/harness-bindings/README.md @@ -14,10 +14,10 @@ compare harnesses, read this file. ## Available bindings -- [`claude-code.md`](claude-code.md) — Claude Code. Judgment/labor tiers map to +- [`claude-code.md`](claude-code.md): Claude Code. Judgment/labor tiers map to distinct models (opus / sonnet); binding is an exact-string substitution (a round-trip check verifies it reconstructs the original skill text). -- [`pi.md`](pi.md) — Pi with open-weight models. Every tier maps to one model +- [`pi.md`](pi.md): Pi with open-weight models. Every tier maps to one model (GLM-5.2); binding is semantic (the agent interprets each phrase). Requires forked-context subagents (`pi-subagents`); the `native` evaluation surface is unavailable. diff --git a/docs/harness-bindings/pi.md b/docs/harness-bindings/pi.md index 07da8bb..449865c 100644 --- a/docs/harness-bindings/pi.md +++ b/docs/harness-bindings/pi.md @@ -28,7 +28,7 @@ the description into the sentence. Pi runs GLM-5.2 for every role; both `judgment-tier` and `labor-tier` map to it. The judgment/labor distinction stays legible in the prose but does not -drive model selection here — there is no separate tier to select. What makes a +drive model selection here; there is no separate tier to select. What makes a subagent an adversary is its separated context, not its weights, so a single model does not weaken the design on its own. @@ -40,7 +40,7 @@ criterion on the first round. ## Required: forked-context subagents -Forge's adversaries — planner vs critic, generator vs evaluator — only work +Forge's adversaries (planner vs critic, generator vs evaluator) only work because each cannot see the other's reasoning. Pi core ships no subagent tool; the `pi-subagents` package provides one with forked contexts. **Do not run forge on a Pi without it.** Without forked contexts the pipeline collapses into