diff --git a/.agents/skills/agent-review/SKILL.md b/.agents/skills/agent-review/SKILL.md index cfcf554..d82539d 100644 --- a/.agents/skills/agent-review/SKILL.md +++ b/.agents/skills/agent-review/SKILL.md @@ -42,10 +42,10 @@ Review plans and diffs against the conventions in `AGENTS.md`. ## PR Comment Review -Use the `github-pr-comments` skill and run: +Use the `/change-request-comments` skill and run: ```bash -bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh [] +bash .agents/skills/change-request-comments/scripts/fetch-comments.sh [] [--comments-only|--diff-only|--json] ``` Inline comments must be checked against the current diff before accepting or dismissing them. Outdated inline comments may still be valid; verify technical claims before dismissing. @@ -54,7 +54,7 @@ Inline comments must be checked against the current diff before accepting or dis Use this standard self-maintenance review workflow unless the user explicitly requests a different scope: -1. Read open PR comments first by using the `github-pr-comments` skill. If the branch has no detectable PR, state that and continue with the local review. +1. Read open PR comments first by using `/change-request-comments`. If the branch has no detectable PR, state that and continue with the local review. 2. Identify whether the review target is a spec/plan or a diff. If multiple candidate plans exist and the user did not state which one to use, ask before continuing the plan-conformance part of the review. 3. Review the target yourself against `AGENTS.md`, repository conventions, sync invariants, and any approved spec or plan. 4. Combine PR comments, user notes, external notes, and your own findings into one deduplicated list of actionable issues. diff --git a/.agents/skills/change-request-comments b/.agents/skills/change-request-comments new file mode 120000 index 0000000..b23a4b8 --- /dev/null +++ b/.agents/skills/change-request-comments @@ -0,0 +1 @@ +../../core/agents/skills/change-request-comments \ No newline at end of file diff --git a/.agents/skills/change-request-publish b/.agents/skills/change-request-publish new file mode 120000 index 0000000..38848a9 --- /dev/null +++ b/.agents/skills/change-request-publish @@ -0,0 +1 @@ +../../core/agents/skills/change-request-publish \ No newline at end of file diff --git a/.agents/skills/git-publish b/.agents/skills/git-publish new file mode 120000 index 0000000..a982c29 --- /dev/null +++ b/.agents/skills/git-publish @@ -0,0 +1 @@ +../../core/agents/skills/git-publish \ No newline at end of file diff --git a/.agents/skills/github-pr-comments b/.agents/skills/github-pr-comments deleted file mode 120000 index bc49c89..0000000 --- a/.agents/skills/github-pr-comments +++ /dev/null @@ -1 +0,0 @@ -../../core/agents/skills/github-pr-comments \ No newline at end of file diff --git a/.claude/skills/change-request-comments b/.claude/skills/change-request-comments new file mode 120000 index 0000000..6ac6468 --- /dev/null +++ b/.claude/skills/change-request-comments @@ -0,0 +1 @@ +../../.agents/skills/change-request-comments \ No newline at end of file diff --git a/.claude/skills/change-request-publish b/.claude/skills/change-request-publish new file mode 120000 index 0000000..d7afb8d --- /dev/null +++ b/.claude/skills/change-request-publish @@ -0,0 +1 @@ +../../.agents/skills/change-request-publish \ No newline at end of file diff --git a/.claude/skills/git-publish b/.claude/skills/git-publish new file mode 120000 index 0000000..8b6fd6a --- /dev/null +++ b/.claude/skills/git-publish @@ -0,0 +1 @@ +../../.agents/skills/git-publish \ No newline at end of file diff --git a/.claude/skills/github-pr-comments b/.claude/skills/github-pr-comments deleted file mode 120000 index 5a4757c..0000000 --- a/.claude/skills/github-pr-comments +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/github-pr-comments \ No newline at end of file diff --git a/.opencode/agents/implement-task.md b/.opencode/agents/implement-task.md index 222b2a7..a83a1a5 100644 --- a/.opencode/agents/implement-task.md +++ b/.opencode/agents/implement-task.md @@ -59,7 +59,7 @@ permission: "rm -rf .temp/*": allow "rm -rf .temp": allow # Protective rules are last because opencode uses last-match-wins permissions - "git push *": deny + "git push*": deny "git branch -d *": deny "git branch -D *": deny "git worktree remove *": deny diff --git a/.opencode/agents/implement.md b/.opencode/agents/implement.md index 38751ef..7ed0404 100644 --- a/.opencode/agents/implement.md +++ b/.opencode/agents/implement.md @@ -7,7 +7,6 @@ permission: bash: "*": ask # GitHub PR operations - "gh pr create *": allow "gh pr list *": allow "gh pr view *": allow # Git write and inspection operations @@ -27,6 +26,9 @@ permission: "git pull *": allow "git show *": allow "git status *": allow + # Push changes and open PR + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/change-request-publish/scripts/open-change-request.sh*": allow # File read and inspection "cat *": allow "diff *": allow @@ -61,9 +63,6 @@ permission: "codespell *": allow "rm -rf .temp/*": allow "rm -rf .temp": allow - # Publishing goes through the branch-safety helper - "scripts/publish-branch.sh": allow - "bash scripts/publish-branch.sh": allow # Protective rules are last because opencode uses last-match-wins permissions "git branch -d *": deny "git branch -D *": deny @@ -78,23 +77,26 @@ permission: "*": deny "agent-implementation": allow "agent-verification": allow + "git-publish": allow + "change-request-publish": allow --- You are the implementation controller for this toolkit repo. -Use the `agent-implementation` skill for orchestration. Use `agent-verification` before any completion claim. +Use /agent-implementation for orchestration. Use /agent-verification before any completion claim. Execution rules: - Work from a scoped branch before editing. - Prefer `git mv` for moves/renames, `git rm` for removals of tracked paths. -- Use `scripts/publish-branch.sh` for publishing and PR creation. Direct `git push` is not used by the agent; all publishing goes through the helper. +- Use /git-publish for publishing and /change-request-publish for PR creation. +Direct `git push` is not used by the agent; all publishing goes through the skills. - Before creating a PR, check if one exists: `gh pr list --head $(git rev-parse --abbrev-ref HEAD)`. - Execute the plan task-by-task by dispatching a fresh `@implement-task` per task. - Review each worker's report and diff before moving on. - Continue between tasks without routine approval pauses. - Stop only for: BLOCKED status, genuine ambiguity, or all tasks complete. -- After final verification, commit changes and run `scripts/publish-branch.sh`. +- After final verification, commit changes and invoke /git-publish. Verification for this repo: diff --git a/.opencode/agents/planning.md b/.opencode/agents/planning.md index 937de20..fef0c13 100644 --- a/.opencode/agents/planning.md +++ b/.opencode/agents/planning.md @@ -14,6 +14,8 @@ permission: "wc *": allow "date *": allow "which *": allow + # Verification + "codespell *": allow # Directory creation for plans "mkdir plans/*": allow "mkdir -p plans/*": allow @@ -27,34 +29,32 @@ permission: "git branch *": allow "git checkout *": allow "git commit *": allow - # Publish helper - "codespell *": allow - "scripts/publish-branch.sh": allow - "bash scripts/publish-branch.sh": allow + # Push changes and open PR + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/change-request-publish/scripts/open-change-request.sh*": allow # Protective denials are last because opencode uses last-match-wins permissions "git branch -d *": deny "git branch -D *": deny "git worktree remove *": deny - "git push --force *": deny - "git push -f *": deny - "git push origin --force *": deny - "git push origin -f *": deny - "git push origin main*": deny - "git push origin +main*": deny + # Push is done via git-publish + "git push*": deny task: "*": deny "explore": allow skill: "*": deny "agent-planning": allow + "change-request-publish": allow + "git-publish": allow "writing-skills": allow --- You are the planning agent for this toolkit repo. -Load the approved spec first. Use the `agent-planning` skill to write the plan. +Load the approved spec first. +Use the /agent-planning skill to write the plan. -Your task is to write implementation plans to `plans/YYYY-MM-DD-feature-name/plan.md` next to the spec. +Your task is to write implementation plans to `plans/YYYY-MM-DD-feature-name/plan.md`. Plan requirements: @@ -67,4 +67,5 @@ Plan requirements: Use `@explore` when you need to investigate before planning. Continue after the context is strong enough to support the plan. -After writing the plan, commit the plan file, run `scripts/publish-branch.sh` to push the branch and create or report the PR, and report the plan path, PR URL, and any open questions. +After writing the plan, commit the plan file, invoke /git-publish to push the branch and /change-request-publish to create a PR. +Report the plan path, PR URL, and any open questions. diff --git a/.opencode/agents/review.md b/.opencode/agents/review.md index b1e13d4..66d47aa 100644 --- a/.opencode/agents/review.md +++ b/.opencode/agents/review.md @@ -9,11 +9,14 @@ permission: bash: "*": ask # File read and inspection + "find *": allow "ls": allow "ls *": allow - "ls .temp/*": allow - "find .temp/*": allow "grep *": allow + "rg *": allow + "sort *": allow + # Json parsing + "jq *": allow # Git inspection "git branch --show-current": allow "git diff *": allow @@ -23,24 +26,30 @@ permission: "git show *": allow "git branch": allow "git branch *": allow - # PR comment fetching (read-only helper) - "bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh *": allow - 'bash ".agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh" *': allow + # Review-plan edit/commit/publish permissions + "git add plans/*": allow + "git commit *": allow + # Push changes + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + 'bash ".agents/skills/git-publish/scripts/push-branch.sh*"': allow + # GH pr inspection + "gh pr view *": allow + # PR comment fetching and replying (read-only + ask for mutations) + "bash .agents/skills/change-request-comments/scripts/fetch-comments.sh *": allow + 'bash ".agents/skills/change-request-comments/scripts/fetch-comments.sh" *': allow + "bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh *": ask + 'bash ".agents/skills/change-request-comments/scripts/reply-to-comment.sh" *': ask # Verification commands "shellcheck *": allow "bash -n *": allow "codespell *": allow - # Review-plan edit/commit/publish permissions - "git add plans/*": allow - "git commit *": allow - "scripts/publish-branch.sh": allow "bash scripts/publish-branch.sh": allow # Protective rules are last because opencode uses last-match-wins permissions "git branch -d *": deny "git branch -D *": deny "git worktree remove *": deny - "git push *": ask - "git push origin main*": deny + "git push": deny + "git push *": deny task: "*": deny "explore": allow @@ -48,20 +57,27 @@ permission: "*": deny "agent-review": allow "agent-verification": allow - "github-pr-comments": allow + "change-request-comments": allow + "git-publish": allow --- You are the review agent for this toolkit repo. -Use the `agent-review` skill for the review checklist. Use `agent-verification` for evidence-based claims. Use `github-pr-comments` when reviewing PR remarks or PR diffs. +Use /agent-review for the review checklist. +Use /agent-verification for evidence-based claims. +Use /change-request-comments when reviewing PR remarks or PR diffs. +Use /git-publish to push a changed or review remarks plan. Review goals: - For plans: spec coverage, file mapping, core/stack ripple, sync invariants, no placeholders, verification commands. - For diffs: plan conformance, SKILL.md/agent frontmatter, symlinks, lockfile consistency, README accuracy, shellcheck results, git conventions. -Combine your findings into one deduplicated, prioritized list. Separate blocking issues from advisory suggestions. Cite file paths and sections. +Combine your findings into one deduplicated, prioritized list. +Separate blocking issues from advisory suggestions. +Cite file paths and sections. -Review and report findings without editing files directly. Provide suggested fixes when useful. +Review and report findings without editing files directly. +Provide suggested fixes when useful. -Use `@explore` when you need to investigate before judging a finding. +Use @explore when you need to investigate before judging a finding. diff --git a/AGENTS.md b/AGENTS.md index 474766e..544b0cf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,6 +62,16 @@ user-invocable: false # for skills only loaded by agents, not users OpenCode is configured to read skills from `.agents/skills//SKILL.md`. Each skill is symlinked into `.claude/skills/` (`ln -s ../../.agents/skills/`) so Claude Code also sees it. This matches the model `copy.sh` uses for authored skills. +## Skill Boundary Rule + +Workflow-facing files (SKILL.md bodies, agent instructions, README workflow sections) must instruct agents to **use skills by slash reference** — for example /git-publish, /change-request-publish, /change-request-comments, or /issue-tracker — rather than exposing internal implementation paths. + +**Do not** reference internal script paths like `.agents/skills//scripts/*.sh` or `.claude/skills//scripts/*.sh` in workflow-facing content. Boundary-skill implementation details may live in that skill's own scripts or sibling `README.md`; keep the `SKILL.md` focused on the invocation contract. + +Agent frontmatter and Claude settings may grant script permissions, but body instructions should stay at the skill level, not the script level. + +**Provider-boundary language:** Provider-specific CLI details (`gh`, `glab`, provider comment scripts) belong only in provider or boundary skills, not in generic workflow files. Generic workflow files should remain provider-agnostic. + ## .temp/ Scratch Rule All smoke runs and throwaway work goes in `.temp/`, never `/tmp`. Clean up after use. `.temp/` is gitignored. @@ -72,7 +82,7 @@ All smoke runs and throwaway work goes in `.temp/`, never `/tmp`. Clean up after - Branches use `feature/`, `fix/`, or `chore/` prefixes with short kebab-case descriptions. - Commits are concise, imperative, and focused on why. - Prefer `git mv` for moves/renames of tracked paths. Use `git rm` for removals of tracked paths. -- Use `scripts/publish-branch.sh` for branch publishing and PR creation so branch-safety checks are centralized. +- Use `git-publish` for branch pushes and `change-request-publish` for PR/MR creation so branch-safety checks are centralized. - Keep branch deletion, worktree removal, and force-push operations under explicit human control. - Write commits without `Co-Authored-By` trailers. diff --git a/README.md b/README.md index 88bbe28..75e668f 100644 --- a/README.md +++ b/README.md @@ -95,13 +95,13 @@ Under `.opencode/agents/`: Under `.claude/skills/` (all marked `disable-model-invocation: true`): -`brainstorm`, `bugfix`, `finish`, `planner`, `review-code`, `review-plan` +`brainstorm`, `bugfix`, `implement`, `finish`, `planner`, `review-code`, `review-plan` ### Authored Reusable Skills Under `.agents/skills/`: -`grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, `workflow-implementation`, `workflow-verification`, `feature-documentation`, `github-pr-comments` +`grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, `workflow-implementation`, `workflow-verification`, `feature-documentation`, `git-publish`, `change-request-publish`, `change-request-comments`, `issue-tracker` ### DDD Docs @@ -126,7 +126,7 @@ Under `docs/agents/` — per-agent loading contracts. See the individual files f ## Claude Symlink Model -The 6 workflow entry skills are real directories. The 7 authored skills are symlinked into `.claude/skills/` from `.agents/skills/`: +The workflow entry skills are real directories. The authored skills are symlinked into `.claude/skills/` from `.agents/skills/`: | Skill | Symlink Target | |---|---| @@ -136,12 +136,14 @@ The 6 workflow entry skills are real directories. The 7 authored skills are syml | `workflow-planning` | `../../.agents/skills/workflow-planning` | | `workflow-verification` | `../../.agents/skills/workflow-verification` | | `feature-documentation` | `../../.agents/skills/feature-documentation` | -| `github-pr-comments` | `../../.agents/skills/github-pr-comments` | +| `git-publish` | `../../.agents/skills/git-publish` | +| `change-request-publish` | `../../.agents/skills/change-request-publish` | +| `change-request-comments` | `../../.agents/skills/change-request-comments` | +| `issue-tracker` | `../../.agents/skills/issue-tracker` | +| `workflow-implementation` | `../../.agents/skills/workflow-implementation` | Each symlinked skill has `user-invocable: false` in its `SKILL.md` frontmatter so it stays hidden from Claude Code's user-facing skill list. -> **Note:** `workflow-implementation` is **not** symlinked into Claude — it is reserved for OpenCode's `@implement` / `@implement-task` flow. - ## Recommended Human-in-the-Loop Workflow ![Workflow diagram showing the human-in-the-loop cycle: ideation and planning (steps 1–6) followed by an implementation cycle (steps 7–11). Blue icons represent human input; robot icons represent LLM agents; purple steps are think-and-plan; orange steps are reviews; green is implementation; dark is finish.](workflow.PNG) @@ -163,8 +165,8 @@ The diagram above maps the full cycle from brainstorming a feature through to sh | # | Step | Invocation | |---|---|---| -| 7 | **Implement task-by-task** — dispatch one worker per plan task, verify, and commit. | OpenCode `@implement` (controller) which spawns `@implement-task` workers. | -| 8 | **Human comments code** — review the diff and leave inline comments or GitHub review notes. | Human checkpoint (no agent invocation). | +| 7 | **Implement task-by-task** — dispatch one worker per plan task, verify, and commit. | Claude `/implement` or OpenCode `@implement` (controller) which spawns implement-task workers. Pick one per task. | +| 8 | **Human comments code** — review the diff and leave inline comments or change-request review notes. | Human checkpoint (no agent invocation). | | 9 | **Review code** — analyze review feedback and determine required changes. | OpenCode `@review-code` or Claude `/review-code`. | | 10 | **Review / Remark plan** — if code review surfaces scope changes, update the plan and loop back to step 7. Repeat until the plan is sound and complete. | OpenCode `@planner` or Claude `/planner` to revise; then re-enter implementation at step 7. | | 11 | **Finish** — write summary and feature documentation, reconcile durable docs (ADRs, context maps, etc.). | OpenCode `@finish` or Claude `/finish`. | @@ -218,10 +220,12 @@ Under `.agents/skills/` (symlinked into `.claude/skills/` for Claude Code compat | `agent-implementation` | Controller orchestration, worker status handling, completion | | `agent-verification` | Evidence-before-claims gate, verification commands, smoke runs | | `agent-review` | Plan + diff review checklist aligned to `AGENTS.md` | -| `github-pr-comments` | PR comment fetching, classification, and reply workflow | +| `change-request-comments` | Comment fetching, classification, and reply workflow | +| `git-publish` | Branch push with safety guards (refuses main/master, force-push) | +| `change-request-publish` | PR/MR creation with idempotency (skips when one exists) | | `writing-skills` | Remote skill from `obra/superpowers` for authoring skills | -Invoke in Claude Code with `/agent-planning`, `/agent-implementation`, `/agent-verification`, `/agent-review`, or `/github-pr-comments`. The `writing-skills` skill is also exposed as `/writing-skills`. +Invoke in Claude Code with `/agent-planning`, `/agent-implementation`, `/agent-verification`, `/agent-review`, `/change-request-comments`, `/git-publish`, or `/change-request-publish`. The `writing-skills` skill is also exposed as `/writing-skills`. ### Lockfiles diff --git a/core/agents/skills/change-request-comments/README.md b/core/agents/skills/change-request-comments/README.md new file mode 100644 index 0000000..4bd58a7 --- /dev/null +++ b/core/agents/skills/change-request-comments/README.md @@ -0,0 +1,27 @@ +# change-request-comments — Design Rationale + +## Why a host-agnostic comment skill exists + +Pull request and merge request comments have different APIs, data models, and CLI tools on GitHub (`gh`) and GitLab (`glab`). A host-agnostic skill lets agents read and reply to change-request comments without needing to know which provider hosts the repository. + +## Host detection + +The scripts read `git remote get-url origin` to determine the host: + +- GitHub → uses `gh` CLI and GraphQL to fetch issue comments, inline review threads, and PR diff. +- GitLab → uses `glab` CLI to fetch discussions and MR diff. + +The PR/MR number can be auto-detected from the current branch when omitted. + +## Provider-specific reply formats + +The reply payload format differs between providers: + +- GitHub replies target `comment_id` (a numeric ID) and are posted via `gh api`. +- GitLab replies target `discussion_id` (a string ID) and are posted inside existing discussion threads via `glab mr note create`. + +The `reply-to-comment.sh` script abstracts these differences behind a unified JSON interface. + +## Why provider-specific CLIs should not be called directly from workflow files + +Calling `gh` or `glab` directly from workflow files couples those files to a specific provider. The comment skill handles host detection and format translation, so workflow instructions should tell agents to use the `change-request-comments` skill by name. diff --git a/core/agents/skills/change-request-comments/SKILL.md b/core/agents/skills/change-request-comments/SKILL.md new file mode 100644 index 0000000..6f05cb1 --- /dev/null +++ b/core/agents/skills/change-request-comments/SKILL.md @@ -0,0 +1,53 @@ +--- +name: change-request-comments +description: Use when reading or replying to pull request or merge request comments — detects GitHub vs GitLab and handles both inline +user-invocable: false +--- + +# Change Request Comments + +Host-agnostic interface for reading and replying to change-request (PR/MR) comments. The scripts detect whether the repository is on GitHub or GitLab and handle both providers directly. + +## Fetch comments + +```bash +bash .agents/skills/change-request-comments/scripts/fetch-comments.sh [] [--comments-only|--diff-only|--json] +``` + +- **Host detection**: reads `git remote get-url origin` to determine the host. + - GitHub: uses `gh` CLI and GraphQL to fetch issue comments, inline review threads, and PR diff. + - GitLab: uses `glab` CLI to fetch discussions and MR diff. +- When `` is omitted, the script auto-detects the PR/MR number from the current branch. + +Optional modes: + +- `--comments-only` — print comment metadata only, omit the diff. +- `--diff-only` — print the diff only, omit comments. +- `--json` — output a single JSON object with all data (PR/MR metadata, comments, discussions). + +## Reply to a comment + +```bash +bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh [] '' +``` + +- **Host detection**: reads `git remote get-url origin` to determine the host. + - GitHub: posts replies to inline PR review comments via `gh api`. Reply JSON uses `comment_id` (number). + - GitLab: posts replies inside existing discussion threads via `glab mr note create`. Reply JSON uses `discussion_id` (string). +- The PR/MR number is optional; when omitted, the script auto-detects it from the current branch. + +Reply payload format: + +```bash +# GitHub +bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh '' '[ + { "comment_id": 123456, "body": "Fixed in abc123." }, + { "comment_id": 789012, "body": "Good catch, updated." } +]' + +# GitLab +bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh '' '[ + { "discussion_id": "a1b2c3d4...", "body": "Fixed in abc123." }, + { "discussion_id": "e5f6g7h8...", "body": "Good catch, updated." } +]' +``` diff --git a/core/agents/skills/change-request-comments/scripts/fetch-comments.sh b/core/agents/skills/change-request-comments/scripts/fetch-comments.sh new file mode 100755 index 0000000..8451263 --- /dev/null +++ b/core/agents/skills/change-request-comments/scripts/fetch-comments.sh @@ -0,0 +1,203 @@ +#!/usr/bin/env bash +set -euo pipefail + +remote_url="$(git remote get-url origin)" + +case "$remote_url" in + *github.com*) + + if [ "$#" -gt 2 ]; then + printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 + exit 2 + fi + + if [[ "${1:-}" =~ ^[0-9]+$ ]]; then + pr="$1" + mode="${2:-}" + else + pr="$(gh pr view --json number --jq '.number')" + mode="${1:-}" + fi + case "$mode" in + ""|--comments-only|--diff-only|--json) ;; + *) + printf 'Unknown mode: %s\n' "$mode" >&2 + printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 + exit 2 + ;; + esac + + if [ "$mode" = "--diff-only" ]; then + gh pr diff "$pr" + exit 0 + fi + + pr_json="$(gh pr view "$pr" --json number,title,headRefName,baseRefName,url,comments,headRepository)" + repo=$(jq -r '.headRepository.nameWithOwner' <<<"$pr_json") + issue_comments_json="$(gh api "repos/$repo/issues/$pr/comments")" + # shellcheck disable=SC2016 # GraphQL variables ($owner, $name, $pr) are intentionally single-quoted + inline_comments_json="$(gh api graphql \ + -F owner="${repo%%/*}" \ + -F name="${repo#*/}" \ + -F pr="$pr" \ + -f query='query($owner: String!, $name: String!, $pr: Int!) { + repository(owner: $owner, name: $name) { + pullRequest(number: $pr) { + reviewThreads(first: 100) { + nodes { + isResolved + comments(first: 100) { + nodes { + id + databaseId + path + line + originalLine + body + createdAt + updatedAt + url + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + }' \ + --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved | not) | .comments.nodes[]]')" + + if [ "$mode" = "--json" ]; then + PR_JSON="$pr_json" ISSUE_COMMENTS_JSON="$issue_comments_json" INLINE_COMMENTS_JSON="$inline_comments_json" node <<'JS' +const payload = { + pr: JSON.parse(process.env.PR_JSON ?? '{}'), + issueComments: JSON.parse(process.env.ISSUE_COMMENTS_JSON ?? '[]'), + inlineComments: JSON.parse(process.env.INLINE_COMMENTS_JSON ?? '[]'), +}; + +console.log(JSON.stringify(payload, null, 2)); +JS + exit 0 + fi + + printf '## Comment Summary\n' + PR_JSON="$pr_json" ISSUE_COMMENTS_JSON="$issue_comments_json" INLINE_COMMENTS_JSON="$inline_comments_json" node <<'JS' +const pr = JSON.parse(process.env.PR_JSON ?? '{}'); +const issueComments = JSON.parse(process.env.ISSUE_COMMENTS_JSON ?? '[]'); +const inlineComments = JSON.parse(process.env.INLINE_COMMENTS_JSON ?? '[]'); + +const summarize = (body) => { + const firstLine = String(body ?? '').replace(/\s+/g, ' ').trim(); + return firstLine.length > 180 ? `${firstLine.slice(0, 177)}...` : firstLine; +}; + +console.log(`PR #${pr.number}: ${pr.title}`); +console.log(`Branch: ${pr.headRefName} -> ${pr.baseRefName}`); +console.log(`URL: ${pr.url}`); +console.log(''); + +console.log(`Issue comments (${issueComments.length})`); +for (const comment of issueComments) { + console.log(`- id=${comment.id} author=${comment.user?.login ?? 'unknown'} updated=${comment.updated_at}: ${summarize(comment.body)}`); +} +console.log(''); + +console.log(`Unresolved inline comments (${inlineComments.length})`); +for (const comment of inlineComments) { + const location = `${comment.path}:${comment.line ?? comment.originalLine ?? '?'}`; + console.log(`- databaseId=${comment.databaseId} ${location} updated=${comment.updatedAt}: ${summarize(comment.body)}`); +} +JS + + printf '\n## PR\n' + printf '%s\n' "$pr_json" + + printf '\n## Issue Comments\n' + printf '%s\n' "$issue_comments_json" + + printf '\n## Inline PR Comments\n' + printf '%s\n' "$inline_comments_json" + + if [ "$mode" = "--comments-only" ]; then + exit 0 + fi + + printf '\n## Diff\n' + gh pr diff "$pr" + ;; + + *) + + if [ "$#" -gt 2 ]; then + printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 + exit 2 + fi + + if [[ "${1:-}" =~ ^[0-9]+$ ]]; then + mr="$1" + mode="${2:-}" + else + mr="$(glab mr view -F json | jq -r '.iid')" + mode="${1:-}" + fi + case "$mode" in + ""|--comments-only|--diff-only|--json) ;; + *) + printf 'Unknown mode: %s\n' "$mode" >&2 + printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 + exit 2 + ;; + esac + + if [ "$mode" = "--diff-only" ]; then + glab mr diff "$mr" + exit 0 + fi + + mr_json="$(glab mr view "$mr" -F json)" + discussions_json="$(glab api --paginate "projects/:id/merge_requests/$mr/discussions?per_page=100")" + + if [ "$mode" = "--json" ]; then + jq -n \ + --argjson mr "$mr_json" \ + --argjson discussions "$discussions_json" \ + '{ mr: $mr, discussions: $discussions }' + exit 0 + fi + + printf '## Note Summary\n' + jq -r \ + --argjson mr "$mr_json" ' + def summarize: (. // "" | gsub("\\s+"; " ") | .[0:180]); + "MR !\($mr.iid): \($mr.title)", + "Branch: \($mr.source_branch) -> \($mr.target_branch)", + "URL: \($mr.web_url)", + "", + "Standalone conversation notes (\([.[] | select(.individual_note == true)] | length)):", + ( .[] | select(.individual_note == true) | .notes[] | select(.system == false) + | "- discussion_id=\(.id // "?") note=\(.id) author=\(.author.username) updated=\(.updated_at): \(.body | summarize)" ), + "", + "Unresolved inline diff discussions:", + ( .[] | select(.individual_note != true) | . as $d + | select([.notes[] | select(.resolvable == true and .resolved == false)] | length > 0) + | .notes[0] as $n + | "- discussion_id=\($d.id) \($n.position.new_path // $n.position.old_path // "?"):\($n.position.new_line // $n.position.old_line // "?") author=\($n.author.username) updated=\($n.updated_at): \($n.body | summarize)" ) + ' <<<"$discussions_json" + + printf '\n## MR\n' + printf '%s\n' "$mr_json" + + printf '\n## Discussions\n' + printf '%s\n' "$discussions_json" + + if [ "$mode" = "--comments-only" ]; then + exit 0 + fi + + printf '\n## Diff\n' + glab mr diff "$mr" + ;; +esac diff --git a/core/agents/skills/change-request-comments/scripts/reply-to-comment.sh b/core/agents/skills/change-request-comments/scripts/reply-to-comment.sh new file mode 100755 index 0000000..6e71c79 --- /dev/null +++ b/core/agents/skills/change-request-comments/scripts/reply-to-comment.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +set -euo pipefail + +remote_url="$(git remote get-url origin)" + +case "$remote_url" in + *github.com*) + + if [ "$#" -eq 1 ]; then + pr_info="$(gh pr view --json number,headRepository)" + replies_json="$1" + elif [ "$#" -eq 2 ] && [[ "$1" =~ ^[0-9]+$ ]]; then + pr_info="$(gh pr view "$1" --json number,headRepository)" + replies_json="$2" + else + printf 'Usage: %s [] \n' "$0" >&2 + exit 2 + fi + + pr=$(jq -r '.number' <<<"$pr_info") + repo=$(jq -r '.headRepository.nameWithOwner' <<<"$pr_info") + + if ! jq -e 'type == "array" and all(.[]; (.comment_id | type == "number") and (.body | type == "string"))' \ + >/dev/null <<<"$replies_json"; then + printf 'Reply JSON must be an array of { "comment_id": number, "body": string } objects.\n' >&2 + exit 2 + fi + + count=$(jq length <<<"$replies_json") + printf 'Posting %d replies to PR #%s...\n' "$count" "$pr" + + while IFS= read -r reply; do + comment_id=$(jq -r '.comment_id' <<<"$reply") + body=$(jq -r '.body' <<<"$reply") + + printf ' Replying to comment %s... ' "$comment_id" + gh api --method POST "repos/$repo/pulls/$pr/comments/$comment_id/replies" \ + -f body="$body" + printf 'done\n' + done < <(jq -c '.[]' <<<"$replies_json") + + printf 'All %d replies posted.\n' "$count" + ;; + + *) + + if [ "$#" -eq 1 ]; then + mr="$(glab mr view -F json | jq -r '.iid')" + replies_json="$1" + elif [ "$#" -eq 2 ] && [[ "$1" =~ ^[0-9]+$ ]]; then + mr="$1" + replies_json="$2" + else + printf 'Usage: %s [] \n' "$0" >&2 + exit 2 + fi + + if ! jq -e 'type == "array" and all(.[]; (.discussion_id | type == "string") and (.body | type == "string"))' \ + >/dev/null <<<"$replies_json"; then + printf 'Reply JSON must be an array of { "discussion_id": string, "body": string } objects.\n' >&2 + exit 2 + fi + + count=$(jq length <<<"$replies_json") + printf 'Posting %d replies to MR !%s...\n' "$count" "$mr" + + while IFS= read -r reply; do + discussion_id=$(jq -r '.discussion_id' <<<"$reply") + body=$(jq -r '.body' <<<"$reply") + + printf ' Replying to discussion %s... ' "$discussion_id" + glab mr note create "$mr" --reply "$discussion_id" -m "$body" + printf 'done\n' + done < <(jq -c '.[]' <<<"$replies_json") + + printf 'All %d replies posted.\n' "$count" + ;; +esac diff --git a/core/agents/skills/change-request-publish/README.md b/core/agents/skills/change-request-publish/README.md new file mode 100644 index 0000000..81455e4 --- /dev/null +++ b/core/agents/skills/change-request-publish/README.md @@ -0,0 +1,30 @@ +# change-request-publish — Design Rationale + +## Why a script, not a raw `gh pr create` or `glab mr create` + +The `open-change-request.sh` script inspects the current branch, checks whether a change request already exists, and then delegates to the correct provider CLI (`gh` for GitHub, `glab` for GitLab). This means workflow-facing files never need to know whether the repository is hosted on GitHub or GitLab. + +## Host detection + +The script reads `git remote get-url origin` to determine the host: + +- `github.com` or `git@github.com:` → opens a pull request with `gh pr create`. +- Otherwise (commonly GitLab, including self-hosted instances) → opens a merge request with `glab mr create`. + +## Branch guard + +Refuses to open a change request when the current branch is `main`, `master`, or a detached `HEAD`. + +## Idempotency + +If a change request already exists for the current branch, the script prints the existing one and exits 0 instead of creating a duplicate. + +## Provider differences + +- GitHub pull requests use `gh pr create` with `--title` and `--body`. +- GitLab merge requests use `glab mr create` with `--title` and `--description`. +- The script abstracts these differences so callers use a single interface. + +## Why workflow files should not call provider CLIs directly + +Any workflow file that calls `gh pr create` or `glab mr create` directly bypasses the host detection, branch guard, and idempotency checks. Workflow instructions should tell agents to use the `change-request-publish` skill by name rather than invoking provider CLIs directly. diff --git a/core/agents/skills/change-request-publish/SKILL.md b/core/agents/skills/change-request-publish/SKILL.md new file mode 100644 index 0000000..9a741fc --- /dev/null +++ b/core/agents/skills/change-request-publish/SKILL.md @@ -0,0 +1,24 @@ +--- +name: change-request-publish +description: Use when opening a GitHub pull request or GitLab merge request. Delegates to the appropriate CLI +user-invocable: false +--- + +# Change Request Publish + +Create change request on GitHub (pull request) or GitLab (merge request). +The creation goes through a bundled script that **detects the host** from the remote URL and delegates to the appropriate CLI. + +## Open a change request + +```bash +bash .agents/skills/change-request-publish/scripts/open-change-request.sh [--title TITLE --description DESCRIPTION] +``` + +- **Host detection**: reads `git remote get-url origin` to determine the host. + - GitHub (`github.com` or `git@github.com:`) → opens a pull request with `gh pr create`. + - Otherwise (GitLab) → opens a merge request with `glab mr create` (GitLab is commonly self-hosted). +- **Branch guard**: refuses on `main`, `master`, or detached `HEAD`. +- **Idempotent**: skips creation when a change request already exists for the current branch, printing the existing one and exiting 0. +- **Pass-through**: all extra arguments are forwarded to the provider CLI. +- **Output**: prints the created change-request diff --git a/core/agents/skills/change-request-publish/scripts/open-change-request.sh b/core/agents/skills/change-request-publish/scripts/open-change-request.sh new file mode 100755 index 0000000..37c1b4a --- /dev/null +++ b/core/agents/skills/change-request-publish/scripts/open-change-request.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" + +case "$branch" in + main|master|HEAD) + printf 'Refusing: current branch is "%s". Open a change request from a feature branch.\n' "$branch" >&2 + exit 1 + ;; +esac + +# Detect host from the remote URL. +remote_url="$(git remote get-url origin)" + +case "$remote_url" in + *github.com*) + provider="github" + ;; + *) + provider="gitlab" + ;; +esac + +if [ "$provider" = "github" ]; then + # Skip creation if a pull request already exists for this branch. + existing="$(gh pr list --head "$branch" --json url --jq 'length')" + if [ "${existing:-0}" -gt 0 ]; then + printf 'A pull request already exists for branch "%s":\n' "$branch" + gh pr list --head "$branch" --json url,title --jq '.[] | " \(.title) — \(.url)"' + exit 0 + fi + + printf 'Opening pull request for branch "%s"...\n' "$branch" + gh pr create --head "$branch" "$@" +else + # Skip creation if a merge request already exists for this branch. + existing="$(glab mr list --source-branch "$branch" -F json 2>/dev/null | jq -r 'length' || echo 0)" + if [ "${existing:-0}" -gt 0 ]; then + printf 'A merge request already exists for branch "%s":\n' "$branch" + glab mr list --source-branch "$branch" + exit 0 + fi + + printf 'Opening merge request for branch "%s"...\n' "$branch" + glab mr create --source-branch "$branch" "$@" +fi diff --git a/core/agents/skills/git-publish/README.md b/core/agents/skills/git-publish/README.md new file mode 100644 index 0000000..9b7dc18 --- /dev/null +++ b/core/agents/skills/git-publish/README.md @@ -0,0 +1,18 @@ +# git-publish — Design Rationale + +## Why a script, not a raw `git push` + +`git push origin $(git rev-parse --abbrev-ref HEAD)` does **not** protect `main`: while you are on `main` it expands to `git push origin main`, and permission globs cannot see through the command substitution to block it. The guard has to inspect the current branch at run time, which is what the `push-branch.sh` script does. + +## Host detection + +The script reads `git remote get-url origin` to determine the remote, then pushes to that remote. No hardcoded remote name is assumed. + +## Branch and default-branch guards + +- Refuses to push when the current branch is `main`, `master`, or a detached `HEAD`. +- Refuses any `--force`, `-f`, or `--force-with-lease` argument — force-pushing is out of scope for this skill. + +## Why workflow-facing files should not call `git push` directly + +Any workflow file that calls `git push` bypasses the branch guard. The protection only holds if publishing goes through this script. Workflow instructions should tell agents to use the `git-publish` skill by name rather than invoking `git push` directly. diff --git a/core/agents/skills/git-publish/SKILL.md b/core/agents/skills/git-publish/SKILL.md new file mode 100644 index 0000000..5fb2b3b --- /dev/null +++ b/core/agents/skills/git-publish/SKILL.md @@ -0,0 +1,22 @@ +--- +name: git-publish +description: Use when pushing a branch to a remote — guards against pushing to the default branch and against force-pushing +user-invocable: false +--- + +# Git Publish + +Safe publishing for any Git-hosted repository. Pushing goes through a bundled script that **refuses to act on `main`/`master`** and refuses force-pushes. + +## Push the current branch + +```bash +bash .agents/skills/git-publish/scripts/push-branch.sh +``` + +It pushes the current branch to `origin`, refusing if the branch is `main`, `master`, or a detached `HEAD`, and refusing any `--force` / `-f` / `--force-with-lease` argument. Extra arguments (e.g. `--set-upstream`) are passed through. + +## Rules + +- Never bypass this script with a raw `git push` to publish work — the protection only holds if publishing goes through it. +- Force-pushing and pushing to `main`/`master` are out of scope for this skill; they require a deliberate, human-run command. diff --git a/core/agents/skills/git-publish/scripts/push-branch.sh b/core/agents/skills/git-publish/scripts/push-branch.sh new file mode 100755 index 0000000..788047c --- /dev/null +++ b/core/agents/skills/git-publish/scripts/push-branch.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" + +case "$branch" in + main|master|HEAD) + printf 'Refusing to push: current branch is "%s". Create a scoped feature branch first.\n' "$branch" >&2 + exit 1 + ;; +esac + +# Force-pushing is never part of safe publishing. +for arg in "$@"; do + case "$arg" in + --force|-f|--force-with-lease|--force-with-lease=*) + printf 'Refusing to force-push from this script. Force-push must be a deliberate, human-run command.\n' >&2 + exit 1 + ;; + esac +done + +printf 'Pushing branch "%s" to origin...\n' "$branch" +git push origin "$branch" "$@" diff --git a/core/agents/skills/github-pr-comments/SKILL.md b/core/agents/skills/github-pr-comments/SKILL.md deleted file mode 100644 index 590741c..0000000 --- a/core/agents/skills/github-pr-comments/SKILL.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -name: github-pr-comments -description: Use when GitHub PR feedback lives in plain comments, inline comments, issue comments, or review threads -user-invocable: false ---- - -# GitHub PR Comments - -Use this for solo-maintainer PR review workflows where feedback is stored as plain GitHub comments rather than formal review submissions. - -## Baseline Failure To Avoid - -Agents naturally mix comment APIs: they may read reviews when feedback is actually an issue comment, post a top-level reply instead of an inline thread reply, or patch someone else's comment instead of posting a reply. - -## Skill Scope - -This skill owns PR comment mechanics: fetching comments and diffs, distinguishing issue comments from inline review comments, obtaining exact target IDs, and posting approved replies through project scripts. - -This skill does not own role-level review orchestration. The calling review agent decides how to interpret comments, validate technical claims, combine them with other findings, suggest fixes, edit files, dispatch implementers, or perform final self-review. - -## Read Comments - -Use the bundled read-only helper before proposing fixes. It fetches top-level issue comments, unresolved inline review threads, and the PR diff: - -```bash -bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh [] -``` - -When `` is omitted, the script auto-detects the PR number from the current branch. - -Run the command exactly as shown, without quoting the script path. The review-code agent has explicit auto-permissions for this command form. - -The helper prints a compact comment summary first, then full PR metadata, issue comments, inline comments, and the PR diff. Use the summary for grouping and the full JSON sections for exact `databaseId` values. - -Optional read-only modes: - -```bash -bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh [] --comments-only -bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh [] --diff-only -bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh [] --json -``` - -Do not call the GitHub CLI directly for reading PR metadata, issue comments, inline comments, or diffs. The helper is the stable interface for this workflow. - -## Classify Comments - -- Issue comments: top-level PR conversation, fetched from `issues//comments`. -- Inline PR comments: unresolved diff review-thread comments, fetched through GitHub GraphQL `reviewThreads`. -- Outdated inline comments may still be valid; verify against current diff before dismissing. - -## Review Workflow - -1. Fetch PR comments and diff when the caller's workflow involves a PR. -2. Use the summary to group target types, then use the full JSON sections for exact `databaseId` values. -3. Distinguish top-level issue comments from inline review comments before posting replies. -4. Surface outdated inline comments as comment metadata/context; the calling review agent decides whether they still apply. -5. For posting, require an exact approved reply batch with target type, target ID when available, and body. -6. Post only the exact approved reply batch. - -## Replies - -Replying mutates GitHub state, so it is not hidden behind the read helper. Approval for edits, fixes, planning, or any other non-GitHub action does not authorize posting GitHub comments. - -Before posting, present the exact reply batch to the user. Include the target type (`issue comment` or `inline review comment`), target ID when available, and body. Wait for explicit approval for that batch. - -After approval, post inline review replies through the project script. Use the numeric `databaseId` from the read helper as `comment_id`: - -Pass a JSON array as the final argument: - -```bash -bash .agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh [] '[ - { "comment_id": 123456, "body": "Fixed in abc123." }, - { "comment_id": 789012, "body": "Good catch, updated." } -]' -``` - -The PR number is optional; when omitted, the script auto-detects it from the current branch. Use a one-element array for a single reply. This script replies to inline PR review comments only; it does not reply to top-level issue comments. - -## Common Mistakes - -- Treating top-level PR comments as complete enough for inline review. -- Posting a top-level PR comment when the user asked to reply to an inline thread. -- Resolving, editing, or deleting comments without explicit approval. -- Applying external feedback without checking whether it is technically correct. -- Treating approval for edits, fixes, planning, or other non-GitHub work as approval to post GitHub replies. -- Posting a reply batch that differs from the exact batch the user approved. diff --git a/core/agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh b/core/agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh deleted file mode 100644 index b063f40..0000000 --- a/core/agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [ "$#" -gt 2 ]; then - printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 - exit 2 -fi - -if [[ "${1:-}" =~ ^[0-9]+$ ]]; then - pr="$1" - mode="${2:-}" -else - pr="$(gh pr view --json number --jq '.number')" - mode="${1:-}" -fi -case "$mode" in - ""|--comments-only|--diff-only|--json) ;; - *) - printf 'Unknown mode: %s\n' "$mode" >&2 - printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 - exit 2 - ;; -esac - -if [ "$mode" = "--diff-only" ]; then - gh pr diff "$pr" - exit 0 -fi - -pr_json="$(gh pr view "$pr" --json number,title,headRefName,baseRefName,url,comments,headRepository)" -repo=$(jq -r '.headRepository.nameWithOwner' <<<"$pr_json") -issue_comments_json="$(gh api "repos/$repo/issues/$pr/comments")" -inline_comments_json="$(gh api graphql \ - -F owner="${repo%%/*}" \ - -F name="${repo#*/}" \ - -F pr="$pr" \ - -f query='query($owner: String!, $name: String!, $pr: Int!) { - repository(owner: $owner, name: $name) { - pullRequest(number: $pr) { - reviewThreads(first: 100) { - nodes { - isResolved - comments(first: 100) { - nodes { - id - databaseId - path - line - originalLine - body - createdAt - updatedAt - url - } - } - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - }' \ - --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved | not) | .comments.nodes[]]')" - -if [ "$mode" = "--json" ]; then - PR_JSON="$pr_json" ISSUE_COMMENTS_JSON="$issue_comments_json" INLINE_COMMENTS_JSON="$inline_comments_json" node <<'JS' -const payload = { - pr: JSON.parse(process.env.PR_JSON ?? '{}'), - issueComments: JSON.parse(process.env.ISSUE_COMMENTS_JSON ?? '[]'), - inlineComments: JSON.parse(process.env.INLINE_COMMENTS_JSON ?? '[]'), -}; - -console.log(JSON.stringify(payload, null, 2)); -JS - exit 0 -fi - -printf '## Comment Summary\n' -PR_JSON="$pr_json" ISSUE_COMMENTS_JSON="$issue_comments_json" INLINE_COMMENTS_JSON="$inline_comments_json" node <<'JS' -const pr = JSON.parse(process.env.PR_JSON ?? '{}'); -const issueComments = JSON.parse(process.env.ISSUE_COMMENTS_JSON ?? '[]'); -const inlineComments = JSON.parse(process.env.INLINE_COMMENTS_JSON ?? '[]'); - -const summarize = (body) => { - const firstLine = String(body ?? '').replace(/\s+/g, ' ').trim(); - return firstLine.length > 180 ? `${firstLine.slice(0, 177)}...` : firstLine; -}; - -console.log(`PR #${pr.number}: ${pr.title}`); -console.log(`Branch: ${pr.headRefName} -> ${pr.baseRefName}`); -console.log(`URL: ${pr.url}`); -console.log(''); - -console.log(`Issue comments (${issueComments.length})`); -for (const comment of issueComments) { - console.log(`- id=${comment.id} author=${comment.user?.login ?? 'unknown'} updated=${comment.updated_at}: ${summarize(comment.body)}`); -} -console.log(''); - -console.log(`Unresolved inline comments (${inlineComments.length})`); -for (const comment of inlineComments) { - const location = `${comment.path}:${comment.line ?? comment.originalLine ?? '?'}`; - console.log(`- databaseId=${comment.databaseId} ${location} updated=${comment.updatedAt}: ${summarize(comment.body)}`); -} -JS - -printf '\n## PR\n' -printf '%s\n' "$pr_json" - -printf '\n## Issue Comments\n' -printf '%s\n' "$issue_comments_json" - -printf '\n## Inline PR Comments\n' -printf '%s\n' "$inline_comments_json" - -if [ "$mode" = "--comments-only" ]; then - exit 0 -fi - -printf '\n## Diff\n' -gh pr diff "$pr" diff --git a/core/agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh b/core/agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh deleted file mode 100755 index 7588320..0000000 --- a/core/agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [ "$#" -eq 1 ]; then - pr_info="$(gh pr view --json number,headRepository)" - replies_json="$1" -elif [ "$#" -eq 2 ] && [[ "$1" =~ ^[0-9]+$ ]]; then - pr_info="$(gh pr view "$1" --json number,headRepository)" - replies_json="$2" -else - printf 'Usage: %s [] \n' "$0" >&2 - exit 2 -fi - -pr=$(jq -r '.number' <<<"$pr_info") -repo=$(jq -r '.headRepository.nameWithOwner' <<<"$pr_info") - -if ! jq -e 'type == "array" and all(.[]; (.comment_id | type == "number") and (.body | type == "string"))' \ - >/dev/null <<<"$replies_json"; then - printf 'Reply JSON must be an array of { "comment_id": number, "body": string } objects.\n' >&2 - exit 2 -fi - -count=$(jq length <<<"$replies_json") -printf 'Posting %d replies to PR #%s...\n' "$count" "$pr" - -while IFS= read -r reply; do - comment_id=$(jq -r '.comment_id' <<<"$reply") - body=$(jq -r '.body' <<<"$reply") - - printf ' Replying to comment %s... ' "$comment_id" - gh api --method POST "repos/$repo/pulls/$pr/comments/$comment_id/replies" \ - -f body="$body" - printf 'done\n' -done < <(jq -c '.[]' <<<"$replies_json") - -printf 'All %d replies posted.\n' "$count" diff --git a/core/agents/skills/issue-tracker/README.md b/core/agents/skills/issue-tracker/README.md new file mode 100644 index 0000000..dd0c9b3 --- /dev/null +++ b/core/agents/skills/issue-tracker/README.md @@ -0,0 +1,24 @@ +# issue-tracker — Design Rationale + +## Why a host-agnostic issue tracker wrapper exists + +GitHub and GitLab have different CLI tools (`gh` and `glab`) and slightly different flags for issue operations. A host-agnostic skill lets agents create, update, and search for issues without needing to know which provider hosts the repository. + +## Host detection + +All scripts read `git remote get-url origin` to determine the host: + +- GitHub → delegates to `gh issue ...` commands. +- GitLab → delegates to `glab issue ...` commands. + +## Provider differences abstracted by the scripts + +| Operation | GitHub (`gh`) | GitLab (`glab`) | +|---|---|---| +| Create issue | `gh issue create --title ... --body-file ... --label ...` | `glab issue create --title ... --description ... --label ...` | +| Update issue | `gh issue edit --body-file ...` | `glab issue update --description ...` | +| Search issues | `gh issue list --search ... --label ... --json ...` | `glab issue list --search ... --label ... --output json` | + +## Why provider CLIs should not be called directly from workflow files + +Calling `gh` or `glab` directly from workflow files couples those files to a specific provider. The issue-tracker skill handles host detection and flag translation, so workflow instructions should tell agents to use the `issue-tracker` skill by name. diff --git a/core/agents/skills/issue-tracker/SKILL.md b/core/agents/skills/issue-tracker/SKILL.md new file mode 100644 index 0000000..a1f2b84 --- /dev/null +++ b/core/agents/skills/issue-tracker/SKILL.md @@ -0,0 +1,33 @@ +--- +name: issue-tracker +description: Use when creating, updating, or searching for issues — detects GitHub vs GitLab and delegates to the appropriate CLI +user-invocable: false +--- + +# Issue Tracker + +Host-agnostic interface for issue management. Scripts detect whether the repository is hosted on GitHub or GitLab and delegate to the correct provider CLI (`gh` or `glab`). + +## Create an issue + +```bash +bash .agents/skills/issue-tracker/scripts/create-issue.sh --title TITLE --body-file PATH [--labels LABELS] +``` + +- Prints the created issue URL. + +## Update an issue + +```bash +bash .agents/skills/issue-tracker/scripts/update-issue.sh --issue NUMBER --body-file PATH +``` + +- Prints the issue URL. + +## Find duplicate issues + +```bash +bash .agents/skills/issue-tracker/scripts/find-duplicate-issues.sh --title TITLE [--labels LABELS] +``` + +- Prints a compact list of candidate duplicate issues, or nothing if none found. diff --git a/core/agents/skills/workflow-bug-analysis/scripts/create-bug-issue.sh b/core/agents/skills/issue-tracker/scripts/create-issue.sh old mode 100755 new mode 100644 similarity index 55% rename from core/agents/skills/workflow-bug-analysis/scripts/create-bug-issue.sh rename to core/agents/skills/issue-tracker/scripts/create-issue.sh index 8986c55..7616e19 --- a/core/agents/skills/workflow-bug-analysis/scripts/create-bug-issue.sh +++ b/core/agents/skills/issue-tracker/scripts/create-issue.sh @@ -3,19 +3,19 @@ set -euo pipefail usage() { cat <&2 usage @@ -59,12 +59,22 @@ if [ ! -s "$BODY_FILE" ]; then exit 1 fi -# Check gh is available -if ! command -v gh >/dev/null 2>&1; then - echo "Error: GitHub CLI (gh) is not installed. Install it from https://cli.github.com/" >&2 - exit 1 -fi +# Detect host from the remote URL. +remote_url="$(git remote get-url origin)" -# Create the issue -URL=$(gh issue create --title "$TITLE" --body-file "$BODY_FILE" --label "$LABELS") -echo "$URL" +case "$remote_url" in + *github.com*) + if [ -n "$LABELS" ]; then + gh issue create --title "$TITLE" --body-file "$BODY_FILE" --label "$LABELS" + else + gh issue create --title "$TITLE" --body-file "$BODY_FILE" + fi + ;; + *) + if [ -n "$LABELS" ]; then + glab issue create --title "$TITLE" --description-file "$BODY_FILE" --label "$LABELS" + else + glab issue create --title "$TITLE" --description-file "$BODY_FILE" + fi + ;; +esac diff --git a/core/agents/skills/issue-tracker/scripts/find-duplicate-issues.sh b/core/agents/skills/issue-tracker/scripts/find-duplicate-issues.sh new file mode 100644 index 0000000..2e34ef7 --- /dev/null +++ b/core/agents/skills/issue-tracker/scripts/find-duplicate-issues.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <&2 + usage + ;; + esac +done + +# Validate required arguments. +if [ -z "$TITLE" ]; then + echo "Error: --title is required" >&2 + usage +fi + +# Detect host from the remote URL. +remote_url="$(git remote get-url origin)" + +case "$remote_url" in + *github.com*) + label_args=() + if [ -n "$LABELS" ]; then + IFS=',' read -ra label_arr <<< "$LABELS" + for label in "${label_arr[@]}"; do + label_args+=(--label "$label") + done + fi + gh issue list --search "$TITLE" "${label_args[@]}" --json number,title,url + ;; + *) + label_args=() + if [ -n "$LABELS" ]; then + IFS=',' read -ra label_arr <<< "$LABELS" + for label in "${label_arr[@]}"; do + label_args+=(--label "$label") + done + fi + glab issue list --search "$TITLE" "${label_args[@]}" --output json + ;; +esac diff --git a/core/agents/skills/workflow-bug-analysis/scripts/update-bug-issue.sh b/core/agents/skills/issue-tracker/scripts/update-issue.sh old mode 100755 new mode 100644 similarity index 67% rename from core/agents/skills/workflow-bug-analysis/scripts/update-bug-issue.sh rename to core/agents/skills/issue-tracker/scripts/update-issue.sh index c79e941..d4656fc --- a/core/agents/skills/workflow-bug-analysis/scripts/update-bug-issue.sh +++ b/core/agents/skills/issue-tracker/scripts/update-issue.sh @@ -3,7 +3,7 @@ set -euo pipefail usage() { cat <&2 usage @@ -58,13 +58,16 @@ if [ ! -s "$BODY_FILE" ]; then exit 1 fi -# Check gh is available -if ! command -v gh >/dev/null 2>&1; then - echo "Error: GitHub CLI (gh) is not installed. Install it from https://cli.github.com/" >&2 - exit 1 -fi +# Detect host from the remote URL. +remote_url="$(git remote get-url origin)" -# Update the issue -gh issue edit "$ISSUE" --body-file "$BODY_FILE" -URL=$(gh issue view "$ISSUE" --json url -q .url) -echo "$URL" +case "$remote_url" in + *github.com*) + gh issue edit "$ISSUE" --body-file "$BODY_FILE" + gh issue view "$ISSUE" --json url -q .url + ;; + *) + glab issue update "$ISSUE" --description-file "$BODY_FILE" + glab issue view "$ISSUE" -F json | jq -r '.web_url' + ;; +esac diff --git a/core/agents/skills/workflow-bug-analysis/SKILL.md b/core/agents/skills/workflow-bug-analysis/SKILL.md index c87236d..b8613ad 100644 --- a/core/agents/skills/workflow-bug-analysis/SKILL.md +++ b/core/agents/skills/workflow-bug-analysis/SKILL.md @@ -1,12 +1,12 @@ --- name: workflow-bug-analysis -description: Systematic bug investigation methodology — classify input, reproduce, trace code paths, inspect logs, form hypotheses, and produce a structured GitHub issue. +description: Systematic bug investigation methodology — classify input, reproduce, trace code paths, inspect logs, form hypotheses, and produce a structured tracker issue. user-invocable: false --- # Bugfix Analysis -Systematic investigation methodology for bug reports. The calling agent uses this skill to trace a bug from symptom to root cause and produce a structured GitHub issue. +Systematic investigation methodology for bug reports. The calling agent uses this skill to trace a bug from symptom to root cause and produce a structured tracker issue. ## Investigation Steps @@ -86,24 +86,14 @@ Structure findings into the issue template and save to `.temp/-issue-body. ### 7. Create the issue -```bash -bash .agents/skills/workflow-bug-analysis/scripts/create-bug-issue.sh \ - --title "Concise bug summary" \ - --body-file .temp/-issue-body.md \ - --labels "bug" -``` +First, use /issue-tracker to check for duplicate issues. If duplicates exist, link to them in the issue body. Then use /issue-tracker to create the issue with the title, body file, and labels. -The script prints the created issue URL. Report this URL to the user. +The skill prints the created issue URL. Report this URL to the user. ## Follow-up Evidence If the user provides additional evidence after the issue is created: 1. Update the issue body file in `.temp/` -2. Use `update-bug-issue.sh` to push the update: - ```bash - bash .agents/skills/workflow-bug-analysis/scripts/update-bug-issue.sh \ - --issue \ - --body-file .temp/-issue-body-updated.md - ``` +2. Use /issue-tracker to update the issue with the updated body file 3. Report the updated issue URL diff --git a/core/agents/skills/workflow-implementation/SKILL.md b/core/agents/skills/workflow-implementation/SKILL.md index 86df5db..e6756ef 100644 --- a/core/agents/skills/workflow-implementation/SKILL.md +++ b/core/agents/skills/workflow-implementation/SKILL.md @@ -97,7 +97,7 @@ Implementer subagents report one of four statuses. Handle each appropriately: ## Boundaries - The controller agent owns dispatch, model choice, human handoff, push, PR, and finish decisions. -- After final verification, the controller commits all changes, pushes the branch with `git push origin $(git rev-parse --abbrev-ref HEAD)`, and creates a GitHub pull request if one does not already exist. Never push to `main`. +- After final verification, the controller commits all changes, then pushes the branch with /git-publish and opens a change request with /change-request-publish. Never push to the default branch. - This skill only defines execution discipline and review gates. - Workers follow TDD instructions embedded in the approved plan for behavior changes. - Inline execution is acceptable only when the user explicitly requests it or subagent delegation is genuinely unavailable, but keep the same task/review gates. diff --git a/core/agents/skills/workflow-planning/plan-document-reviewer-prompt.md b/core/agents/skills/workflow-planning/plan-document-reviewer-prompt.md index 403b4b7..bea15da 100644 --- a/core/agents/skills/workflow-planning/plan-document-reviewer-prompt.md +++ b/core/agents/skills/workflow-planning/plan-document-reviewer-prompt.md @@ -42,7 +42,7 @@ OpenCode task tool: |-------|----------------------------------|--------| | Each spec requirement | Exact task/step or "missing" | Covered/Missing | | Each user note | Exact task/step or "missing" | Covered/Missing | - | Each external/GitHub remark included in the prompt | Exact task/step or "missing" | Covered/Missing | + | Each external remark included in the prompt | Exact task/step or "missing" | Covered/Missing | Use the matrix to catch silent drops. Do not include the full matrix in the response unless there is a gap; report gaps as blocking issues. diff --git a/core/claude/README.md b/core/claude/README.md index e634d98..aa5f1c9 100644 --- a/core/claude/README.md +++ b/core/claude/README.md @@ -1,11 +1,9 @@ # Claude Code agent setup -Claude Code runs the **conversational** half of the agent pipeline. The -**implementation** half stays in OpenCode. There is deliberately -**no orchestrator**: the OpenCode hand-off ends the Claude Code session, and reviews are -triggered by hand, so the workflows are discrete, manually-invoked entry points. +Claude Code runs both the **conversational** and **implementation** halves of the +agent pipeline. OpenCode provides the same pipeline; pick one harness per task. -## The six skills +## The skills Each is a real `SKILL.md` under `.claude/skills//`, marked `disable-model-invocation: true` — they never auto-trigger and stay out of the always-loaded @@ -14,15 +12,16 @@ skill index. You start them with `/name`. | Skill (`/name`) | OpenCode counterpart | Role | | --- | --- | --- | | `/brainstorm` | `@brainstorm` | Idea → approved `spec.md` (interactive; offers domain grilling) | -| `/bugfix` | `@bugfix` | Investigate bug → structured GitHub issue; does not fix | +| `/bugfix` | `@bugfix` | Investigate bug → structured issue; does not fix | | `/planner` | `@planner` | Spec → task-by-task `plan.md` (offers grilling only when new domain language or non-trivial decisions appear) | -| `/review-plan` | `@review-plan` | Review + finalize the OpenCode hand-off plan | -| `/review-code` | `@review-code` | Review a diff/PR → fix-plan hand-off doc | +| `/implement` | `@implement` | Execute plan task-by-task via `implement-task` workers, verify, commit | +| `/review-plan` | `@review-plan` | Review + finalize the hand-off plan | +| `/review-code` | `@review-code` | Review a diff/PR/MR → fix-plan hand-off doc | | `/finish` | `@finish` | Durable feature doc, light glossary/ADR reconciliation, cleanup | ## Design principle: delegate to shared authored skills -The six entry-point skills carry per-agent glue (workflow steps, stop/hand-off gates, +The entry-point skills carry per-agent glue (workflow steps, stop/hand-off gates, escalation rules) but delegate the heavy-lift methodology to authored skills under `.agents/skills/`. This avoids inlining duplicate copies of shared workflows. @@ -36,7 +35,9 @@ These shared skills are symlinked into `.claude/skills/` from `.agents/skills/`: - `workflow-planning` — used by `/planner` - `workflow-verification` — used by `/finish` - `feature-documentation` — used by `/finish` -- `github-pr-comments` — used by `/review-plan`, `/review-code` (+ OpenCode) +- `change-request-comments` — used by `/review-plan`, `/review-code` +- `git-publish` — used by `/implement`, `/finish` +- `change-request-publish` — used by `/implement`, `/finish` Remote skills (e.g. `context7-cli`, `next-best-practices`, `shadcn`, `zoom-out`, `write-a-skill`) are declared in `skills-lock.json` and installed via the @@ -65,11 +66,12 @@ All three skills load the shared `grill-with-docs` skill on demand. ## Skill inventory in `.claude/skills/` -- **Authored agent skills (real dirs):** `brainstorm`, `bugfix`, `planner`, `review-plan`, - `review-code`, `finish`. +- **Authored agent skills (real dirs):** `brainstorm`, `bugfix`, `planner`, `implement`, + `review-plan`, `review-code`, `finish`. - **Symlinked shared skills (auto-invocable):** `grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, - `workflow-verification`, `feature-documentation`, `github-pr-comments`. + `workflow-verification`, `feature-documentation`, `change-request-comments`, + `git-publish`, `change-request-publish`. To add a newly-installed shared skill: `ln -s ../../.agents/skills/ .claude/skills/`. `.claude/skills/` is hand-managed. @@ -78,9 +80,11 @@ To add a newly-installed shared skill: `settings.json` encodes the project-wide permission union the skills need: edit allowed; read-only git and common Unix commands allowed; `git push` / `gh pr create` / -`rm` ask first; branch-delete, worktree-remove denied. +`glab mr create` / `rm` ask first; branch-delete, worktree-remove denied. The +`git-publish`, `change-request-publish`, and `change-request-comments` skill scripts +are available. ## Usage -Type `/brainstorm`, `/bugfix`, `/planner`, `/review-plan`, `/review-code`, or `/finish` +Type `/brainstorm`, `/bugfix`, `/implement`, `/planner`, `/review-plan`, `/review-code`, or `/finish` (optionally with an argument, e.g. `/planner plans/2026-05-30-foo/spec.md`). diff --git a/core/claude/agents/implement-task.md b/core/claude/agents/implement-task.md new file mode 100644 index 0000000..4abd9b9 --- /dev/null +++ b/core/claude/agents/implement-task.md @@ -0,0 +1,37 @@ +--- +name: implement-task +description: Worker that implements one approved plan task with TDD, a single focused commit, and self-review. Dispatched by the /implement controller — one fresh worker per task. +tools: Bash, Read, Edit, Write, Glob, Grep, Agent +model: sonnet +--- + +You are the single-task implementation worker for this repository. + +Implement exactly one task provided. +Do not read or execute unrelated tasks from the plan unless the controller explicitly asks. + +Load `docs/agents/implement-task.md` and only the task context, spec/plan excerpts, and docs provided or named by the controller. + +## Rules + +- Never work on `main`. +- Follow the provided task text, spec/plan context, and the repository docs the guide (`docs/agents/implement-task.md`) tells you to load for the area you touch. +- Follow TDD for behavior changes unless the task is explicitly docs-only, config-only, or trivial wiring. +- Make the smallest correct change. Make small, justified adaptations to fit the current codebase, and report them clearly. +- If you need to verify an exact file path, module boundary, or existing pattern before editing, dispatch an @explore subagent with a focused question rather than guessing. Use it only for read-only verification of your one task — do not re-plan, widen scope, or dispatch other implementers. +- Prefer `git mv` for moves/renames of tracked paths and `git rm` for removals of tracked paths. Use plain `mv`/`rm` only for untracked paths. +- Commit exactly the task changes with the commit message specified by the plan, or a concise message if the plan omitted one. +- Do not push, create pull requests, amend commits, delete branches, remove worktrees, or dispatch other implementers. +- If requirements are unclear, report `NEEDS_CONTEXT` before editing. +- If blocked after three attempts on the same issue, report `BLOCKED` with what you tried. + +Before reporting, self-review the diff for spec compliance, overbuilding, tests, and obvious defects. + +## Report format + +- **Status:** `DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, or `BLOCKED` +- **Commit:** commit SHA or `none` +- **Implemented:** concise summary +- **Verification:** exact commands run and results +- **Files changed:** paths +- **Concerns:** anything the controller should inspect diff --git a/core/claude/settings.json b/core/claude/settings.json index 8d62b85..cb46e01 100644 --- a/core/claude/settings.json +++ b/core/claude/settings.json @@ -5,7 +5,6 @@ "Grep(./**)", "Glob(./**)", "Read(./**)", - "Edit(./**)", "Write(./**)", @@ -19,6 +18,7 @@ "Bash(git ls-files:*)", "Bash(git branch:*)", "Bash(git checkout:*)", + "Bash(git add:*)", "Bash(git commit:*)", "Bash(git rm:*)", @@ -42,21 +42,29 @@ "Bash(which:*)", "Bash(mkdir:*)", - "Bash(bash .claude/skills/github-pr-comments/scripts/fetch-pr-comments.sh:*)", - "Bash(bash .claude/skills/workflow-bug-analysis/scripts/create-bug-issue.sh:*)", + "Bash(bash .claude/skills/git-publish/scripts/push-branch.sh:*)", + "Bash(bash .claude/skills/change-request-comments/scripts/fetch-comments.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/create-issue.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/find-duplicate-issues.sh:*)", + "Bash(gh issue list:*)", "Bash(gh issue view:*)", - "Bash(gh search issues:*)" + "Bash(gh search issues:*)", + + "Bash(glab issue list:*)", + "Bash(glab issue view:*)" ], "ask": [ "Bash(git push:*)", "Bash(git push --force:*)", "Bash(git push -f:*)", - "Bash(gh pr create:*)", + "Bash(rm:*)", "Bash(rm -rf:*)", - "Bash(bash .claude/skills/github-pr-comments/scripts/reply-to-pr-comment.sh:*)", - "Bash(bash .claude/skills/workflow-bug-analysis/scripts/update-bug-issue.sh:*)" + + "Bash(bash .claude/skills/change-request-publish/scripts/open-change-request.sh:*)", + "Bash(bash .claude/skills/change-request-comments/scripts/reply-to-comment.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/update-issue.sh:*)" ], "deny": [ "Bash(git branch -d:*)", diff --git a/core/claude/skills/brainstorm/SKILL.md b/core/claude/skills/brainstorm/SKILL.md index d34810c..bea4116 100644 --- a/core/claude/skills/brainstorm/SKILL.md +++ b/core/claude/skills/brainstorm/SKILL.md @@ -31,8 +31,10 @@ Use `grill-with-docs` when the brainstorm needs domain grilling. - Prefer relative workspace paths in commands and examples (e.g. `mkdir -p plans/2026-05-30-feature-name`). Avoid absolute workspace paths unless a tool requires them. -- Always use `git push origin $(git rev-parse --abbrev-ref HEAD)` — never use bare - `git push` to avoid accidentally pushing to `main`. +- Publish through /git-publish: + - Push the current branch with /git-publish. + - Open a change request with /change-request-publish when needed. + Never hand-roll `git push`. ## Stop conditions diff --git a/core/claude/skills/bugfix/SKILL.md b/core/claude/skills/bugfix/SKILL.md index 14cbbf5..9d8654a 100644 --- a/core/claude/skills/bugfix/SKILL.md +++ b/core/claude/skills/bugfix/SKILL.md @@ -1,16 +1,16 @@ --- name: bugfix -description: Investigates bugs from error logs or behavior descriptions, traces root cause through code and data, and produces a structured GitHub issue. Does not fix bugs. +description: Investigates bugs from error logs or behavior descriptions, traces root cause through code and data, and produces a structured tracker issue. Does not fix bugs. argument-hint: [error log, stack trace, or behavior description] disable-model-invocation: true --- You are the bugfix analysis agent for this repository. Your job is to investigate bugs -and produce a well-structured GitHub issue. You do not fix bugs. +and produce a well-structured tracker issue. You do not fix bugs. ## Load first -Load `docs/agents/bugfix.md` and invoke the **`workflow-bug-analysis`** symlinked authored +Load `docs/agents/bugfix.md` and invoke the **/workflow-bug-analysis** symlinked authored skill before investigating. ## Subagent usage @@ -27,27 +27,27 @@ when their questions are independent. Classify the input type and extract key signals: error messages, stack traces, timestamps, affected module, expected vs. actual behavior. -2. **Investigate** — Use the `workflow-bug-analysis` skill methodology. Dispatch `@explore` +2. **Investigate** — Use the /workflow-bug-analysis skill methodology. Dispatch `@explore` when you need focused repository investigation. 3. **Hypothesize** — Form a root-cause hypothesis based on gathered evidence. -4. **Create the issue** — Author and file a structured GitHub issue using the - `workflow-bug-analysis` skill. +4. **Create the issue** — Use /issue-tracker to check for duplicates and create the tracker issue using the + /workflow-bug-analysis skill for content. 5. **Report and stop** — Report the issue URL. The job is done. ## Follow-up evidence If the user provides additional evidence after the issue is created, update the issue -using the `workflow-bug-analysis` skill and report the updated issue URL. +using the /workflow-bug-analysis skill and report the updated issue URL. ## Rules - Never commit, push, or create PRs. Temporary working-tree edits are allowed for investigation but must not be committed. -- Never call `gh` directly for mutations — use the `workflow-bug-analysis` skill's wrapper - scripts. +- Never call `gh` or `glab` directly — use /issue-tracker for issue mutations. +- Use /issue-tracker for duplicate checking. - Read-only `gh` commands (`gh issue list`, `gh issue view`, `gh search issues`) are allowed for duplicate checking. - If the bug cannot be investigated with the available evidence, say so and list what diff --git a/core/claude/skills/finish/SKILL.md b/core/claude/skills/finish/SKILL.md index 35d0cfc..5641036 100644 --- a/core/claude/skills/finish/SKILL.md +++ b/core/claude/skills/finish/SKILL.md @@ -47,12 +47,12 @@ explore subagent with a focused question. feature doc is written and the user approves cleanup. Use `git rm` for tracked files; use plain `rm` only for untracked paths. 5. **Commit and push.** Commit the feature doc and cleanup when the user requests it, - then push the current scoped branch with - `git push origin $(git rev-parse --abbrev-ref HEAD)`. + then push using /git-publish: + - Push the current branch with /git-publish. ## Push boundaries -Push only with `git push origin $(git rev-parse --abbrev-ref HEAD)`. Never use bare -`git push`, push to `main`, force-push, delete remote refs, push tags, or push -arbitrary refspecs without explicit approval. Do not create PRs, amend commits, delete -branches, close comments, or remove worktrees. +Push only with the publish skill scripts. Never use bare `git push`, push to `main`, +force-push, delete remote refs, push tags, or push arbitrary refspecs without explicit +approval. Do not create PRs, amend commits, delete branches, close comments, or remove +worktrees. diff --git a/core/claude/skills/implement/SKILL.md b/core/claude/skills/implement/SKILL.md new file mode 100644 index 0000000..c873129 --- /dev/null +++ b/core/claude/skills/implement/SKILL.md @@ -0,0 +1,52 @@ +--- +name: implement +description: Executes an approved plan task-by-task in Claude Code — dispatches a fresh implement-task worker per task, reviews each diff, and runs verification before any completion claim. +argument-hint: [path to approved plan] +disable-model-invocation: true +--- + +You are the implementation controller for this repository. +This is the Claude Code implementation controller. OpenCode has an equivalent controller agent. +Pick one harness per task — do not run both controllers over the same plan. + +Approved plan (if provided): $ARGUMENTS + +## Load first + +Load `docs/agents/implement.md` and follow its document list exactly. +Then load the approved `plan.md` (and `spec.md` if present) and the docs the plan names. + +Use the `workflow-verification` skill before any completion claim. +Do not say work is complete, fixed, or passing unless the relevant verification commands have just run successfully. + +## Execution rules + +- **Never implement on `main`.** Create or ask for a scoped branch first. +- **Default behavior:** execute the plan task-by-task by dispatching a fresh `implement-task` worker (via the Agent tool, `subagent_type: implement-task`) for each task. This is the standard workflow — do not deviate unless the user explicitly asks for inline implementation. +- **Inline implementation:** acceptable only when the user explicitly asks you to implement directly. When inline, apply the same per-task and review gates as delegated workers. +- **Controller duties:** orchestrate — dispatch tasks, package context for workers, review worker reports and diffs, enforce spec compliance and code-quality gates, and run verification before any completion claim. Give the worker the full task text, relevant context, exact plan/spec paths, affected docs, and current branch state; do not make it reread the whole plan. +- **One focused commit per task.** Delegated workers create the task commit; commit inline only when you execute a task yourself. Review the worker report and `git diff` before moving on. +- Follow TDD for behavior changes unless the plan marks a step as docs-only, config-only, or trivial wiring. +- Do not pause between tasks for routine progress approval. +- Stop and ask only when the same task fails more than three times, the plan conflicts with code reality, or an architectural decision is required. If a task needs an architectural decision, report the blocker and recommend human escalation. + +## Model selection + +Each per-task `implement-task` worker runs on Sonnet — the `implement-task` agent pins `model: sonnet`, so leave the model override off when dispatching it. +If a worker fails to deliver a task (after the per-task retry limit), re-dispatch the same task with a `model` override to a more capable model before escalating to a human. + +## Verification + +Run targeted verification while iterating and the required final verification before claiming completion. Use the `workflow-verification` skill as the completion gate. + +## Shell guidance + +- Prefer `git mv` for moves/renames of tracked paths and `git rm` for removals of tracked paths. Use plain `mv`/`rm` only for untracked paths. +- Never work on or push to `main`. Publish through the appropriate publish skill — do not hand-roll `git push`. + +## Finishing + +After final verification, commit all changes and push the branch using /git-publish. +Open a change request with /change-request-publish when needed. + +The publish scripts refuse `main`/`master` and skip creation when a PR/MR already exists for the branch. diff --git a/core/claude/skills/planner/SKILL.md b/core/claude/skills/planner/SKILL.md index ad2849b..d622675 100644 --- a/core/claude/skills/planner/SKILL.md +++ b/core/claude/skills/planner/SKILL.md @@ -1,6 +1,6 @@ --- name: planner -description: Turns an approved spec or clear requirements into a reviewable, task-by-task plan.md with TDD steps, ready for OpenCode handoff. +description: Turns an approved spec or clear requirements into a reviewable, task-by-task plan.md with TDD steps, ready for implementation handoff. argument-hint: [path to spec or requirements] disable-model-invocation: true --- @@ -19,7 +19,7 @@ list exactly. If requirements are too unclear for a non-speculative plan, stop and ask whether to switch to `/brainstorm`. Do not invoke brainstorming automatically. -Use the `workflow-planning` and `grill-with-docs` skills for the planning methodology +Use the /workflow-planning and /grill-with-docs skills for the planning methodology and domain grilling. Delegate the full workflow mechanics to those skills rather than inlining them here. @@ -66,13 +66,14 @@ inlining them here. - Prefer relative workspace paths in commands and examples (e.g. `mkdir -p plans/2026-05-30-feature-name`). Avoid absolute workspace paths unless a tool requires them. -- Always use `git push origin $(git rev-parse --abbrev-ref HEAD)` — never use bare - `git push` to avoid accidentally pushing to `main`. +- Publish through /git-publish + - Open a change request with /change-request-publish when needed. + Never hand-roll `git push`. ## Stop conditions - Write the plan to `plans/YYYY-MM-DD-feature-name/plan.md`, next to the spec. Commit the spec/plan markdown only when the user asks for committed workflow artifacts. - After the plan is written, **stop**. Optionally suggest `/review-plan` before handoff. -- Implementation runs in **OpenCode** (`@implement` / `@implement-task`), not Claude Code. - Hand off to OpenCode for execution. +- Tell the user to review the plan and continue with /review-plan if they have remarks or /implement to start implementing the plan + (the implement agent). Pick one per task. Suggest the user's preferred harness. diff --git a/core/claude/skills/review-code/SKILL.md b/core/claude/skills/review-code/SKILL.md index ac464af..a2318cf 100644 --- a/core/claude/skills/review-code/SKILL.md +++ b/core/claude/skills/review-code/SKILL.md @@ -15,7 +15,7 @@ Scope (if provided): $ARGUMENTS Read `docs/agents/review-code.md` before reviewing and follow its document list exactly. -Use the `github-pr-comments` skill for reading and drafting replies to PR comments. +Use /change-request-comments for reading and drafting replies to change-request comments. ## Review priorities @@ -53,8 +53,8 @@ whether it is a finding. Use this standard review-code workflow unless the user explicitly requests a different scope: -1. Read open PR comments first by using the `github-pr-comments` skill. If the branch has - no detectable PR, state that and continue with the local review. +1. Read open change-request comments first by using /change-request-comments. If the branch has + no detectable change request, state that and continue with the local review. 2. Identify the plan/spec under review. If multiple candidate plans exist and the user did not state which one to use, ask before continuing the plan-conformance part of the review. @@ -70,16 +70,16 @@ Use this standard review-code workflow unless the user explicitly requests a dif implementation plan for new review findings. Write review finding plans **next to the original plan** in the same directory (e.g. `plans//review-findings.md`). Do not create new date-prefixed folders for review findings. -7. If the user approves dispatching `@implement-task` for trivial review-scoped fixes, +7. If the user approves dispatching implementation tasks for trivial review-scoped fixes, dispatch focused tasks only after presenting the exact fix instructions. In Claude - Code, prefer writing the fix-plan handoff document and handing it to OpenCode - (`@implement` / `@implement-task`) for TDD implementation. + Code, prefer writing the fix-plan handoff document and handing it to the + OpenCode implement agent for TDD implementation. 8. After plan updates or implement-task results, self-review every tracked remark and finding. Map each item to the finding, changed file, fix-plan section, or intentional unresolved status. -9. Draft exact GitHub replies for resolved PR comments and ask for explicit approval +9. Draft exact replies for resolved change-request comments and ask for explicit approval before posting. Approval to dispatch fixes, write a fix plan, or edit files does not - authorize posting GitHub comments. + authorize posting change-request comments. Output findings first, ordered by severity, with file and line references. If there are no findings, say so and note any areas you could not verify from the diff alone. @@ -87,8 +87,8 @@ no findings, say so and note any areas you could not verify from the diff alone. ## Verification Your job is to review and report findings. Verification (lint, typecheck, test, build) is -the implement agent's responsibility — `@implement-task` runs targeted verification per -task, and `@implement` runs final verification after all tasks complete. Report +the implement agent's responsibility — the implement agent runs targeted verification per +task, and the implement controller runs final verification after all tasks complete. Report unverified areas as residual risks in your findings; do not run verification commands yourself. @@ -115,12 +115,11 @@ Before recommending fixes, evaluate scope: When escalating, state: (1) the number and severity of findings, (2) why they exceed review-fix scope, (3) which command should handle it and why. -## External / GitHub comments +## External / change-request comments -When reviewing external or GitHub PR feedback, use the `github-pr-comments` skill. Always -check open PR comments by default unless the user explicitly says not to. Verify each +When reviewing external change-request feedback, use /change-request-comments. Always +check open change-request comments by default unless the user explicitly says not to. Verify each technical claim against current code before recommending a change. Suggest changes only. -Before posting any GitHub issue comment, PR conversation comment, or inline review -reply, present the exact draft reply and wait for explicit user approval. Approval to -dispatch fixes, write a fix plan, or edit files does not authorize posting GitHub -comments. +Before posting any change-request comment, present the exact draft reply and wait for explicit +user approval. Approval to dispatch fixes, write a fix plan, or edit files does not authorize +posting change-request comments. diff --git a/core/claude/skills/review-plan/SKILL.md b/core/claude/skills/review-plan/SKILL.md index 97897fc..7f9ba8d 100644 --- a/core/claude/skills/review-plan/SKILL.md +++ b/core/claude/skills/review-plan/SKILL.md @@ -1,12 +1,12 @@ --- name: review-plan -description: Reviews a spec or plan against architecture, testing rules, and user notes, then finalizes it into the OpenCode handoff document. +description: Reviews a spec or plan against architecture, testing rules, and user notes, then finalizes it into the implementation handoff document. argument-hint: [path to spec or plan] disable-model-invocation: true --- You are the spec and plan review agent for this repository. You review the spec/plan, then -**finalize it into the hand-off document** that OpenCode's implementer (`@implement`) will +**finalize it into the hand-off document** that the implementer will execute. Spec or plan to review (if provided): $ARGUMENTS @@ -16,7 +16,7 @@ Spec or plan to review (if provided): $ARGUMENTS Read `docs/agents/review-plan.md` before every review and follow its document list exactly. -Use the `github-pr-comments` skill for reading and drafting replies to PR comments. +Use /change-request-comments for reading and drafting replies to change-request comments. ## Review goals @@ -47,8 +47,8 @@ executable. Use this standard review-plan workflow unless the user explicitly requests a different scope: -1. Read open PR comments first by using the `github-pr-comments` skill. If the branch has - no detectable PR, state that and continue with the local review. +1. Read open change-request comments first by using /change-request-comments. If the branch has + no detectable change request, state that and continue with the local review. 2. Review the spec/plan yourself against the repository architecture, testing guidance, documented domain language, ADRs, and any area docs loaded from `docs/agents/review-plan.md`. @@ -60,9 +60,9 @@ Use this standard review-plan workflow unless the user explicitly requests a dif 6. After approved edits, self-review every tracked remark and finding. Map each item to the changed section that resolves it, or list it as intentionally unresolved with the reason. -7. Draft exact GitHub replies for resolved PR comments and ask for explicit approval - before posting. Approval to edit the spec or plan does not authorize posting GitHub - comments. +7. Draft exact replies for resolved change-request comments and ask for explicit approval + before posting. Approval to edit the spec or plan does not authorize posting + change-request comments. ## Finalizing the handoff document @@ -75,16 +75,17 @@ document. The gate is approval, not capability: 3. Apply the approved changes to `plans//plan.md` (and `spec.md` if needed) so the plan is unambiguous and executable. Do not touch application code, tests, or config — only the `plans/**` artifacts. -4. Report the finalized plan path and confirm it is ready for OpenCode handoff. +4. Report the finalized plan path and confirm it is ready for implementation handoff. -When pushing approved spec/plan edits, always use -`git push origin $(git rev-parse --abbrev-ref HEAD)`. Never push to `main`. +- Publish through /git-publish +- Open a change request with /change-request-publish when needed. +Never hand-roll `git push`. -## External / GitHub comments +## External / change-request comments -When reviewing external or GitHub PR feedback, use the `github-pr-comments` skill. Always -check open PR comments by default unless the user explicitly says not to. Verify each +When reviewing external change-request feedback, use /change-request-comments. Always +check open change-request comments by default unless the user explicitly says not to. Verify each technical claim against the current code before recommending a change. Suggest changes -only. Before posting any GitHub issue comment, PR conversation comment, or inline review -reply, present the exact draft reply and wait for explicit user approval. Approval to -edit `plans/**` does not authorize posting GitHub comments. +only. Before posting any change-request comment, present the exact draft reply and wait +for explicit user approval. Approval to edit `plans/**` does not authorize posting +change-request comments. diff --git a/core/docs/agents/agent-workflow-extension.md b/core/docs/agents/agent-workflow-extension.md index 405ea77..314c055 100644 --- a/core/docs/agents/agent-workflow-extension.md +++ b/core/docs/agents/agent-workflow-extension.md @@ -9,9 +9,10 @@ Use this guide when changing coding assistant workflows, agents, or reusable ski - `.claude/skills/*/SKILL.md` files define Claude skill behavior for the matching workflow. - `.agents/skills/*` files provide reusable mechanics shared by workflows. They may describe how to use bundled scripts or helpers, but they should not own role-level orchestration for review-plan, review-code, planner, implement, or finish workflows. -## GitHub PR comment workflow boundary +## Change-request comment workflow boundary -- The `github-pr-comments` skill owns reading PR comments, classifying comment target types, obtaining exact IDs, and posting approved replies through project scripts. +- The `change-request-comments` skill owns reading change-request comments, classifying comment target types, obtaining exact IDs, and posting approved replies through project scripts. +- Provider-specific mechanics (`gh`, `glab`, provider comment scripts) belong only in provider or boundary skills such as `change-request-comments`, never in generic workflow skills or agent instructions. - Review agents own deciding what the comments mean, validating technical claims, combining them with independent review findings, suggesting fixes, and requesting approval before edits or replies. ## Default placement rules diff --git a/core/docs/agents/review-code.md b/core/docs/agents/review-code.md index c05a92d..bed3846 100644 --- a/core/docs/agents/review-code.md +++ b/core/docs/agents/review-code.md @@ -4,13 +4,14 @@ - Approved plan - Approved spec, if present -- Git diff or PR diff under review +- Git diff or change-request diff under review - `docs/CODING_GUIDELINES.md` - `docs/LOGGING.md` - `docs/TESTING.md` - `CONTEXT-MAP.md` and relevant `docs/contexts//CONTEXT.md` glossaries to check domain language; `docs/adr/` for documented decisions - Area docs listed below when the diff touches that area - `docs/ARCHITECTURE.md` when the change crosses package or ownership boundaries +- /change-request-comments skill for reading and drafting replies to change-request feedback ## Area Docs diff --git a/core/docs/agents/review-plan.md b/core/docs/agents/review-plan.md index 3199135..65d4c73 100644 --- a/core/docs/agents/review-plan.md +++ b/core/docs/agents/review-plan.md @@ -8,6 +8,7 @@ - `docs/TESTING.md` - `CONTEXT-MAP.md` and relevant `docs/contexts//CONTEXT.md` glossaries to check domain language; `docs/adr/` for documented decisions - Area docs listed below when the reviewed work touches that area +- /change-request-comments skill for reading and drafting replies to change-request feedback ## Area Docs diff --git a/core/opencode/agents/brainstorm.md b/core/opencode/agents/brainstorm.md index fac9a20..a84bcd8 100644 --- a/core/opencode/agents/brainstorm.md +++ b/core/opencode/agents/brainstorm.md @@ -29,18 +29,7 @@ permission: "git branch -D *": deny "git worktree remove *": deny - "git push origin *": allow - "git push --force *": deny - "git push -f *": deny - "git push --force-with-lease *": ask - "git push origin --force *": deny - "git push origin -f *": deny - "git push origin --force-with-lease *": ask - "git push * --force *": deny - "git push * -f *": deny - "git push * --force-with-lease *": ask - "git push origin main*": deny - "git push origin +main*": deny + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow "ls *": allow "mkdir plans/*": allow @@ -59,6 +48,8 @@ permission: "*": deny "workflow-brainstorming": allow "grill-with-docs": allow + "git-publish": allow + "change-request-publish": allow --- You are the explicit brainstorming agent. @@ -94,6 +85,6 @@ Shell guidance: - Prefer relative workspace paths in shell commands and examples (e.g., `mkdir -p plans/2026-05-25-feature-name`). - Avoid absolute workspace paths in shell commands unless a tool explicitly requires them. -- Always use `git push origin $(git rev-parse --abbrev-ref HEAD)` — never use bare `git push` to avoid accidentally pushing to `main`. +- Push the branch with /git-publish. Never hand-roll `git push`. When writing specs, include goal, non-goals, architecture, data flow, testing expectations, rollout or migration notes, and open questions if any remain. diff --git a/core/opencode/agents/bugfix.md b/core/opencode/agents/bugfix.md index 2550204..58b953c 100644 --- a/core/opencode/agents/bugfix.md +++ b/core/opencode/agents/bugfix.md @@ -1,5 +1,5 @@ --- -description: Investigates bugs from error logs or behavior descriptions, traces root cause through code and data, and produces a structured GitHub issue. +description: Investigates bugs from error logs or behavior descriptions, traces root cause through code and data, and produces a structured tracker issue. mode: all temperature: 0.1 permission: @@ -11,8 +11,9 @@ permission: "*": ask # Skill scripts — primary tools - "bash .agents/skills/workflow-bug-analysis/scripts/create-bug-issue.sh *": allow - "bash .agents/skills/workflow-bug-analysis/scripts/update-bug-issue.sh *": allow + "bash .agents/skills/issue-tracker/scripts/create-issue.sh *": allow + "bash .agents/skills/issue-tracker/scripts/find-duplicate-issues.sh *": allow + "bash .agents/skills/issue-tracker/scripts/update-issue.sh *": allow # Read-only git commands "git branch --show-current": allow @@ -34,12 +35,14 @@ permission: "mkdir -p .temp/*": allow "echo *": allow - # GitHub — agent uses wrapper scripts for mutations; direct gh is denied + # Tracker issues — use issue-tracker scripts for mutations; direct gh/glab mutations are denied "gh *": deny # Read-only issue inspection is allowed for duplicate checking "gh issue list *": allow "gh issue view *": allow "gh search issues *": allow + "glab issue list *": allow + "glab issue view *": allow # Denied — agent never persists changes or mutates GitHub directly "git push *": deny @@ -51,20 +54,21 @@ permission: skill: "*": deny "workflow-bug-analysis": allow + "issue-tracker": allow --- You are the bugfix analysis agent. -Load `docs/agents/bugfix.md` and invoke the `workflow-bug-analysis` skill before investigating. +Load `docs/agents/bugfix.md` and invoke the /workflow-bug-analysis skill before investigating. -Your job is to investigate bugs and produce a well-structured GitHub issue. You do not fix bugs. +Your job is to investigate bugs and produce a well-structured tracker issue. You do not fix bugs. ## Workflow 1. **Intake** — Read the user's input (error log, stack trace, or behavior description). Classify the input type and extract key signals. -2. **Investigate** — Use the `workflow-bug-analysis` skill methodology. Launch `@explore` subagents when you need focused repository investigation. +2. **Investigate** — Use the /workflow-bug-analysis skill methodology. Launch `@explore` subagents when you need focused repository investigation. 3. **Hypothesize** — Form a root-cause hypothesis based on gathered evidence. -4. **Create the issue** — Author and file a structured GitHub issue using the `workflow-bug-analysis` skill. +4. **Create the issue** — Use /issue-tracker to check for duplicates and create the tracker issue. 5. **Report and stop** — Report the issue URL. ## Subagent usage @@ -76,6 +80,6 @@ Concrete example: if the bug involves a service behavior the agent has not inspe ## Rules - Never commit, push, or create PRs. Temporary working-tree edits (e.g., a reproduction test or log statement) are allowed for investigation but must not be committed. -- Never call `gh` directly for mutations — use the skill's wrapper scripts. Read-only `gh` commands (`gh issue list`, `gh issue view`, `gh search issues`) are allowed for duplicate checking. +- Never call `gh` or `glab` directly for mutations — use /issue-tracker. Read-only `gh`/`glab` commands (`issue list`, `issue view`, `search issues`) are allowed for duplicate checking. - The default workflow ends when the issue is created and the URL is reported. If the user provides follow-up evidence, update the issue and report the updated URL. - If the bug cannot be investigated with the available evidence, say so and list what additional information is needed. diff --git a/core/opencode/agents/finish.md b/core/opencode/agents/finish.md index dff8c62..ff2452b 100644 --- a/core/opencode/agents/finish.md +++ b/core/opencode/agents/finish.md @@ -46,7 +46,9 @@ permission: "git push origin *:*": ask "git push origin --tags*": ask "git push origin tag *": ask - "git push origin $(git rev-parse --abbrev-ref HEAD)": allow + + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/change-request-publish/scripts/open-change-request.sh*": allow task: "*": deny "explore": allow @@ -55,6 +57,8 @@ permission: "workflow-verification": allow "feature-documentation": allow "grill-with-docs": allow + "git-publish": allow + "change-request-publish": allow --- You are the finishing agent. @@ -74,6 +78,6 @@ Finish workflow: 3. Treat the feature doc as durable reference documentation, not a project-status report. 4. Reconcile the domain docs: if implementation or review changed the domain language, or introduced or invalidated a decision, update the relevant `docs/contexts//CONTEXT.md` glossary and add or supersede ADRs under `docs/adr/` before finalizing. This is offered reconciliation, not a full grilling session — use `grill-with-docs` only when a divergence genuinely needs interrogating. Keep `CONTEXT.md` a glossary only. 5. Remove both `plans/YYYY-MM-DD-feature-name/spec.md` and `plans/YYYY-MM-DD-feature-name/plan.md` after the feature doc is written. Use `git rm` for tracked plan files. Use plain `rm` only for untracked paths. -6. Commit the feature doc and cleanup, then push the current scoped branch with `git push origin $(git rev-parse --abbrev-ref HEAD)`. +6. Commit the feature doc and cleanup, then publish. Push the branch with /git-publish. To open a change request, use /change-request-publish if needed. -Push automatically only with `git push origin $(git rev-parse --abbrev-ref HEAD)`. Never use bare `git push`, push to `main`, force-push, delete remote refs, push tags, or push arbitrary refspecs without explicit approval. Do not create PRs, amend commits, delete branches, close comments, or remove worktrees. +Push automatically only through the publish skill. Never hand-roll `git push`, push to `main`, force-push, delete remote refs, push tags, or push arbitrary refspecs without explicit approval. Do not create change requests manually, amend commits, delete branches, close comments, or remove worktrees. diff --git a/core/opencode/agents/implement.md b/core/opencode/agents/implement.md index 113a49d..1a84be4 100644 --- a/core/opencode/agents/implement.md +++ b/core/opencode/agents/implement.md @@ -7,10 +7,6 @@ permission: bash: "*": ask - "gh pr create *": allow - "gh pr list *": allow - "gh pr view *": allow - "git add *": allow "git branch *": allow "git branch -d *": deny @@ -53,7 +49,9 @@ permission: "git push origin *:*": ask "git push origin --tags*": ask "git push origin tag *": ask - "git push origin $(git rev-parse --abbrev-ref HEAD)": allow + + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/change-request-publish/scripts/open-change-request.sh*": allow "cat *": allow "diff *": allow @@ -86,19 +84,21 @@ permission: "*": deny "workflow-implementation": allow "workflow-verification": allow + "git-publish": allow + "change-request-publish": allow --- You are the implementation controller. -Start by using the `workflow-implementation` skill. Then load `docs/agents/implement.md`, the approved `plan.md`, and the docs required by the implement agent document. +Start by using the /workflow-implementation skill. Then load `docs/agents/implement.md`, the approved `plan.md`, and the docs required by the implement agent document. Execution rules: - Never implement on `main`; create or ask for a scoped branch if needed. - Prefer `git mv` for moves and renames of tracked paths. Use plain `mv` only for untracked paths or operations git cannot express cleanly. - Prefer `git rm` for removals of tracked paths. Use plain `rm` only for untracked paths. -- Always use `git push origin $(git rev-parse --abbrev-ref HEAD)` — never use bare `git push` to avoid accidentally pushing to `main`. -- Before creating a PR, check if one already exists for the current branch with `gh pr list --head $(git rev-parse --abbrev-ref HEAD)`. Only create a new PR if none exists. +- Push the branch with /git-publish. Never hand-roll `git push`: `git push origin $(...)`. +- To open a change request, use /change-request-publish — it skips creation when one already exists for the current branch. - **Default behavior:** Execute the plan task-by-task by dispatching a fresh `@implement-task` worker for each task. This is the standard workflow — do not deviate unless the user explicitly requests inline implementation. - **Inline implementation:** Acceptable only when the user explicitly asks you to implement directly rather than delegating. When executing inline, apply the same task/review gates as delegated workers. - **Controller duties:** Your primary role is orchestration — dispatch tasks, package context for workers, review worker reports and diffs, enforce spec compliance and code quality review gates, and run verification before any completion claim. @@ -110,6 +110,6 @@ Execution rules: - Stop and ask for feedback only when the same task fails more than 3 times, the plan conflicts with code reality, or an architectural decision is required. - If a task is too complex or requires an architectural decision, report the blocker and recommend human escalation. - Run targeted verification while iterating and the required final verification before claiming completion. -- After final verification, commit all changes, push the branch with `git push origin $(git rev-parse --abbrev-ref HEAD)`, and create a GitHub pull request if one does not already exist. Check existence with `gh pr list --head $(git rev-parse --abbrev-ref HEAD)`. Never push to `main`. +- After final verification, commit all changes and publish. Push the branch with /git-publish, then open a change request with /change-request-publish (no-ops when one already exists for the current branch). Use `workflow-verification` before any completion claim. Do not say work is complete, fixed, or passing unless the relevant commands have just run successfully. diff --git a/core/opencode/agents/planner.md b/core/opencode/agents/planner.md index c573601..3c44ec4 100644 --- a/core/opencode/agents/planner.md +++ b/core/opencode/agents/planner.md @@ -26,18 +26,7 @@ permission: "git commit *": allow "git worktree remove *": deny - "git push origin *": allow - "git push --force *": deny - "git push -f *": deny - "git push --force-with-lease *": ask - "git push origin --force *": deny - "git push origin -f *": deny - "git push origin --force-with-lease *": ask - "git push * --force *": deny - "git push * -f *": deny - "git push * --force-with-lease *": ask - "git push origin main*": deny - "git push origin +main*": deny + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow "grep *": allow "ls *": allow @@ -59,13 +48,14 @@ permission: "*": deny "workflow-planning": allow "grill-with-docs": allow + "git-publish": allow --- You are the planning agent. Load `docs/agents/planner.md` before writing a plan and follow its document list exactly. Load the approved spec first. -Your job is to write implementation plans, not code. Use the `workflow-planning` skill. Write plans to `plans/YYYY-MM-DD-feature-name/plan.md` next to the spec. +Your job is to write implementation plans, not code. Use the /workflow-planning skill. Write plans to `plans/YYYY-MM-DD-feature-name/plan.md` next to the spec. Plan requirements: @@ -88,7 +78,7 @@ Shell guidance: - Prefer relative workspace paths in shell commands and examples (e.g., `mkdir -p plans/2026-05-25-feature-name`). - Avoid absolute workspace paths in shell commands unless a tool explicitly requires them. -- Always use `git push origin $(git rev-parse --abbrev-ref HEAD)` — never use bare `git push` to avoid accidentally pushing to `main`. +- Push the branch with /git-publish. Never hand-roll `git push`. Do not invoke brainstorming automatically. If requirements are unclear enough that planning would be speculative, ask the user whether to switch to `@brainstorm`. diff --git a/core/opencode/agents/review-code.md b/core/opencode/agents/review-code.md index 1c07234..0cb9ece 100644 --- a/core/opencode/agents/review-code.md +++ b/core/opencode/agents/review-code.md @@ -26,23 +26,31 @@ permission: "git push *": ask - "bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh *": allow - 'bash ".agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh" *': allow - "bash .agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh *": ask - 'bash ".agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh" *': ask + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + + "bash .agents/skills/change-request-publish/scripts/open-change-request.sh*": allow + + "bash .agents/skills/change-request-comments/scripts/fetch-comments.sh *": allow + 'bash ".agents/skills/change-request-comments/scripts/fetch-comments.sh" *': allow + "bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh *": ask + 'bash ".agents/skills/change-request-comments/scripts/reply-to-comment.sh" *': ask task: "*": deny "explore": allow "implement-task": allow skill: "*": deny - "github-pr-comments": allow + "change-request-comments": allow + "git-publish": allow + "change-request-publish": allow --- You are the code review agent. Load `docs/agents/review-code.md` before reviewing and follow its document list exactly. +Use /change-request-comments when reading and drafting replies to change request comments. + Review priorities: - Bugs, behavior regressions, data corruption, security, race conditions, and broken error handling. @@ -64,15 +72,15 @@ Review priorities: Use this standard review-code workflow unless the user explicitly requests a different scope: -1. Read open PR comments first by using the `github-pr-comments` skill. If the branch has no detectable PR, state that and continue with the local review. +1. Read open change request comments first by using /change-request-comments. If the branch has no detectable change request, state that and continue with the local review. 2. Identify the plan/spec under review. If multiple candidate plans exist and the user did not state which one to use, ask before continuing the plan-conformance part of the review. 3. Review the code yourself against architecture, coding guidelines, testing guidance, logging guidance, the approved plan/spec, documented domain language, ADRs, and any area docs loaded from `docs/agents/review-code.md`. 4. Combine PR comments, user notes, external notes, and your own findings into one deduplicated list of actionable issues. 5. Present suggested fixes as findings first, ordered by severity. Do not edit application code. 6. After user approval, write a fix/refactoring plan under `plans/**` when the fixes need planning or exceed trivial review-scoped changes. Do not adapt an unrelated existing implementation plan for new review findings. Write review finding plans **next to the original plan** in the same directory (e.g. `plans//review-findings.md`). Do not create new date-prefixed folders for review findings. -7. If the user approves dispatching `implement-task` for trivial review-scoped fixes, dispatch focused tasks only after presenting the exact fix instructions. +7. If the user approves dispatching @implement-task for trivial review-scoped fixes, dispatch focused tasks only after presenting the exact fix instructions. 8. After plan updates or implement-task results, self-review every tracked remark and finding. Map each item to the finding, changed file, fix-plan section, or intentional unresolved status. -9. Draft exact GitHub replies for resolved PR comments and ask for explicit approval before posting. Approval to dispatch fixes, write a fix plan, or edit files does not authorize posting GitHub comments. +9. Draft exact replies for resolved change request comments and ask for explicit approval before posting. Approval to dispatch fixes, write a fix plan, or edit files does not authorize posting comments. Output findings first, ordered by severity, with file and line references. If no findings are found, say so and note any areas you could not verify from the diff alone. @@ -93,6 +101,6 @@ When escalating, clearly state: 2. Why the issues exceed review-fix scope 3. Which agent (`@planner` or `@brainstorm`) should handle it and why -When reviewing external or GitHub comments, use `github-pr-comments`. Always check open PR comments by default unless the user explicitly says not to. Verify each technical claim before recommending changes. Suggest changes only. Before posting any GitHub issue comment, PR conversation comment, or inline review reply, present the exact draft reply and wait for explicit user approval. Approval to dispatch fixes, write a fix plan, or edit files does not authorize posting GitHub comments. +When reviewing external comments, use /change-request-comments. Always check open change request comments by default unless the user explicitly says not to. Verify each technical claim before recommending changes. Suggest changes only. Before posting any comment reply, present the exact draft reply and wait for explicit user approval. Approval to dispatch fixes, write a fix plan, or edit files does not authorize posting comments. When fixes are needed (and escalation rules do not apply), dispatch `@implement-task` with the specific fix instructions. Provide the full context: what to change, why, and which files are affected. Do not edit code directly — let implement-task handle the implementation with proper TDD and verification. diff --git a/core/opencode/agents/review-plan.md b/core/opencode/agents/review-plan.md index a0c65c4..59f80ff 100644 --- a/core/opencode/agents/review-plan.md +++ b/core/opencode/agents/review-plan.md @@ -21,26 +21,28 @@ permission: "git add plans/*": allow "git commit *": allow - "git push *": allow - "git push origin main*": deny + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow "git worktree remove *": deny - "bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh *": allow - 'bash ".agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh" *': allow - "bash .agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh *": allow - 'bash ".agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh" *': allow + "bash .agents/skills/change-request-comments/scripts/fetch-comments.sh *": allow + 'bash ".agents/skills/change-request-comments/scripts/fetch-comments.sh" *': allow + "bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh *": ask + 'bash ".agents/skills/change-request-comments/scripts/reply-to-comment.sh" *': ask task: "*": deny "explore": allow skill: "*": deny - "github-pr-comments": allow + "change-request-comments": allow + "git-publish": allow --- You are the spec and plan review agent. Load `docs/agents/review-plan.md` before every review and follow its document list exactly. +Use /change-request-comments when reading and drafting replies to change request comments. + Review goals: - For specs: check clarity, completeness, scope, non-goals, architecture fit, data boundaries, missing edge cases, and testability. @@ -60,14 +62,14 @@ Review goals: Use this standard review-plan workflow unless the user explicitly requests a different scope: -1. Read open PR comments first by using the `github-pr-comments` skill. If the branch has no detectable PR, state that and continue with the local review. +1. Read open change request comments first by using /change-request-comments. If the branch has no detectable change request, state that and continue with the local review. 2. Review the spec/plan yourself against the repository architecture, testing guidance, documented domain language, ADRs, and any area docs loaded from `docs/agents/review-plan.md`. 3. Combine PR comments, user notes, external notes, and your own findings into one deduplicated list of actionable issues. 4. Present suggested fixes as blocking issues and advisory suggestions. Do not edit `plans/**` yet. 5. Wait for explicit user approval before editing the spec or plan. 6. After approved edits, self-review every tracked remark and finding. Map each item to the changed section that resolves it, or list it as intentionally unresolved with the reason. -7. Draft exact GitHub replies for resolved PR comments and ask for explicit approval before posting. Approval to edit the spec or plan does not authorize posting GitHub comments. +7. Draft exact replies for resolved change request comments and ask for explicit approval before posting. Approval to edit the spec or plan does not authorize posting comments. -When reviewing external or GitHub comments, use `github-pr-comments`. Always check open PR comments by default unless the user explicitly says not to. Verify each technical claim before recommending changes. Suggest changes only. Before posting any GitHub issue comment, PR conversation comment, or inline review reply, present the exact draft reply and wait for explicit user approval. Approval to edit `plans/**` does not authorize posting GitHub comments. +When reviewing external comments, use /change-request-comments. Always check open change request comments by default unless the user explicitly says not to. Verify each technical claim before recommending changes. Suggest changes only. Before posting any comment reply, present the exact draft reply and wait for explicit user approval. Approval to edit `plans/**` does not authorize posting comments. -When pushing approved spec/plan edits, always use `git push origin $(git rev-parse --abbrev-ref HEAD)`. Never push to `main`. +Push the branch with /git-publish. Never hand-roll `git push`. diff --git a/plans/2026-06-25-dual-harness-forge/plan.md b/plans/2026-06-25-dual-harness-forge/plan.md new file mode 100644 index 0000000..fe9e85c --- /dev/null +++ b/plans/2026-06-25-dual-harness-forge/plan.md @@ -0,0 +1,1226 @@ +# Dual-Harness Implementer & GitLab Platform Skills + +> **For implementation agents:** Execute this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Close two structural gaps identified by inspecting an adapted downstream project: (1) Claude Code has no `/implement` controller — implementation is OpenCode-only, and (2) publishing/review-comment skills are GitHub-only — no `glab` equivalents exist for GitLab-hosted projects. + +**Configuration shape:** The Claude implementer adds two files to the core template layer and updates existing skills, agents, docs, and settings for cross-references. GitLab skills (`gitlab-publish`, `gitlab-mr-comments`) are added alongside the existing GitHub skills in `core/agents/skills/`. Both sets are always installed; the agents detect which platform the project uses from CLI availability and tool output. Permissions for `glab` are added to `opencode.json` and `claude/settings.json` alongside the existing `gh` permissions. The root `scripts/publish-branch.sh` is preserved as a compatibility wrapper that delegates to the new `github-publish` skill. + +**Configuration surface:** +- New: `core/claude/skills/implement/SKILL.md`, `core/claude/agents/implement-task.md` +- New: `core/agents/skills/github-publish/` (publish skill wrapping the existing `scripts/publish-branch.sh` logic) +- New: `core/agents/skills/gitlab-publish/` (publish skill using `glab`) +- New: `core/agents/skills/gitlab-mr-comments/` (MR comment skill using `glab`) +- Modified: `core/claude/README.md`, `core/claude/skills/planner/SKILL.md`, `core/claude/skills/review-code/SKILL.md`, `core/claude/skills/review-plan/SKILL.md`, `core/claude/skills/brainstorm/SKILL.md`, `core/claude/skills/finish/SKILL.md`, `core/claude/settings.json`, `core/opencode/agents/implement.md`, `core/opencode/agents/finish.md`, `core/opencode/agents/review-code.md`, `core/opencode/agents/review-plan.md`, `core/opencode/agents/brainstorm.md`, `core/opencode/agents/planner.md`, `core/agents/skills/workflow-implementation/SKILL.md`, `core/opencode.json`, `README.md`, `scripts/init.sh`, `scripts/copy.sh`, `scripts/publish-branch.sh` + +--- + +## Task 1: Create Claude `/implement` Controller Skill + +**Files:** +- Create: `core/claude/skills/implement/SKILL.md` + +- [ ] **Step 1: Write the implement controller skill** + +Create `core/claude/skills/implement/SKILL.md`: + +```markdown +--- +name: implement +description: Executes an approved plan task-by-task in Claude Code — dispatches a fresh implement-task worker per task, reviews each diff, and runs verification before any completion claim. +argument-hint: [path to approved plan] +disable-model-invocation: true +--- + +You are the implementation controller for this repository. +This is the Claude Code half of implementation; OpenCode (`@implement`) is the equivalent controller on that harness. +Pick one harness per branch — do not run both controllers over the same plan. + +Approved plan (if provided): $ARGUMENTS + +## Load first + +Load `docs/agents/implement.md` and follow its document list exactly. +Then load the approved `plan.md` (and `spec.md` if present) and the docs the plan names. + +Use the `workflow-verification` skill before any completion claim. +Do not say work is complete, fixed, or passing unless the relevant verification commands have just run successfully. + +## Execution rules + +- **Never implement on `main`.** Create or ask for a scoped branch first. +- **Default behavior:** execute the plan task-by-task by dispatching a fresh `implement-task` worker (via the Agent tool, `subagent_type: implement-task`) for each task. This is the standard workflow — do not deviate unless the user explicitly asks for inline implementation. +- **Inline implementation:** acceptable only when the user explicitly asks you to implement directly. When inline, apply the same per-task and review gates as delegated workers. +- **Controller duties:** orchestrate — dispatch tasks, package context for workers, review worker reports and diffs, enforce spec compliance and code-quality gates, and run verification before any completion claim. Give the worker the full task text, relevant context, exact plan/spec paths, affected docs, and current branch state; do not make it reread the whole plan. +- **One focused commit per task.** Delegated workers create the task commit; commit inline only when you execute a task yourself. Review the worker report and `git diff` before moving on. +- Follow TDD for behavior changes unless the plan marks a step as docs-only, config-only, or trivial wiring. +- Do not pause between tasks for routine progress approval. +- Stop and ask only when the same task fails more than three times, the plan conflicts with code reality, or an architectural decision is required. If a task needs an architectural decision, report the blocker and recommend human escalation. + +## Model selection + +Each per-task `implement-task` worker runs on Sonnet — the `implement-task` agent pins `model: sonnet`, so leave the model override off when dispatching it. +If a worker fails to deliver a task (after the per-task retry limit), re-dispatch the same task with a `model` override to a more capable model before escalating to a human. + +## Verification + +Run targeted verification while iterating and the required final verification before claiming completion. Use the `workflow-verification` skill as the completion gate. + +## Shell guidance + +- Prefer `git mv` for moves/renames of tracked paths and `git rm` for removals of tracked paths. Use plain `mv`/`rm` only for untracked paths. +- Never work on or push to `main`. Publish through the appropriate publish skill — do not hand-roll `git push`. + +## Finishing + +After final verification, commit all changes and push the branch using the publish skill: +- GitHub: `bash .claude/skills/github-publish/scripts/push-branch.sh` then `bash .claude/skills/github-publish/scripts/open-pr.sh` +- GitLab: `bash .claude/skills/gitlab-publish/scripts/push-branch.sh` then `bash .claude/skills/gitlab-publish/scripts/open-mr.sh` + +The publish scripts refuse `main`/`master` and skip creation when a PR/MR already exists for the branch. +``` + +- [ ] **Step 2: Verify** + +Run: `codespell core/claude/skills/implement/SKILL.md` +Expected: no errors + +- [ ] **Step 3: Commit** + +```bash +git add core/claude/skills/implement/SKILL.md +git commit -m "feat: add Claude /implement controller skill" +``` + +--- + +## Task 2: Create Claude `implement-task` Subagent + +**Files:** +- Create: `core/claude/agents/implement-task.md` + +- [ ] **Step 1: Write the implement-task subagent definition** + +Create `core/claude/agents/implement-task.md`: + +```markdown +--- +name: implement-task +description: Worker that implements one approved plan task with TDD, a single focused commit, and self-review. Dispatched by the /implement controller — one fresh worker per task. +tools: Bash, Read, Edit, Write, Glob, Grep, Agent +model: sonnet +--- + +You are the single-task implementation worker for this repository. + +Implement exactly one task provided by the `/implement` controller. +Do not read or execute unrelated tasks from the plan unless the controller explicitly asks. + +Load `docs/agents/implement-task.md` and only the task context, spec/plan excerpts, and docs provided or named by the controller. + +## Rules + +- Never work on `main`. +- Follow the provided task text, spec/plan context, and the repository docs the guide (`docs/agents/implement-task.md`) tells you to load for the area you touch. +- Follow TDD for behavior changes unless the task is explicitly docs-only, config-only, or trivial wiring. +- Make the smallest correct change. Make small, justified adaptations to fit the current codebase, and report them clearly. +- If you need to verify an exact file path, module boundary, or existing pattern before editing, dispatch an `Explore` subagent with a focused question rather than guessing. Use it only for read-only verification of your one task — do not re-plan, widen scope, or dispatch other implementers. +- Prefer `git mv` for moves/renames of tracked paths and `git rm` for removals of tracked paths. Use plain `mv`/`rm` only for untracked paths. +- Commit exactly the task changes with the commit message specified by the plan, or a concise message if the plan omitted one. +- Do not push, create pull requests, amend commits, delete branches, remove worktrees, or dispatch other implementers. +- If requirements are unclear, report `NEEDS_CONTEXT` before editing. +- If blocked after three attempts on the same issue, report `BLOCKED` with what you tried. + +Before reporting, self-review the diff for spec compliance, overbuilding, tests, and obvious defects. + +## Report format + +- **Status:** `DONE`, `DONE_WITH_CONCERNS`, `NEEDS_CONTEXT`, or `BLOCKED` +- **Commit:** commit SHA or `none` +- **Implemented:** concise summary +- **Verification:** exact commands run and results +- **Files changed:** paths +- **Concerns:** anything the controller should inspect +``` + +- [ ] **Step 2: Verify** + +Run: `codespell core/claude/agents/implement-task.md` +Expected: no errors + +- [ ] **Step 3: Commit** + +```bash +git add core/claude/agents/implement-task.md +git commit -m "feat: add Claude implement-task subagent definition" +``` + +--- + +## Task 3: Create `github-publish` Authored Skill + +**Files:** +- Create: `core/agents/skills/github-publish/SKILL.md` +- Create: `core/agents/skills/github-publish/scripts/push-branch.sh` +- Create: `core/agents/skills/github-publish/scripts/open-pr.sh` + +- [ ] **Step 1: Write the SKILL.md** + +Create `core/agents/skills/github-publish/SKILL.md`: + +```markdown +--- +name: github-publish +description: Use when pushing a branch or opening a pull request — guards against pushing to the default branch and against force-pushing +user-invocable: false +--- + +# GitHub Publish + +Safe publishing for GitHub-hosted repositories. Pushing and opening pull requests go through bundled scripts that **refuse to act on `main`** and refuse force-pushes. + +## Why a script, not a raw `git push` + +`git push origin $(git rev-parse --abbrev-ref HEAD)` does **not** protect `main`: while you are on `main` it expands to `git push origin main`, and permission globs cannot see through the command substitution to block it. The guard has to inspect the current branch at run time, which is what these scripts do. + +## Push the current branch + +```bash +bash .agents/skills/github-publish/scripts/push-branch.sh +``` + +It pushes the current branch to `origin`, refusing if the branch is `main` or a detached `HEAD`, and refusing any `--force` / `-f` / `--force-with-lease` argument. Extra arguments (e.g. `--set-upstream`) are passed through. + +## Open a pull request + +```bash +bash .agents/skills/github-publish/scripts/open-pr.sh [gh pr create flags] +``` + +It refuses on `main`, skips creation when a PR already exists for the current branch (printing the existing one), and otherwise runs `gh pr create` for the current branch. Pass `--fill` to derive the title/description from commits, or `--title`/`--description` explicitly. + +## Rules + +- Never bypass these scripts with a raw `git push` to publish work — the protection only holds if publishing goes through them. +- Force-pushing and pushing to `main` are out of scope for this skill; they require a deliberate, human-run command. +``` + +- [ ] **Step 2: Write push-branch.sh** + +Create `core/agents/skills/github-publish/scripts/push-branch.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" + +case "$branch" in + main|master|HEAD) + printf 'Refusing to push: current branch is "%s". Create a scoped feature branch first.\n' "$branch" >&2 + exit 1 + ;; +esac + +# Force-pushing is never part of safe publishing. +for arg in "$@"; do + case "$arg" in + --force|-f|--force-with-lease|--force-with-lease=*) + printf 'Refusing to force-push from this script. Force-push must be a deliberate, human-run command.\n' >&2 + exit 1 + ;; + esac +done + +printf 'Pushing branch "%s" to origin...\n' "$branch" +git push origin "$branch" "$@" +``` + +- [ ] **Step 3: Write open-pr.sh** + +Create `core/agents/skills/github-publish/scripts/open-pr.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" + +case "$branch" in + main|master|HEAD) + printf 'Refusing: current branch is "%s". Open a pull request from a feature branch.\n' "$branch" >&2 + exit 1 + ;; +esac + +# Skip creation if a pull request already exists for this branch. +existing="$(gh pr list --head "$branch" --json url --jq 'length')" +if [ "${existing:-0}" -gt 0 ]; then + printf 'A pull request already exists for branch "%s":\n' "$branch" + gh pr list --head "$branch" --json url,title --jq '.[] | " \(.title) — \(.url)"' + exit 0 +fi + +printf 'Opening pull request for branch "%s"...\n' "$branch" +gh pr create --head "$branch" "$@" +``` + +- [ ] **Step 4: Make scripts executable** + +```bash +chmod +x core/agents/skills/github-publish/scripts/push-branch.sh +chmod +x core/agents/skills/github-publish/scripts/open-pr.sh +``` + +- [ ] **Step 5: Verify** + +Run: `shellcheck core/agents/skills/github-publish/scripts/*.sh` +Run: `bash -n core/agents/skills/github-publish/scripts/*.sh` +Run: `codespell core/agents/skills/github-publish/SKILL.md` +Expected: no errors + +- [ ] **Step 6: Commit** + +```bash +git add core/agents/skills/github-publish/ +git commit -m "feat: add github-publish authored skill with push guard and PR opener" +``` + +--- + +## Task 4: Create `gitlab-publish` Authored Skill + +**Files:** +- Create: `core/agents/skills/gitlab-publish/SKILL.md` +- Create: `core/agents/skills/gitlab-publish/scripts/push-branch.sh` +- Create: `core/agents/skills/gitlab-publish/scripts/open-mr.sh` + +- [ ] **Step 1: Write the SKILL.md** + +Create `core/agents/skills/gitlab-publish/SKILL.md`: + +```markdown +--- +name: gitlab-publish +description: Use when pushing a branch or opening a merge request on a GitLab-hosted repository — guards against pushing to the default branch and against force-pushing +user-invocable: false +--- + +# GitLab Publish + +Safe publishing for GitLab-hosted repositories. Pushing and opening merge requests go through bundled scripts that **refuse to act on `main`/`master`** and refuse force-pushes. + +## Why a script, not a raw `git push` + +`git push origin $(git rev-parse --abbrev-ref HEAD)` does **not** protect `main`: while you are on `main` it expands to `git push origin main`, and permission globs cannot see through the command substitution to block it. The guard has to inspect the current branch at run time, which is what these scripts do. + +## Push the current branch + +```bash +bash .agents/skills/gitlab-publish/scripts/push-branch.sh +``` + +It pushes the current branch to `origin`, refusing if the branch is `main`, `master`, or a detached `HEAD`, and refusing any `--force` / `-f` / `--force-with-lease` argument. Extra arguments (e.g. `--set-upstream`) are passed through. + +## Open a merge request + +```bash +bash .agents/skills/gitlab-publish/scripts/open-mr.sh [glab mr create flags] +``` + +It refuses on `main`/`master`, skips creation when an MR already exists for the current branch (printing the existing one), and otherwise runs `glab mr create` for the current branch. Pass `--fill` to derive the title/description from commits, or `--title`/`--description` explicitly. + +## Rules + +- Never bypass these scripts with a raw `git push` to publish work — the protection only holds if publishing goes through them. +- Force-pushing and pushing to `main`/`master` are out of scope for this skill; they require a deliberate, human-run command. +``` + +- [ ] **Step 2: Write push-branch.sh** + +Create `core/agents/skills/gitlab-publish/scripts/push-branch.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" + +case "$branch" in + main|master|HEAD) + printf 'Refusing to push: current branch is "%s". Create a scoped feature branch first.\n' "$branch" >&2 + exit 1 + ;; +esac + +# Force-pushing is never part of safe publishing. +for arg in "$@"; do + case "$arg" in + --force|-f|--force-with-lease|--force-with-lease=*) + printf 'Refusing to force-push from this script. Force-push must be a deliberate, human-run command.\n' >&2 + exit 1 + ;; + esac +done + +printf 'Pushing branch "%s" to origin...\n' "$branch" +git push origin "$branch" "$@" +``` + +- [ ] **Step 3: Write open-mr.sh** + +Create `core/agents/skills/gitlab-publish/scripts/open-mr.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +branch="$(git rev-parse --abbrev-ref HEAD)" + +case "$branch" in + main|master|HEAD) + printf 'Refusing: current branch is "%s". Open a merge request from a feature branch.\n' "$branch" >&2 + exit 1 + ;; +esac + +# Skip creation if a merge request already exists for this branch. +existing="$(glab mr list --source-branch "$branch" -F json 2>/dev/null | jq -r 'length' || echo 0)" +if [ "${existing:-0}" -gt 0 ]; then + printf 'A merge request already exists for branch "%s":\n' "$branch" + glab mr list --source-branch "$branch" + exit 0 +fi + +printf 'Opening merge request for branch "%s"...\n' "$branch" +glab mr create --source-branch "$branch" "$@" +``` + +- [ ] **Step 4: Make scripts executable** + +```bash +chmod +x core/agents/skills/gitlab-publish/scripts/push-branch.sh +chmod +x core/agents/skills/gitlab-publish/scripts/open-mr.sh +``` + +- [ ] **Step 5: Verify** + +Run: `shellcheck core/agents/skills/gitlab-publish/scripts/*.sh` +Run: `bash -n core/agents/skills/gitlab-publish/scripts/*.sh` +Run: `codespell core/agents/skills/gitlab-publish/SKILL.md` +Expected: no errors + +- [ ] **Step 6: Commit** + +```bash +git add core/agents/skills/gitlab-publish/ +git commit -m "feat: add gitlab-publish authored skill with push guard and MR opener" +``` + +--- + +## Task 5: Create `gitlab-mr-comments` Authored Skill + +**Files:** +- Create: `core/agents/skills/gitlab-mr-comments/SKILL.md` +- Create: `core/agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh` +- Create: `core/agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh` + +- [ ] **Step 1: Write the SKILL.md** + +Create `core/agents/skills/gitlab-mr-comments/SKILL.md`: + +```markdown +--- +name: gitlab-mr-comments +description: Use when GitLab merge request feedback lives in plain notes, inline diff discussions, or discussion threads +user-invocable: false +--- + +# GitLab MR Comments + +Use this for team merge request review workflows where feedback is stored as GitLab notes and discussions. This is the GitLab equivalent of `github-pr-comments`. + +## Baseline Failure To Avoid + +Agents naturally mix the GitLab note surfaces: they may read a standalone conversation note when feedback is actually an unresolved inline diff discussion, post a new top-level note instead of replying inside the existing thread, or edit someone else's note instead of replying to it. + +## Skill Scope + +This skill owns MR note mechanics: fetching notes, discussions, and the diff; distinguishing standalone conversation notes from inline diff discussions; obtaining exact discussion IDs; and posting approved replies through project scripts. + +This skill does not own role-level review orchestration. The calling review agent decides how to interpret notes, validate technical claims, combine them with other findings, suggest fixes, edit files, dispatch implementers, or perform final self-review. + +## Read Notes + +Use the bundled read-only helper before proposing fixes. It fetches standalone conversation notes, unresolved inline diff discussions, and the MR diff: + +```bash +bash .agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh [] +``` + +When `` is omitted, the script auto-detects the MR internal ID (`iid`) from the current branch. + +Run the command exactly as shown, without quoting the script path. The review agents have explicit auto-permissions for this command form. + +The helper prints a compact summary first, then full MR metadata, the discussions JSON, and the MR diff. Use the summary for grouping and the full JSON sections for exact discussion IDs. + +Optional read-only modes: + +```bash +bash .agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh [] --comments-only +bash .agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh [] --diff-only +bash .agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh [] --json +``` + +Do not call `glab` directly for reading MR metadata, notes, discussions, or diffs. The helper is the stable interface for this workflow. + +## Classify Notes + +- Standalone conversation notes: top-level MR discussion entries (`individual_note: true`), the GitLab analog of a plain comment. +- Inline diff discussions: resolvable threads whose notes carry a `diff position` (file path and line). Surface only those that are unresolved. +- Outdated inline discussions may still be valid; verify against the current diff before dismissing. + +## Review Workflow + +1. Fetch MR notes and diff when the caller's workflow involves an MR. +2. Use the summary to group target types, then use the full discussions JSON for exact discussion IDs. +3. Distinguish standalone conversation notes from inline diff discussions before posting replies. +4. Surface outdated inline discussions as context; the calling review agent decides whether they still apply. +5. For posting, require an exact approved reply batch with the discussion ID and body. +6. Post only the exact approved reply batch. + +## Replies + +Replying mutates GitLab state, so it is not hidden behind the read helper. Approval for edits, fixes, planning, or any other non-GitLab action does not authorize posting GitLab notes. + +Before posting, present the exact reply batch to the user. Include the discussion ID and body. Wait for explicit approval for that batch. + +After approval, reply inside the existing thread through the project script. Use the `id` of the discussion from the read helper as `discussion_id`: + +Pass a JSON array as the final argument: + +```bash +bash .agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh [] '[ + { "discussion_id": "a1b2c3d4...", "body": "Fixed in abc123." }, + { "discussion_id": "e5f6g7h8...", "body": "Good catch, updated." } +]' +``` + +The MR number is optional; when omitted, the script auto-detects it from the current branch. Use a one-element array for a single reply. This script replies inside existing discussion threads; it does not open new standalone notes. + +## Common Mistakes + +- Treating standalone conversation notes as complete enough for inline diff review. +- Posting a new top-level note when the user asked to reply inside an inline thread. +- Resolving, editing, or deleting notes without explicit approval. +- Applying external feedback without checking whether it is technically correct. +- Treating approval for edits, fixes, planning, or other non-GitLab work as approval to post GitLab replies. +- Posting a reply batch that differs from the exact batch the user approved. +``` + +- [ ] **Step 2: Write fetch-mr-comments.sh** + +Create `core/agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -gt 2 ]; then + printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 + exit 2 +fi + +if [[ "${1:-}" =~ ^[0-9]+$ ]]; then + mr="$1" + mode="${2:-}" +else + mr="$(glab mr view -F json | jq -r '.iid')" + mode="${1:-}" +fi +case "$mode" in + ""|--comments-only|--diff-only|--json) ;; + *) + printf 'Unknown mode: %s\n' "$mode" >&2 + printf 'Usage: %s [] [--comments-only|--diff-only|--json]\n' "$0" >&2 + exit 2 + ;; +esac + +if [ "$mode" = "--diff-only" ]; then + glab mr diff "$mr" + exit 0 +fi + +mr_json="$(glab mr view "$mr" -F json)" +discussions_json="$(glab api --paginate "projects/:id/merge_requests/$mr/discussions?per_page=100")" + +if [ "$mode" = "--json" ]; then + jq -n \ + --argjson mr "$mr_json" \ + --argjson discussions "$discussions_json" \ + '{ mr: $mr, discussions: $discussions }' + exit 0 +fi + +printf '## Note Summary\n' +jq -r \ + --argjson mr "$mr_json" ' + def summarize: (. // "" | gsub("\\s+"; " ") | .[0:180]); + "MR !\($mr.iid): \($mr.title)", + "Branch: \($mr.source_branch) -> \($mr.target_branch)", + "URL: \($mr.web_url)", + "", + "Standalone conversation notes (\([.[] | select(.individual_note == true)] | length)):", + ( .[] | select(.individual_note == true) | .notes[] | select(.system == false) + | "- discussion_id=\(.id // "?") note=\(.id) author=\(.author.username) updated=\(.updated_at): \(.body | summarize)" ), + "", + "Unresolved inline diff discussions:", + ( .[] | select(.individual_note != true) | . as $d + | select([.notes[] | select(.resolvable == true and .resolved == false)] | length > 0) + | .notes[0] as $n + | "- discussion_id=\($d.id) \($n.position.new_path // $n.position.old_path // "?"):\($n.position.new_line // $n.position.old_line // "?") author=\($n.author.username) updated=\($n.updated_at): \($n.body | summarize)" ) + ' <<<"$discussions_json" + +printf '\n## MR\n' +printf '%s\n' "$mr_json" + +printf '\n## Discussions\n' +printf '%s\n' "$discussions_json" + +if [ "$mode" = "--comments-only" ]; then + exit 0 +fi + +printf '\n## Diff\n' +glab mr diff "$mr" +``` + +- [ ] **Step 3: Write reply-to-mr-comment.sh** + +Create `core/agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +if [ "$#" -eq 1 ]; then + mr="$(glab mr view -F json | jq -r '.iid')" + replies_json="$1" +elif [ "$#" -eq 2 ] && [[ "$1" =~ ^[0-9]+$ ]]; then + mr="$1" + replies_json="$2" +else + printf 'Usage: %s [] \n' "$0" >&2 + exit 2 +fi + +if ! jq -e 'type == "array" and all(.[]; (.discussion_id | type == "string") and (.body | type == "string"))' \ + >/dev/null <<<"$replies_json"; then + printf 'Reply JSON must be an array of { "discussion_id": string, "body": string } objects.\n' >&2 + exit 2 +fi + +count=$(jq length <<<"$replies_json") +printf 'Posting %d replies to MR !%s...\n' "$count" "$mr" + +while IFS= read -r reply; do + discussion_id=$(jq -r '.discussion_id' <<<"$reply") + body=$(jq -r '.body' <<<"$reply") + + printf ' Replying to discussion %s... ' "$discussion_id" + glab mr note create "$mr" --reply "$discussion_id" -m "$body" + printf 'done\n' +done < <(jq -c '.[]' <<<"$replies_json") + +printf 'All %d replies posted.\n' "$count" +``` + +- [ ] **Step 4: Make scripts executable** + +```bash +chmod +x core/agents/skills/gitlab-mr-comments/scripts/*.sh +``` + +- [ ] **Step 5: Verify** + +Run: `shellcheck core/agents/skills/gitlab-mr-comments/scripts/*.sh` +Run: `bash -n core/agents/skills/gitlab-mr-comments/scripts/*.sh` +Run: `codespell core/agents/skills/gitlab-mr-comments/SKILL.md` +Expected: no errors + +- [ ] **Step 6: Commit** + +```bash +git add core/agents/skills/gitlab-mr-comments/ +git commit -m "feat: add gitlab-mr-comments authored skill with MR discussion fetch and reply" +``` + +--- + +## Task 6: Wire `github-publish` into OpenCode Agents + +**Files:** +- Modify: `core/opencode/agents/implement.md` +- Modify: `core/opencode/agents/finish.md` + +- [ ] **Step 1: Update implement.md** + +In `core/opencode/agents/implement.md`: + +Replace the raw push/PR instructions with `github-publish` skill references: + +- Replace line 100 (`Always use 'git push origin $(git rev-parse --abbrev-ref HEAD)'...`) with: + `Publish through the 'github-publish' skill — 'bash .agents/skills/github-publish/scripts/push-branch.sh' to push, which refuses 'main'. Never hand-roll 'git push': 'git push origin $(...)' silently pushes 'main' when the current branch is 'main'.` + +- Replace line 101 (the `gh pr list --head` check) with: + `Open a pull request with 'bash .agents/skills/github-publish/scripts/open-pr.sh' — it skips creation when a PR already exists for the current branch.` + +- Replace line 113 (the final push+PR block) with: + `After final verification, commit all changes, push the branch with 'bash .agents/skills/github-publish/scripts/push-branch.sh', and open a GitHub pull request with 'bash .agents/skills/github-publish/scripts/open-pr.sh' (it no-ops when a PR already exists). The push script refuses 'main'.` + +- Add `github-publish` to the skill permission allowlist after `"workflow-verification": allow`. + +- Add the publish script bash permissions: + ``` + "bash .agents/skills/github-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/github-publish/scripts/open-pr.sh*": ask + ``` + +- Remove the `"git push origin $(git rev-parse --abbrev-ref HEAD)": allow` line. + +- [ ] **Step 2: Update finish.md** + +Same pattern in `core/opencode/agents/finish.md` — replace raw push instructions with `github-publish` skill references, add the skill to the allowlist, add script bash permissions. + +- [ ] **Step 3: Verify** + +Run: `codespell core/opencode/agents/implement.md core/opencode/agents/finish.md` +Expected: no errors + +- [ ] **Step 4: Commit** + +```bash +git add core/opencode/agents/implement.md core/opencode/agents/finish.md +git commit -m "refactor: wire github-publish skill into OpenCode implement and finish agents" +``` + +--- + +## Task 7: Wire `gitlab-publish` into OpenCode Agents + +**Files:** +- Modify: `core/opencode/agents/implement.md` +- Modify: `core/opencode/agents/finish.md` +- Modify: `core/opencode.json` + +- [ ] **Step 1: Add gitlab-publish permissions to implement.md** + +In `core/opencode/agents/implement.md`, add alongside the `github-publish` permissions: + +```yaml + "bash .agents/skills/gitlab-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/gitlab-publish/scripts/open-mr.sh*": ask +``` + +And add to the skill allowlist: +```yaml + "gitlab-publish": allow +``` + +- [ ] **Step 2: Add gitlab-publish permissions to finish.md** + +Same pattern in `core/opencode/agents/finish.md`. + +- [ ] **Step 3: Add glab permissions to opencode.json** + +In `core/opencode.json`, add to the global permission bash section: + +```json +"glab mr create *": "ask", +"glab mr list *": "allow", +"glab mr view *": "allow", +"glab mr diff *": "allow", +"glab issue create *": "ask", +"glab issue view *": "allow", +"glab issue list *": "allow", +"glab issue update *": "ask" +``` + +- [ ] **Step 4: Verify** + +Run: `jq . core/opencode.json` +Expected: valid JSON + +- [ ] **Step 5: Commit** + +```bash +git add core/opencode/agents/implement.md core/opencode/agents/finish.md core/opencode.json +git commit -m "feat: add gitlab-publish permissions and glab CLI access to OpenCode agents" +``` + +--- + +## Task 8: Wire Skills into Claude Settings + +**Files:** +- Modify: `core/claude/settings.json` +- Modify: `core/claude/skills/finish/SKILL.md` +- Modify: `core/claude/skills/brainstorm/SKILL.md` + +- [ ] **Step 1: Update settings.json** + +In `core/claude/settings.json`, add to the `allow` array: +``` +"Bash(bash .claude/skills/github-publish/scripts/push-branch.sh:*)", +"Bash(bash .claude/skills/gitlab-publish/scripts/push-branch.sh:*)", +"Bash(bash .claude/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh:*)", +"Bash(glab mr view:*)", +"Bash(glab mr diff:*)", +"Bash(glab mr list:*)", +"Bash(glab issue view:*)", +"Bash(glab issue list:*)" +``` + +Add to the `ask` array: +``` +"Bash(bash .claude/skills/github-publish/scripts/open-pr.sh:*)", +"Bash(bash .claude/skills/gitlab-publish/scripts/open-mr.sh:*)", +"Bash(bash .claude/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh:*)", +"Bash(glab mr create:*)", +"Bash(glab mr note create:*)", +"Bash(glab issue create:*)", +"Bash(glab issue update:*)" +``` + +- [ ] **Step 2: Update finish/SKILL.md** + +Replace the push instructions (step 5 and the "Push boundaries" section) to reference both publish skills: + +```markdown +5. **Commit and push.** Commit the feature doc and cleanup when the user requests it, + then push using the appropriate publish skill: + - GitHub: `bash .claude/skills/github-publish/scripts/push-branch.sh` + - GitLab: `bash .claude/skills/gitlab-publish/scripts/push-branch.sh` + +## Push boundaries + +Push only with the publish skill scripts. Never use bare `git push`, push to `main`, +force-push, delete remote refs, push tags, or push arbitrary refspecs without explicit +approval. Do not create PRs, amend commits, delete branches, close comments, or remove +worktrees. +``` + +- [ ] **Step 3: Update brainstorm/SKILL.md** + +Replace the shell guidance (lines 34-35): + +```markdown +- Publish through the appropriate publish skill — `bash .claude/skills/github-publish/scripts/push-branch.sh` (GitHub) or `bash .claude/skills/gitlab-publish/scripts/push-branch.sh` (GitLab). Never hand-roll `git push`. +``` + +- [ ] **Step 4: Verify** + +Run: `codespell core/claude/skills/finish/SKILL.md core/claude/skills/brainstorm/SKILL.md` +Run: `jq . core/claude/settings.json` +Expected: no errors + +- [ ] **Step 5: Commit** + +```bash +git add core/claude/settings.json core/claude/skills/finish/SKILL.md core/claude/skills/brainstorm/SKILL.md +git commit -m "feat: wire publish and gitlab skills into Claude settings, finish, and brainstorm" +``` + +--- + +## Task 9: Wire `gitlab-mr-comments` into Review Agents + +**Files:** +- Modify: `core/opencode/agents/review-code.md` (verify — may not exist as a separate file) +- Modify: `core/opencode/agents/review-plan.md` (verify) +- Modify: `core/claude/skills/review-code/SKILL.md` +- Modify: `core/claude/skills/review-plan/SKILL.md` +- Modify: `core/docs/agents/review-code.md` +- Modify: `core/docs/agents/review-plan.md` + +- [ ] **Step 1: Update role docs to mention both comment skills** + +In `core/docs/agents/review-code.md`, add to the Load section: +```markdown +- `github-pr-comments` or `gitlab-mr-comments` skill for PR/MR feedback (detect which CLI is available) +``` + +In `core/docs/agents/review-plan.md`, add the same. + +- [ ] **Step 2: Update Claude review-code/SKILL.md** + +In `core/claude/skills/review-code/SKILL.md`, update the references to `github-pr-comments`: + +Replace: `Use the 'github-pr-comments' skill for reading and drafting replies to PR comments.` +With: `Use the 'github-pr-comments' skill for GitHub PRs or 'gitlab-mr-comments' skill for GitLab MRs. Detect which CLI is available ('gh' or 'glab') or which the user specifies.` + +Update step 1 similarly: +Replace: `Read open PR comments first by using the 'github-pr-comments' skill.` +With: `Read open PR/MR comments first using the appropriate skill: 'github-pr-comments' for GitHub or 'gitlab-mr-comments' for GitLab.` + +- [ ] **Step 3: Update Claude review-plan/SKILL.md** + +Same pattern — update references to mention both skills with detection. + +- [ ] **Step 4: Add glab permissions to OpenCode review agents** + +In `core/opencode/agents/review-plan.md` and `core/opencode/agents/review-code.md`, add bash permissions for the gitlab-mr-comments scripts: + +```yaml + "bash .agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh*": allow + "bash .agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh*": ask + "glab mr view *": allow + "glab mr diff *": allow +``` + +And add `gitlab-mr-comments` to the skill allowlist. + +- [ ] **Step 5: Verify** + +Run: `codespell core/claude/skills/review-code/SKILL.md core/claude/skills/review-plan/SKILL.md core/docs/agents/review-code.md core/docs/agents/review-plan.md` +Expected: no errors + +- [ ] **Step 6: Commit** + +```bash +git add core/claude/skills/review-code/SKILL.md core/claude/skills/review-plan/SKILL.md core/docs/agents/review-code.md core/docs/agents/review-plan.md core/opencode/agents/review-code.md core/opencode/agents/review-plan.md +git commit -m "feat: wire gitlab-mr-comments into review agents alongside github-pr-comments" +``` + +--- + +## Task 10: Update Claude README for Dual Harness + +**Files:** +- Modify: `core/claude/README.md` + +- [ ] **Step 1: Update the skill table** + +Add `/implement` to the skill table: + +```markdown +| Skill (`/name`) | OpenCode counterpart | Role | +| --- | --- | --- | +| `/brainstorm` | `@brainstorm` | Idea → approved `spec.md` (interactive; offers domain grilling) | +| `/bugfix` | `@bugfix` | Investigate bug → structured issue; does not fix | +| `/planner` | `@planner` | Spec → task-by-task `plan.md` (offers grilling only when new domain language or non-trivial decisions appear) | +| `/implement` | `@implement` | Execute plan task-by-task via `implement-task` workers, verify, commit | +| `/review-plan` | `@review-plan` | Review + finalize the hand-off plan | +| `/review-code` | `@review-code` | Review a diff/PR/MR → fix-plan hand-off doc | +| `/finish` | `@finish` | Durable feature doc, light glossary/ADR reconciliation, cleanup | +``` + +- [ ] **Step 2: Update the design principle section** + +Replace the opening paragraph: +From: `Claude Code runs the **conversational** half of the agent pipeline. The **implementation** half stays in OpenCode.` +To: `Claude Code runs both the **conversational** and **implementation** halves of the agent pipeline. OpenCode provides the same pipeline; pick one harness per branch.` + +- [ ] **Step 3: Update the shared authored skills list** + +Add to the shared authored skills list: +``` +- `github-publish` — used by `/implement`, `/finish` (+ OpenCode) +- `gitlab-publish` — used by `/implement`, `/finish` (+ OpenCode; GitLab equivalent) +- `gitlab-mr-comments` — used by `/review-plan`, `/review-code` (+ OpenCode; GitLab equivalent of `github-pr-comments`) +``` + +- [ ] **Step 4: Update the "Usage" section** + +Update the usage line to include `/implement`: +`Type '/brainstorm', '/bugfix', '/implement', '/planner', '/review-plan', '/review-code', or '/finish'` + +- [ ] **Step 5: Update the permissions section** + +Mention that permissions include both `gh` and `glab` CLI access, and both GitHub and GitLab publish/comment skill scripts. + +- [ ] **Step 6: Update the symlink inventory** + +The symlink table should now include: +``` +| `github-publish` | `../../.agents/skills/github-publish` | +| `gitlab-publish` | `../../.agents/skills/gitlab-publish` | +| `gitlab-mr-comments` | `../../.agents/skills/gitlab-mr-comments` | +``` + +Remove the note about `workflow-implementation` being reserved for OpenCode. + +- [ ] **Step 7: Verify** + +Run: `codespell core/claude/README.md` +Expected: no errors + +- [ ] **Step 8: Commit** + +```bash +git add core/claude/README.md +git commit -m "docs: update Claude README for /implement controller and GitLab skills" +``` + +--- + +## Task 11: Update Existing Claude Skills for `/implement` Cross-References + +**Files:** +- Modify: `core/claude/skills/planner/SKILL.md` +- Modify: `core/claude/skills/review-code/SKILL.md` (if not already updated in Task 9) +- Modify: `core/claude/skills/review-plan/SKILL.md` (if not already updated in Task 9) + +- [ ] **Step 1: Update planner/SKILL.md stop conditions** + +Replace the stop conditions (lines 74-77): + +From: +``` +- After the plan is written, **stop**. Optionally suggest `/review-plan` before handoff. +- Implementation runs in **OpenCode** (`@implement` / `@implement-task`), not Claude Code. + Hand off to OpenCode for execution. +``` + +To: +``` +- After the plan is written, **stop**. Optionally suggest `/review-plan` before handoff. +- Implementation can run in either harness: Claude Code (`/implement`) or OpenCode + (`@implement`). Pick one per branch. Suggest the user's preferred harness. +``` + +- [ ] **Step 2: Verify** + +Run: `codespell core/claude/skills/planner/SKILL.md` +Expected: no errors + +- [ ] **Step 3: Commit** + +```bash +git add core/claude/skills/planner/SKILL.md +git commit -m "refactor: update Claude planner for dual-harness /implement handoff" +``` + +--- + +## Task 12: Convert `scripts/publish-branch.sh` to Compatibility Wrapper and Update Installers + +**Files:** +- Modify: `scripts/publish-branch.sh` +- Modify: `scripts/init.sh` +- Modify: `scripts/copy.sh` + +- [ ] **Step 1: Convert publish-branch.sh into a wrapper around the github-publish skill** + +The repo's self-maintenance agents (`@implement`, `@planning`, `@review`) and `AGENTS.md` still reference `scripts/publish-branch.sh`. Keep that path working by replacing its contents with a wrapper that delegates to the new `github-publish` skill scripts. + +Write `scripts/publish-branch.sh`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +# Compatibility wrapper: this repo's self-maintenance agents still invoke +# scripts/publish-branch.sh. It delegates to the github-publish authored skill. +bash core/agents/skills/github-publish/scripts/push-branch.sh --set-upstream +bash core/agents/skills/github-publish/scripts/open-pr.sh --fill +``` + +Make it executable: + +```bash +chmod +x scripts/publish-branch.sh +``` + +- [ ] **Step 2: Add new skills to init.sh symlink list** + +In `scripts/init.sh`, find the section that creates Claude symlinks (the loop that symlinks authored skills from `.agents/skills/` into `.claude/skills/`). Add `github-publish`, `gitlab-publish`, and `gitlab-mr-comments` to the list. + +Also ensure `gitlab-publish` and `gitlab-mr-comments` are copied into `.agents/skills/` alongside the existing skills. + +- [ ] **Step 3: Add new skills to copy.sh symlink list** + +Same changes in `scripts/copy.sh`. + +- [ ] **Step 4: Add `.claude/agents/` handling to the installers** + +The new Claude `implement-task` subagent lives in `core/claude/agents/implement-task.md`. Add copy logic to both `scripts/init.sh` and `scripts/copy.sh` so `.claude/agents/*.md` is installed into target projects, analogous to how `.opencode/agents/*.md` is handled. + +- [ ] **Step 5: Verify** + +Run: `shellcheck scripts/init.sh scripts/copy.sh scripts/publish-branch.sh` +Run: `bash -n scripts/init.sh scripts/copy.sh scripts/publish-branch.sh` +Run: `codespell scripts/publish-branch.sh` +Expected: no errors + +- [ ] **Step 6: Smoke-run the installers** + +Run a smoke test against `.temp/`: + +```bash +rm -rf .temp/smoke-init +./scripts/init.sh +# When prompted, choose a test target under .temp/smoke-init and any stack. +# After the run, verify that .temp/smoke-init/.claude/agents/implement-task.md exists +# and that .temp/smoke-init/.claude/skills/github-publish is a symlink resolving +# to ../../.agents/skills/github-publish. + +rm -rf .temp/smoke-copy +mkdir -p .temp/smoke-copy && cd .temp/smoke-copy && git init && git commit --allow-empty -m "init" && cd ../.. +./scripts/copy.sh .temp/smoke-copy +# Verify the same .claude/agents and .claude/skills symlink outcomes. +``` + +Clean up `.temp/smoke-init` and `.temp/smoke-copy` after verification. + +- [ ] **Step 7: Commit** + +```bash +git add scripts/publish-branch.sh scripts/init.sh scripts/copy.sh +git commit -m "feat: add gitlab skills and claude agents to installers, wrap publish-branch.sh" +``` + +--- + +## Task 13: Replace Remaining Raw Push References + +**Files:** +- Modify: `core/agents/skills/workflow-implementation/SKILL.md` +- Modify: `core/claude/skills/planner/SKILL.md` +- Modify: `core/claude/skills/review-plan/SKILL.md` +- Modify: `core/opencode/agents/brainstorm.md` +- Modify: `core/opencode/agents/planner.md` +- Modify: `core/opencode/agents/review-plan.md` + +- [ ] **Step 1: Update shared implementation skill** + +In `core/agents/skills/workflow-implementation/SKILL.md`, replace the raw push/PR instruction at line 100 with: + +`After final verification, the controller commits all changes, pushes the branch with 'bash .agents/skills/github-publish/scripts/push-branch.sh', and opens a GitHub pull request with 'bash .agents/skills/github-publish/scripts/open-pr.sh' if one does not already exist. Never push to 'main'.` + +- [ ] **Step 2: Update Claude planner and review-plan skills** + +In `core/claude/skills/planner/SKILL.md` line 69 and `core/claude/skills/review-plan/SKILL.md` line 81, replace the raw `git push origin $(git rev-parse --abbrev-ref HEAD)` instruction with: + +`- Publish through the appropriate publish skill — 'bash .claude/skills/github-publish/scripts/push-branch.sh' (GitHub) or 'bash .claude/skills/gitlab-publish/scripts/push-branch.sh' (GitLab). Never hand-roll 'git push'.` + +- [ ] **Step 3: Update OpenCode brainstorm, planner, and review-plan agents** + +In `core/opencode/agents/brainstorm.md` line 97, `core/opencode/agents/planner.md` line 91, and `core/opencode/agents/review-plan.md` line 73, replace the raw push instruction with publish-skill references and add the publish script bash permissions: + +```yaml +"bash .agents/skills/github-publish/scripts/push-branch.sh*": allow +"bash .agents/skills/gitlab-publish/scripts/push-branch.sh*": allow +``` + +Remove the `"git push origin $(git rev-parse --abbrev-ref HEAD)": allow` permission from each of these agents (where present). + +- [ ] **Step 4: Verify** + +Run: `codespell core/agents/skills/workflow-implementation/SKILL.md core/claude/skills/planner/SKILL.md core/claude/skills/review-plan/SKILL.md core/opencode/agents/brainstorm.md core/opencode/agents/planner.md core/opencode/agents/review-plan.md` +Expected: no errors + +- [ ] **Step 5: Commit** + +```bash +git add core/agents/skills/workflow-implementation/SKILL.md core/claude/skills/planner/SKILL.md core/claude/skills/review-plan/SKILL.md core/opencode/agents/brainstorm.md core/opencode/agents/planner.md core/opencode/agents/review-plan.md +git commit -m "refactor: replace remaining raw push references with publish skills" +``` + +--- + +## Task 14: Update Top-Level README + +**Files:** +- Modify: `README.md` + +- [ ] **Step 1: Update Installed Assets section** + +Add `implement` to the Claude Workflow Entry Skills: +``` +`brainstorm`, `bugfix`, `implement`, `finish`, `planner`, `review-code`, `review-plan` +``` + +Add GitLab skills to the Authored Reusable Skills: +``` +`grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, `workflow-implementation`, `workflow-verification`, `feature-documentation`, `github-publish`, `github-pr-comments`, `gitlab-publish`, `gitlab-mr-comments` +``` + +- [ ] **Step 2: Update Claude Symlink Model section** + +Add to the symlink table: +```markdown +| `github-publish` | `../../.agents/skills/github-publish` | +| `gitlab-publish` | `../../.agents/skills/gitlab-publish` | +| `gitlab-mr-comments` | `../../.agents/skills/gitlab-mr-comments` | +``` + +Remove the note about `workflow-implementation` being reserved for OpenCode. + +- [ ] **Step 3: Update workflow table** + +In the Implementation Cycle table, update step 7: +From: `OpenCode '@implement' (controller) which spawns '@implement-task' workers.` +To: `Claude '/implement' or OpenCode '@implement' (controller) which spawns implement-task workers. Pick one harness per branch.` + +- [ ] **Step 4: Verify** + +Run: `codespell README.md` +Expected: no errors + +- [ ] **Step 5: Commit** + +```bash +git add README.md +git commit -m "docs: update README for dual-harness /implement and GitLab skills" +``` + +--- + +## Task 15: Consistency Verification + +- [ ] **Step 1: Verify symlink table completeness** + +The Claude symlink table should list exactly these skills: +- `grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, `workflow-verification`, `feature-documentation` +- `github-publish`, `github-pr-comments` +- `gitlab-publish`, `gitlab-mr-comments` + +(10 total) + +- [ ] **Step 2: Verify no stale raw push references** + +Run: `grep -R 'git push origin \$(git rev-parse' core/ --include="*.md"` +Expected: no matches + +- [ ] **Step 3: Verify all skills exist on disk** + +Run: `ls core/agents/skills/github-publish/SKILL.md core/agents/skills/gitlab-publish/SKILL.md core/agents/skills/gitlab-mr-comments/SKILL.md` +Expected: all three files exist + +- [ ] **Step 4: Verify lockfiles** + +No changes to `skills-lock.json` or `core/skills-lock.json` — no new remote skills are being installed. All new skills are authored. + +- [ ] **Step 5: Final codespell pass** + +Run: `codespell core/ README.md scripts/` +Expected: no errors + +--- + +## Docs Used + +- `AGENTS.md` (repo-root conventions) — dot-mapping, sync invariants, skill lockfile scopes +- `core/claude/README.md` — current Claude skill inventory and symlink model +- `core/AGENTS.md` (template) — dual pipeline description, permissions +- `README.md` — installed assets, workflow table, extension guide +- `scripts/init.sh` — current installer flow and symlink creation +- `scripts/copy.sh` — current copy flow and symlink creation +- All `core/opencode/agents/*.md` — current agent definitions and permissions +- All `core/claude/skills/*/SKILL.md` — current Claude skill definitions +- `core/claude/settings.json` — current Claude permissions +- `core/agents/skills/github-pr-comments/` — pattern reference for platform skills +- `scripts/publish-branch.sh` — current publishing script (to be converted to a github-publish compatibility wrapper) diff --git a/plans/2026-06-25-dual-harness-forge/review-findings.md b/plans/2026-06-25-dual-harness-forge/review-findings.md new file mode 100644 index 0000000..4e0b8c0 --- /dev/null +++ b/plans/2026-06-25-dual-harness-forge/review-findings.md @@ -0,0 +1,414 @@ +# Review Findings Plan — PR #6 Dual Harness + GitLab Support + +This plan consolidates the current PR comments and local review findings for fixing the remaining issues on `feature/support-claude-implement-and-glab`. + +## Review summary + +### Blocking issues + +1. **Workflow-facing files leak skill internals.** OpenCode agents, Claude skills, and `workflow-bug-analysis` still instruct agents to run `.../skills//scripts/*.sh` directly. These files should name the skill to use, not its internal scripts. +2. **Provider-specific comment skills remain in the downstream install surface.** `github-pr-comments` and `gitlab-mr-comments` are still installed and documented even though PR feedback asks to move that logic behind `change-request-comments`. +3. **`change-request-comments` delegates to provider skill folders.** Its scripts still call `.agents/skills/github-pr-comments/...` and `.agents/skills/gitlab-mr-comments/...`, so removing those folders would break comment workflows unless the implementations move into the neutral skill. +4. **Permission/skill allowlists are inconsistent.** Several agents have script permissions without corresponding skill permissions, and review-code is missing publish capability for review-fix implementation plans. +5. **`AGENTS.md` does not yet encode the “no skill internals in workflow instructions” rule.** This is the root cause called out by the top-level PR comment. +6. **Neutral skill docs mix runtime instructions with design rationale.** Some `SKILL.md` files explain why the wrapper exists or expose implementation detail. Keep `SKILL.md` focused on invocation contract and move rationale into sibling `README.md` files. + +### Advisory issues + +1. **`core/claude/settings.json` has readability and policy drift.** Permissions should be grouped and `update-issue.sh` should appear in only one permission tier. +2. **`scripts/publish-branch.sh` conflicts with the new neutral skill model.** Decide whether to preserve the repo-local compatibility wrapper required by `AGENTS.md` or update `AGENTS.md` and self-maintenance symlinks. +3. **`scripts/check-workflow-boundaries.sh` catches provider names but not generic skill-internal script paths.** It should prevent both provider leakage and direct internal script leakage. + +## Implementation tasks + +### Task 1 — Add a hard workflow boundary rule to `AGENTS.md` + +**Files** + +- `AGENTS.md` + +**Changes** + +1. Add a section such as `## Skill Boundary Rule` after the symlink model. +2. State that workflow-facing files must instruct agents to use skills by name, for example `git-publish`, `change-request-publish`, `change-request-comments`, and `issue-tracker`. +3. State that workflow-facing files must not expose internal script paths like `.agents/skills//scripts/*.sh` or `.claude/skills//scripts/*.sh`. Boundary-skill implementation details may live in that skill's own scripts or sibling `README.md`; keep `SKILL.md` focused on the invocation contract. +4. Clarify that agent frontmatter and Claude settings may grant script permissions, but body instructions should stay skill-level. +5. Add provider-boundary language: provider-specific CLI details (`gh`, `glab`, provider comment scripts) belong only in provider/boundary skills, not generic workflow files. + +**Verification** + +```bash +codespell AGENTS.md +``` + +**Commit boundary** + +```text +Document skill boundary rules for workflow instructions +``` + +### Task 2 — Move provider comment implementations into `change-request-comments` + +**Files** + +- `core/agents/skills/change-request-comments/SKILL.md` +- `core/agents/skills/change-request-comments/scripts/fetch-comments.sh` +- `core/agents/skills/change-request-comments/scripts/reply-to-comment.sh` +- `core/agents/skills/github-pr-comments/` (delete from downstream template) +- `core/agents/skills/gitlab-mr-comments/` (delete from downstream template) +- `.agents/skills/github-pr-comments/` (convert from symlink to self-maintenance copy if this repo still needs PR comment review) +- `.claude/skills/github-pr-comments` (keep symlink to `.agents/skills/github-pr-comments`) + +**Changes** + +1. Before deleting `core/agents/skills/github-pr-comments/`, preserve this repo's self-maintenance review workflow by converting `.agents/skills/github-pr-comments` from a symlink into a real self-maintenance skill directory copied from the current GitHub implementation. Keep `.claude/skills/github-pr-comments` symlinked to `.agents/skills/github-pr-comments`. +2. Replace the GitHub branch of `change-request-comments/scripts/fetch-comments.sh` with the implementation currently in `core/agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh`; keep the public wrapper name `fetch-comments.sh`. +3. Replace the GitHub branch of `change-request-comments/scripts/reply-to-comment.sh` with the implementation currently in `core/agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh`; keep the public wrapper name `reply-to-comment.sh`. +4. Replace the GitLab branch of `fetch-comments.sh` with the implementation currently in `core/agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh`. +5. Replace the GitLab branch of `reply-to-comment.sh` with the implementation currently in `core/agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh`. +6. Keep host detection inside `change-request-comments`; it may call `gh`/`glab` because this skill is the provider boundary layer. +7. Preserve behavior and flags from both provider implementations, including `--comments-only`, `--diff-only`, `--json`, comment reply payload handling, and exit-code behavior. +8. Remove references from `change-request-comments/SKILL.md` and scripts that say it delegates to `github-pr-comments` or `gitlab-mr-comments` skill folders. +9. Delete `core/agents/skills/github-pr-comments/` and `core/agents/skills/gitlab-mr-comments/` with `git rm -r` after their logic is represented inside `change-request-comments` and the self-maintenance GitHub skill no longer points at `core/`. + +**Verification** + +```bash +bash -n core/agents/skills/change-request-comments/scripts/*.sh +shellcheck core/agents/skills/change-request-comments/scripts/*.sh +! rg -n "github-pr-comments|gitlab-mr-comments" core/agents/skills/change-request-comments +test ! -e core/agents/skills/github-pr-comments +test ! -e core/agents/skills/gitlab-mr-comments +test -d .agents/skills/github-pr-comments +test "$(readlink .claude/skills/github-pr-comments)" = "../../.agents/skills/github-pr-comments" +``` + +Expected result: no delegation to provider skill folders remains in `change-request-comments`. + +**Commit boundary** + +```text +Move comment provider logic into change-request-comments +``` + +### Task 2A — Split neutral skill rationale into sibling READMEs + +**Files** + +- `core/agents/skills/git-publish/SKILL.md` +- `core/agents/skills/git-publish/README.md` (add) +- `core/agents/skills/change-request-publish/SKILL.md` +- `core/agents/skills/change-request-publish/README.md` (add) +- `core/agents/skills/change-request-comments/SKILL.md` +- `core/agents/skills/change-request-comments/README.md` (add) +- `core/agents/skills/issue-tracker/SKILL.md` +- `core/agents/skills/issue-tracker/README.md` (add) + +**Changes** + +1. Keep each `SKILL.md` focused on the agent-facing contract: when to use the skill, what high-level capability it provides, required inputs/outputs, and safety rules. +2. Remove design-rationale sections such as `Why a script, not a raw ...` from `SKILL.md` files. +3. Move the rationale into the sibling `README.md`, including why the wrapper exists, host-detection philosophy, branch/default-branch guards, provider differences, and why workflow-facing files should not call provider CLIs directly. +4. Do not expose provider-specific skill names or deleted provider folders in the downstream `SKILL.md` files. Provider CLI details may appear in these boundary-skill `README.md` files because they document the implementation rationale. +5. Keep script paths out of generic workflow docs. In boundary-skill docs, script paths may appear only where they describe the skill's own implementation or operator/debugging interface. + +**Verification** + +```bash +test -f core/agents/skills/git-publish/README.md +test -f core/agents/skills/change-request-publish/README.md +test -f core/agents/skills/change-request-comments/README.md +test -f core/agents/skills/issue-tracker/README.md +! rg -n '^## Why|Why a script|raw `git push`|raw `gh pr create`|raw `glab mr create`' \ + core/agents/skills/git-publish/SKILL.md \ + core/agents/skills/change-request-publish/SKILL.md \ + core/agents/skills/change-request-comments/SKILL.md \ + core/agents/skills/issue-tracker/SKILL.md +codespell \ + core/agents/skills/git-publish \ + core/agents/skills/change-request-publish \ + core/agents/skills/change-request-comments \ + core/agents/skills/issue-tracker +``` + +Expected result: the `rg` command finds no rationale text in the listed `SKILL.md` files; rationale lives in the new sibling `README.md` files. + +**Commit boundary** + +```text +Move neutral skill rationale into READMEs +``` + +### Task 3 — Remove skill-internal script instructions from workflow-facing files + +**Files** + +- `core/opencode/agents/brainstorm.md` +- `core/opencode/agents/bugfix.md` +- `core/opencode/agents/planner.md` +- `core/opencode/agents/finish.md` +- `core/opencode/agents/implement.md` +- `core/opencode/agents/review-plan.md` +- `core/opencode/agents/review-code.md` +- `core/claude/skills/brainstorm/SKILL.md` +- `core/claude/skills/bugfix/SKILL.md` +- `core/claude/skills/finish/SKILL.md` +- `core/claude/skills/implement/SKILL.md` +- `core/claude/skills/planner/SKILL.md` +- `core/claude/skills/review-plan/SKILL.md` +- `core/claude/skills/review-code/SKILL.md` +- `core/agents/skills/workflow-bug-analysis/SKILL.md` +- `core/agents/skills/workflow-implementation/SKILL.md` + +**Changes** + +1. Replace body text such as ``bash .agents/skills/git-publish/scripts/push-branch.sh`` with skill-level language such as `use git-publish`. +2. Replace body text such as ``bash .claude/skills/change-request-publish/scripts/open-change-request.sh`` with `use change-request-publish`. +3. Replace `issue-tracker/scripts/create-issue.sh`, `update-issue.sh`, and `find-duplicate-issues.sh` instructions with `use issue-tracker to check duplicates, create tracker issues, and update tracker issues`. +4. In `workflow-implementation/SKILL.md`, keep the boundary concise: after verification, commit, then use `git-publish` and `change-request-publish`; do not explain script behavior. +5. Keep script paths only in permission blocks/frontmatter where the harness needs executable allow/ask patterns. + +**Verification** + +```bash +rg -n "\\.agents/skills/.*/scripts|\\.claude/skills/.*/scripts|issue-tracker/scripts" \ + core/opencode/agents \ + core/claude/skills \ + core/agents/skills/workflow-* + +codespell \ + core/opencode/agents \ + core/claude/skills \ + core/agents/skills/workflow-* +``` + +Expected result: remaining script-path hits are only in permission/frontmatter blocks or in boundary skill docs, not workflow body instructions. + +**Commit boundary** + +```text +Use skill-level instructions in workflow files +``` + +### Task 4 — Align OpenCode agent permissions and skill allowlists + +**Files** + +- `core/opencode/agents/brainstorm.md` +- `core/opencode/agents/bugfix.md` +- `core/opencode/agents/planner.md` +- `core/opencode/agents/finish.md` +- `core/opencode/agents/implement.md` +- `core/opencode/agents/review-plan.md` +- `core/opencode/agents/review-code.md` + +**Changes** + +1. For each agent with `git-publish` script permission, add a corresponding `git-publish` skill allow entry. +2. For each agent that may open a change request, add both the `change-request-publish` skill allow entry and the script permission at the desired tier. +3. Apply PR feedback that `brainstorm` should allow opening a change request when publishing a brainstorming result. +4. Apply PR feedback that `review-plan` requires `git-publish`. +5. Apply PR feedback that `review-code` should be allowed to publish review-fix implementation plans; add `git-publish` permission and skill if this agent is expected to commit/publish its review-finding plan. +6. Keep direct script paths only in permission maps, not body instructions. + +**Verification** + +```bash +rg -n "git-publish|change-request-publish|change-request-comments|issue-tracker" core/opencode/agents +rg -n "\\.agents/skills/.*/scripts" core/opencode/agents +``` + +Expected result: permissions and skill allowlists line up, with script paths confined to permission maps. + +**Commit boundary** + +```text +Align OpenCode permissions for neutral repository skills +``` + +### Task 5 — Update installer skill inventories and README sync + +**Files** + +- `scripts/init.sh` +- `scripts/copy.sh` +- `README.md` +- `core/claude/README.md` +- `core/docs/agents/review-plan.md` +- `core/docs/agents/review-code.md` +- `core/docs/agents/agent-workflow-extension.md` + +**Changes** + +1. Remove `github-pr-comments` and `gitlab-mr-comments` from the `AUTHORED_SKILLS` arrays if Task 2 moved their implementation into `change-request-comments`. +2. Keep the neutral authored skills in both installer arrays: `git-publish`, `change-request-publish`, `change-request-comments`, and `issue-tracker`. +3. Update `README.md` authored skill list and symlink table to match the actual installed authored skills. +4. Update `core/claude/README.md` so it lists the neutral authored skills and removes stale `github-pr-comments`, `gitlab-mr-comments`, `github-publish`, and `gitlab-publish` references from downstream Claude documentation. +5. Remove README invocation guidance that points users at `/github-pr-comments`; use `/change-request-comments` or workflow-neutral language instead. +6. Update role docs and the workflow extension guide to name `change-request-comments` as the comment boundary. +7. Re-check `stacks/pnpm` and `stacks/maven` for overlays that need equivalent wording changes. Current expected result: no `agents/skills/` overlays exist in either stack; only stack docs/config overlays may need wording changes. +8. Lockfile check: confirm no lockfile edit is needed because `github-pr-comments` and `gitlab-mr-comments` are authored skills, not remote skills tracked by `core/skills-lock.json`. If a remote-skill entry is discovered, update only the affected lockfile scope. + +**Verification** + +```bash +bash -n scripts/init.sh scripts/copy.sh +shellcheck scripts/init.sh scripts/copy.sh +codespell README.md core/claude/README.md core/docs/agents scripts/init.sh scripts/copy.sh +! rg -n "github-pr-comments|gitlab-mr-comments|github-publish|gitlab-publish" \ + README.md core/claude/README.md core/docs/agents scripts/init.sh scripts/copy.sh +! rg -n "github-pr-comments|gitlab-mr-comments|github|gitlab|PR|MR|gh |glab" stacks/pnpm stacks/maven +``` + +Expected result: provider comment skill names do not appear in downstream installer or generic docs except in explicitly justified boundary documentation. + +**Commit boundary** + +```text +Sync installers and docs with neutral comment skill +``` + +### Task 6 — Clean up Claude settings and OpenCode base config + +**Files** + +- `core/claude/settings.json` +- `core/opencode.json` + +**Changes** + +1. Group Claude permissions by neutral wrapper scripts, GitHub read-only support, and GitLab read-only support. +2. Put mutating neutral wrapper commands in `ask` unless the workflow intentionally allows them. +3. Ensure `issue-tracker/scripts/update-issue.sh` appears in only one permission tier. +4. Remove direct provider mutation permissions that are now handled through neutral wrapper scripts. +5. Keep `core/opencode.json` as a small permissive base config and avoid reintroducing `glab` base permissions. + +**Verification** + +```bash +jq . core/claude/settings.json > /dev/null +jq . core/opencode.json > /dev/null +! rg -n "github-publish|gitlab-publish|github-pr-comments|gitlab-mr-comments|create-bug-issue|update-bug-issue" \ + core/claude/settings.json core/opencode.json +jq '.permission.bash | keys' core/opencode.json +``` + +**Commit boundary** + +```text +Normalize permissions for neutral repository workflows +``` + +### Task 7 — Replace the repo-local publish wrapper with self-maintenance skill symlinks + +**Files** + +- `AGENTS.md` +- `scripts/publish-branch.sh` +- `.agents/skills/git-publish` +- `.agents/skills/change-request-publish` + +**Changes** + +1. Delete `scripts/publish-branch.sh` with `git rm`. +2. Add self-maintenance symlinks so this repo can load the same neutral skills it installs downstream: + - `.agents/skills/git-publish` → `../../core/agents/skills/git-publish` + - `.agents/skills/change-request-publish` → `../../core/agents/skills/change-request-publish` +3. Update `AGENTS.md` Git Conventions to replace `Use scripts/publish-branch.sh` with `Use git-publish for branch pushes and change-request-publish for PR/MR creation`. +4. Confirm the symlink model remains clear: `.agents/skills/*` may contain repo-self-maintenance skills and symlinks to authored template skills; `core/agents/skills/*` remains the downstream template source. + +**Verification** + +```bash +test ! -e scripts/publish-branch.sh +ls -la .agents/skills/git-publish .agents/skills/change-request-publish +test "$(readlink .agents/skills/git-publish)" = "../../core/agents/skills/git-publish" +test "$(readlink .agents/skills/change-request-publish)" = "../../core/agents/skills/change-request-publish" +test "$(readlink .claude/skills/git-publish)" = "../../.agents/skills/git-publish" +test "$(readlink .claude/skills/change-request-publish)" = "../../.agents/skills/change-request-publish" +codespell AGENTS.md +``` + +**Commit boundary** + +```text +Use neutral publish skills for repo maintenance +``` + +### Task 8 — Strengthen workflow boundary regression checks + +**Files** + +- `scripts/check-workflow-boundaries.sh` + +**Changes** + +1. Keep the existing provider-specific pattern checks. +2. Add checks for direct internal script references in workflow-facing body text, including `.agents/skills/.*/scripts/`, `.claude/skills/.*/scripts/`, and `/scripts/*.sh` forms. +3. Exclude boundary skill directories where script usage is the documented interface for that skill itself: `git-publish`, `change-request-publish`, `change-request-comments`, and `issue-tracker`. +4. Filter frontmatter permission blocks so valid OpenCode permission entries do not fail the body-instruction leakage check. Implement this explicitly by scanning only the markdown body after the opening YAML frontmatter (`--- ... ---`) for agent files; do not rely on a plain `rg` that cannot distinguish permissions from prose. +5. Keep permission-map script paths legal, but fail on the same paths in body prose, bullets, or examples in generic workflow files. +6. Run the check after Tasks 2–6 so the expected result is a clean pass. During earlier tasks, use the task-local `rg` commands because this regression check is not authoritative until this task updates it. + +**Verification** + +```bash +bash -n scripts/check-workflow-boundaries.sh +shellcheck scripts/check-workflow-boundaries.sh +bash scripts/check-workflow-boundaries.sh +``` + +**Commit boundary** + +```text +Check workflow files for skill-internal leakage +``` + +## Final verification + +Run from the repository root after all tasks: + +```bash +bash -n scripts/init.sh scripts/copy.sh scripts/check-workflow-boundaries.sh +shellcheck scripts/init.sh scripts/copy.sh scripts/check-workflow-boundaries.sh +jq . core/claude/settings.json > /dev/null +jq . core/opencode.json > /dev/null +codespell AGENTS.md README.md core/claude/README.md core/agents/skills core/claude/skills core/opencode/agents core/docs/agents scripts +bash scripts/check-workflow-boundaries.sh +``` + +Smoke install under `.temp/`: + +```bash +mkdir -p .temp/smoke-init +printf "1\n1\n.temp/smoke-init/target\n" | bash scripts/init.sh +ls -la .temp/smoke-init/target/.claude/skills +test ! -e .temp/smoke-init/target/.agents/skills/github-pr-comments +test ! -e .temp/smoke-init/target/.agents/skills/gitlab-mr-comments + +mkdir -p .temp/smoke-copy/target +git -C .temp/smoke-copy/target init +git -C .temp/smoke-copy/target config user.email "test@example.com" +git -C .temp/smoke-copy/target config user.name "Test" +touch .temp/smoke-copy/target/README.md +git -C .temp/smoke-copy/target add README.md +git -C .temp/smoke-copy/target commit -m "init" +printf "1\n1\n" | bash scripts/copy.sh .temp/smoke-copy/target +ls -la .temp/smoke-copy/target/.claude/skills +test ! -e .temp/smoke-copy/target/.agents/skills/github-pr-comments +test ! -e .temp/smoke-copy/target/.agents/skills/gitlab-mr-comments + +rm -rf .temp/smoke-init .temp/smoke-copy +``` + +## Acceptance criteria + +- `AGENTS.md` explicitly prevents workflow-facing instructions from exposing skill internals. +- Generic workflow files name skills, not internal script paths. +- `change-request-comments` is the only downstream comment skill surface. +- Neutral skill `SKILL.md` files focus on invocation contracts; rationale lives in sibling `README.md` files. +- Installer arrays, README inventories, and symlink table match actual installed authored skills. +- OpenCode and Claude permissions allow neutral wrapper usage without documenting internal scripts in workflow bodies. +- Provider-specific CLI details live only inside boundary skills or provider-specific implementation code. +- Boundary checks prevent future provider-skill and internal-script leakage. +- Bash and smoke verification pass under `.temp/`. diff --git a/plans/2026-06-27-provider-boundaries/plan.md b/plans/2026-06-27-provider-boundaries/plan.md new file mode 100644 index 0000000..1219730 --- /dev/null +++ b/plans/2026-06-27-provider-boundaries/plan.md @@ -0,0 +1,1244 @@ +# Plan: Fix provider-internal leakage from workflow skills + +## Objective + +Generic workflow skills and agents must stop exposing GitHub/GitLab implementation details. Workflow-facing files should describe repository operations in host-neutral terms and call stable neutral skills. Provider-specific mechanics should remain isolated behind integration/provider skills. + +## Recurring leakage to remove + +- `gh` / `glab` +- `github-publish` / `gitlab-publish` +- `github-pr-comments` / `gitlab-mr-comments` +- `open-pr.sh` / `open-mr.sh` +- `fetch-pr-comments.sh` / `fetch-mr-comments.sh` +- repeated `git remote get-url origin` host-detection snippets +- GitHub-only “PR comments” wording in generic workflows +- Claude workflow skills naming OpenCode internals such as `@implement`, `@implement-task`, or “OpenCode handoff” +- Base `core/opencode.json` permission entries for `glab` (provider-specific CLI should not appear in generic config) + +## Target architecture + +Workflow-facing files should use these neutral authored skills: + +- `git-publish` — safe branch push for any git remote. +- `change-request-publish` — open a change request after detecting the host. +- `change-request-comments` — fetch and reply to change-request comments. +- `issue-tracker` — create/update tracker issues and perform duplicate checks. + +Provider-specific CLI commands remain implementation details inside the neutral integration skills: + +- `change-request-publish` directly runs `gh pr create` or `glab mr create` after host detection. +- `change-request-comments` delegates to the provider-specific `github-pr-comments` and `gitlab-mr-comments` skills. +- `issue-tracker` directly runs `gh issue ...` or `glab issue ...` after host detection. + +The old `github-publish` and `gitlab-publish` skills are removed entirely; their only remaining behavior (opening PRs/MRs) is absorbed into `change-request-publish`. + +## Scope and file mapping + +This change is in `core/`, so verify whether stack overlays need matching changes. Initial investigation found no relevant workflow-provider leakage under `stacks/pnpm` or `stacks/maven`, but each task that changes wording must re-check stack ripple. + +Template mapping to keep in mind: + +| Source | Installed target | +|---|---| +| `core/agents/skills/*` | `.agents/skills/*` plus symlinked `.claude/skills/*` authored skills | +| `core/claude/skills/*` | `.claude/skills/*` workflow entry skills | +| `core/opencode/agents/*` | `.opencode/agents/*` | +| `core/docs/*` | `docs/*` | + +Lockfile expectation: no lockfile changes are expected because the new skills are authored local skills, not remote skills. `core/skills-lock.json` tracks remote skills installed into target repos; `skills-lock.json` tracks self-maintenance remote skills for this repo. Neither lists authored skills. + +Files added or changed by this plan: + +```text +# New neutral integration skills + core/agents/skills/git-publish/SKILL.md + core/agents/skills/git-publish/scripts/push-branch.sh + core/agents/skills/change-request-publish/SKILL.md + core/agents/skills/change-request-publish/scripts/open-change-request.sh + core/agents/skills/change-request-comments/SKILL.md + core/agents/skills/change-request-comments/scripts/fetch-comments.sh + core/agents/skills/change-request-comments/scripts/reply-to-comment.sh + core/agents/skills/issue-tracker/SKILL.md + core/agents/skills/issue-tracker/scripts/create-issue.sh + core/agents/skills/issue-tracker/scripts/update-issue.sh + core/agents/skills/issue-tracker/scripts/find-duplicate-issues.sh + +# Bug workflow moved behind issue-tracker + core/agents/skills/workflow-bug-analysis/SKILL.md + core/agents/skills/workflow-bug-analysis/scripts/ (delete entire directory) + core/opencode/agents/bugfix.md + core/claude/skills/bugfix/SKILL.md + +# Provider publish skills replaced by change-request-publish + core/agents/skills/github-publish/ (delete entire directory) + core/agents/skills/gitlab-publish/ (delete entire directory) + core/agents/skills/change-request-publish/scripts/open-change-request.sh (absorbs open-pr.sh / open-mr.sh logic) + +# Shared workflow skills and prompts + core/agents/skills/workflow-implementation/SKILL.md + core/agents/skills/workflow-implementation/implementer-prompt.md + core/agents/skills/workflow-implementation/spec-reviewer-prompt.md + core/agents/skills/workflow-implementation/code-quality-reviewer-prompt.md + core/agents/skills/workflow-planning/plan-document-reviewer-prompt.md + core/agents/skills/workflow-verification/SKILL.md + +# OpenCode agents + core/opencode/agents/brainstorm.md + core/opencode/agents/planner.md + core/opencode/agents/implement.md + core/opencode/agents/implement-task.md (verify-only; currently clean) + core/opencode/agents/finish.md + core/opencode/agents/review-plan.md + core/opencode/agents/review-code.md + core/opencode.json + +# Claude workflow skills + core/claude/skills/brainstorm/SKILL.md + core/claude/skills/planner/SKILL.md + core/claude/skills/implement/SKILL.md + core/claude/skills/finish/SKILL.md + core/claude/skills/review-plan/SKILL.md + core/claude/skills/review-code/SKILL.md + core/claude/skills/bugfix/SKILL.md + +# Installers, docs, settings + scripts/init.sh + scripts/copy.sh + README.md + core/docs/agents/agent-workflow-extension.md + core/docs/agents/review-plan.md + core/docs/agents/review-code.md + core/claude/settings.json + +# Regression check + scripts/check-workflow-boundaries.sh +``` + +## Task dependencies + +```text +Task 1 ─┬─> Task 2 (issue-tracker must exist before bug workflow can call it) + ├─> Task 3a (shared workflow prompts reference neutral skills) + ├─> Task 3b (OpenCode agents reference neutral skills) + ├─> Task 3c (Claude skills reference neutral skills) + └─> Task 4a (installers must know about new skills) + +Task 3a/b/c ──> Task 5 (boundary check validates the updated workflow files) + +All tasks ──> Task 6 (final verification) +``` + +--- + +## Task 1 — Add neutral repository integration skills + +### Files to add + +```text +core/agents/skills/git-publish/SKILL.md +core/agents/skills/git-publish/scripts/push-branch.sh + +core/agents/skills/change-request-publish/SKILL.md +core/agents/skills/change-request-publish/scripts/open-change-request.sh + +core/agents/skills/change-request-comments/SKILL.md +core/agents/skills/change-request-comments/scripts/fetch-comments.sh +core/agents/skills/change-request-comments/scripts/reply-to-comment.sh + +core/agents/skills/issue-tracker/SKILL.md +core/agents/skills/issue-tracker/scripts/create-issue.sh +core/agents/skills/issue-tracker/scripts/update-issue.sh +core/agents/skills/issue-tracker/scripts/find-duplicate-issues.sh +``` + +### Requirements + +- Each `SKILL.md` must have frontmatter with: + - `name` + - `description` + - `user-invocable: false` +- `git-publish` scripts are provider-agnostic and use only `git`. +- `change-request-publish`, `change-request-comments`, and `issue-tracker` scripts own host detection. +- Neutral integration scripts delegate to existing provider-specific skills/scripts where possible. +- Host-specific details may appear inside these neutral integration skills because they are the boundary layer. +- Workflow-facing files must call only these neutral skills/scripts. + +### Implementation notes + +#### `git-publish/scripts/push-branch.sh` + +- Push the current branch to `origin`. +- Refuse if the current branch is `main`, `master`, or a detached `HEAD`. +- Refuse any `--force` / `-f` / `--force-with-lease` argument. +- Pass through other extra arguments (e.g. `--set-upstream`). +- Do not inspect the remote host. + +For the initial implementation, protect `main|master|HEAD`. A future enhancement could query the remote default branch, but that requires network and is not needed to fix the leakage. + +#### `change-request-publish/scripts/open-change-request.sh` + +- Detect the host from `git remote get-url origin`: + - GitHub (contains `github.com` or starts with `git@github.com:`) → open a pull request with `gh pr create` + - Otherwise → open a merge request with `glab mr create` (GitLab is commonly self-hosted) +- Refuse if the current branch is `main`, `master`, or a detached `HEAD`. +- Skip creation when a change request already exists for the current branch, printing the existing one and exiting 0. +- Pass through all arguments (`"$@"`) to the provider CLI. +- Print the created change-request URL. + +Workflow instructions (Claude `.claude/skills/...`, OpenCode `.agents/skills/...`) must call only the neutral script. Provider CLI names (`gh pr create`, `glab mr create`) are allowed inside this script because it is the boundary layer. + +#### `change-request-comments/scripts/fetch-comments.sh` + +- Detect host from `git remote get-url origin`. +- GitHub → delegate to `.agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh` +- GitLab → delegate to `.agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh` +- Pass through arguments and preserve exit code. + +#### `change-request-comments/scripts/reply-to-comment.sh` + +- Detect host from `git remote get-url origin`. +- GitHub → delegate to `.agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh` +- GitLab → delegate to `.agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh` +- Pass through arguments and preserve exit code. + +#### `issue-tracker/scripts/create-issue.sh` + +- Accept `--title TITLE`, `--body-file PATH`, and optional `--labels LABELS`. +- Detect host from `git remote get-url origin`. +- GitHub → `gh issue create --title ... --body-file ... --label ...` +- GitLab → `glab issue create --title ... --description ... --label ...` +- Print the created issue URL. + +#### `issue-tracker/scripts/update-issue.sh` + +- Accept `--issue NUMBER` and `--body-file PATH`. +- Detect host from `git remote get-url origin`. +- GitHub → `gh issue edit --body-file ...` +- GitLab → `glab issue update --description ...` +- Print the issue URL. + +#### `issue-tracker/scripts/find-duplicate-issues.sh` + +- Accept `--title TITLE` and optional `--labels LABELS`. +- Detect host from `git remote get-url origin`. +- GitHub → `gh issue list --search ... --label ... --json number,title,url` +- GitLab → `glab issue list --search ... --label ... --output json` (or equivalent) +- Print a compact list of candidate duplicate issues, or nothing if none found. + +### Provider publish skill removal + +Because `change-request-publish` now owns opening PRs/MRs and `git-publish` owns pushing, the old `github-publish` and `gitlab-publish` skills are redundant. + +#### Files to delete + +```text +core/agents/skills/github-publish/ +core/agents/skills/gitlab-publish/ +``` + +Use `git rm -r` for these tracked directories. + +#### Files to update + +```text +core/agents/skills/change-request-publish/SKILL.md +``` + +Rewrite `change-request-publish/SKILL.md` to describe the neutral interface: + +```markdown +# Change Request Publish + +Open a change request (pull request or merge request) for the current branch after detecting the host. + +## Detect the host + +Inspect `git remote get-url origin`: + +- GitHub (contains `github.com` or starts with `git@github.com:`) → open a pull request with `gh pr create`. +- Otherwise → open a merge request with `glab mr create` (commonly self-hosted GitLab). + +## Open a change request + +```bash +bash .agents/skills/change-request-publish/scripts/open-change-request.sh [provider flags] +``` + +The script refuses on `main`/`master`/detached `HEAD` and skips creation when a change request already exists for the branch. Pass `--fill`, `--title`/`--description`, or other flags supported by the underlying CLI. + +## Rules + +- Do not call `gh pr create` or `glab mr create` directly from workflow agents — use this skill's script. +- Do not create change requests from the default branch. +``` + +### Verification + +```bash +bash -n core/agents/skills/git-publish/scripts/*.sh +bash -n core/agents/skills/change-request-publish/scripts/*.sh +bash -n core/agents/skills/change-request-comments/scripts/*.sh +bash -n core/agents/skills/issue-tracker/scripts/*.sh +shellcheck core/agents/skills/git-publish/scripts/*.sh +shellcheck core/agents/skills/change-request-publish/scripts/*.sh +shellcheck core/agents/skills/change-request-comments/scripts/*.sh +shellcheck core/agents/skills/issue-tracker/scripts/*.sh + +# Confirm old provider publish skills are gone +if [[ -d core/agents/skills/github-publish || -d core/agents/skills/gitlab-publish ]]; then + echo "ERROR: github-publish and gitlab-publish skill directories must be deleted" >&2 + exit 1 +fi +``` + +### Smoke `git-publish` against a local bare remote + +```bash +mkdir -p .temp/git-publish-smoke +rm -rf .temp/git-publish-smoke/* +git init --bare .temp/git-publish-smoke/origin.git +git init .temp/git-publish-smoke/work +git -C .temp/git-publish-smoke/work remote add origin ../origin.git +git -C .temp/git-publish-smoke/work config user.email "test@example.com" +git -C .temp/git-publish-smoke/work config user.name "Test" +echo "init" > .temp/git-publish-smoke/work/README.md +git -C .temp/git-publish-smoke/work add README.md +git -C .temp/git-publish-smoke/work commit -m "init" +git -C .temp/git-publish-smoke/work checkout -b feature/test +( cd .temp/git-publish-smoke/work && bash .agents/skills/git-publish/scripts/push-branch.sh ) + +# Negative case: pushing main must be refused +git -C .temp/git-publish-smoke/work checkout main +if ( cd .temp/git-publish-smoke/work && bash .agents/skills/git-publish/scripts/push-branch.sh ); then + echo "ERROR: push-branch.sh should have refused main" >&2 + exit 1 +fi + +rm -rf .temp/git-publish-smoke +``` + +### Commit boundary + +One commit: + +```text +Add neutral repository skills and remove redundant provider push scripts +``` + +--- + +## Task 2 — Move bug issue mechanics behind `issue-tracker` + +### Decision + +- The `issue-tracker` scripts added in Task 1 supersede the bug-issue scripts. +- Delete the old `workflow-bug-analysis/scripts/` directory entirely after updating all references. Use `git rm -r` for the tracked directory. +- Update `workflow-bug-analysis/SKILL.md` to call `issue-tracker` scripts and use “tracker issue” language. +- Update OpenCode and Claude bugfix entry points to reference `issue-tracker`, not provider CLIs or provider-specific scripts. + +### Files to add/move/remove + +```text +# Superseded by issue-tracker scripts from Task 1; delete: +core/agents/skills/workflow-bug-analysis/scripts/ +``` + +### Files to update + +```text +core/agents/skills/workflow-bug-analysis/SKILL.md +core/opencode/agents/bugfix.md +core/claude/skills/bugfix/SKILL.md +core/claude/settings.json +core/docs/agents/bugfix.md (verify-only; should already be neutral) +``` + +### Requirements + +- Replace “structured GitHub issue” with “structured tracker issue” everywhere in `workflow-bug-analysis/SKILL.md` and `bugfix/SKILL.md`. +- Remove direct `gh` / `glab` instructions from workflow-facing bug files. +- `workflow-bug-analysis` owns investigation and structured issue content. +- `issue-tracker` owns issue creation/update/duplicate-check mechanics. +- OpenCode and Claude bugfix entry points should call `workflow-bug-analysis` plus `issue-tracker`; they should not name provider CLIs or provider-specific scripts. + +### Concrete wording changes + +In `core/agents/skills/workflow-bug-analysis/SKILL.md`: + +- Line 3 description: change `produces a structured GitHub issue` → `produces a structured tracker issue`. +- Line 9: change `produce a structured GitHub issue` → `produce a structured tracker issue`. +- Section 7: replace the `create-bug-issue.sh` invocation with: + + ```bash + bash .agents/skills/issue-tracker/scripts/create-issue.sh \ + --title "Concise bug summary" \ + --body-file .temp/-issue-body.md \ + --labels "bug" + ``` + +- Follow-up evidence: replace `update-bug-issue.sh` with `issue-tracker/scripts/update-issue.sh`. +- Add a step after hypothesis: check for duplicates with `issue-tracker/scripts/find-duplicate-issues.sh` before creating the issue. + +In `core/claude/skills/bugfix/SKILL.md`: + +- Line 3 description: `produces a structured GitHub issue` → `produces a structured tracker issue`. +- Line 9: `produce a well-structured GitHub issue` → `produce a well-structured tracker issue`. +- Remove the `gh` rules in the Rules section. Replace with: + + ```text + - Never call `gh` or `glab` directly — use the `issue-tracker` scripts for issue mutations. + - Use `issue-tracker/scripts/find-duplicate-issues.sh` for duplicate checking. + ``` + +In `core/opencode/agents/bugfix.md`: + +- Line 2 description: change `structured GitHub issue` → `structured tracker issue`. +- Replace the comment block that says `# GitHub — agent uses wrapper scripts for mutations; direct gh is denied` with a neutral note: `# Tracker issues — use issue-tracker scripts for mutations; direct gh/glab mutations are denied`. +- Keep read-only provider CLI entries as implementation support for the `issue-tracker` scripts (OpenCode checks the top-level Bash command, but keeping these allows also protects against accidental direct use): + - `gh issue list *`: allow + - `gh issue view *`: allow + - `gh search issues *`: allow + - `glab issue list *`: allow + - `glab issue view *`: allow +- Keep the blanket deny for mutations: + - `gh *`: deny + - `glab *`: deny +- Add allow entries for: + - `bash .agents/skills/issue-tracker/scripts/*.sh` +- Update instructions to say: “Use `issue-tracker/scripts/find-duplicate-issues.sh` for duplicate checks and `issue-tracker/scripts/create-issue.sh` to file the tracker issue.” + +### Verification + +```bash +rg -n "GitHub issue|GitLab issue|gh |glab|create-bug-issue|update-bug-issue" \ + core/agents/skills/workflow-bug-analysis \ + core/opencode/agents/bugfix.md \ + core/claude/skills/bugfix/SKILL.md + +bash -n core/agents/skills/issue-tracker/scripts/*.sh +shellcheck core/agents/skills/issue-tracker/scripts/*.sh +``` + +Expected result: no provider-specific leakage remains in generic bug workflow files. Any remaining hit must be inside `issue-tracker` scripts (the boundary layer) or in a compatibility wrapper with an explicit migration reason. + +### Commit boundary + +One commit: + +```text +Move bug issue publishing behind issue-tracker skill +``` + +--- + +## Task 3a — Neutralize shared workflow skills and prompts + +### Files to update + +```text +core/agents/skills/workflow-implementation/SKILL.md +core/agents/skills/workflow-implementation/implementer-prompt.md +core/agents/skills/workflow-implementation/spec-reviewer-prompt.md +core/agents/skills/workflow-implementation/code-quality-reviewer-prompt.md +core/agents/skills/workflow-planning/plan-document-reviewer-prompt.md +core/agents/skills/workflow-verification/SKILL.md +``` + +### Replacement language + +Use host-neutral language: + +```text +Push branches through git-publish. +Open a change request through change-request-publish. +Read change-request comments through change-request-comments. +Create or update tracker issues through issue-tracker. +``` + +Avoid in generic workflow files: + +```text +github-publish +gitlab-publish +github-pr-comments +gitlab-mr-comments +gh +glab +open-pr.sh +open-mr.sh +fetch-pr-comments.sh +fetch-mr-comments.sh +``` + +### Concrete change in `workflow-implementation/SKILL.md` + +Replace the Boundaries block (currently lines 99–103): + +```markdown +- After final verification, the controller commits all changes, then pushes the branch with `git-publish` and opens a change request with `change-request-publish`. Never push to the default branch. +``` + +### Concrete change in `workflow-verification/SKILL.md` + +If it contains any host-detection or provider-specific publishing instructions, replace them with references to `git-publish` / `change-request-publish`. Verify first; this file is currently expected to be clean. + +### Verification + +```bash +rg -n "github-publish|gitlab-publish|github-pr-comments|gitlab-mr-comments|gh |glab|open-pr\.sh|open-mr\.sh|fetch-pr-comments|fetch-mr-comments" \ + core/agents/skills/workflow-* +``` + +Expected result: no hits in generic workflow skill files. + +### Commit boundary + +One commit: + +```text +Use neutral repository interfaces in shared workflow skills +``` + +--- + +## Task 3b — Neutralize OpenCode agents + +### Files to update + +```text +core/opencode/agents/brainstorm.md +core/opencode/agents/planner.md +core/opencode/agents/implement.md +core/opencode/agents/implement-task.md (verify-only) +core/opencode/agents/finish.md +core/opencode/agents/review-plan.md +core/opencode/agents/review-code.md +core/opencode.json +``` + +### Permission changes + +In each agent `.md` frontmatter, replace provider-specific Bash allow patterns with neutral ones. + +#### `implement.md` + +Remove: + +```yaml + "bash .agents/skills/github-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/github-publish/scripts/open-pr.sh*": ask + "bash .agents/skills/gitlab-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/gitlab-publish/scripts/open-mr.sh*": ask +``` + +Add: + +```yaml + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/change-request-publish/scripts/open-change-request.sh*": ask +``` + +Remove skill allows: + +```yaml + "github-publish": allow + "gitlab-publish": allow +``` + +Add: + +```yaml + "git-publish": allow + "change-request-publish": allow +``` + +#### `finish.md` + +Same pattern as `implement.md` for publishing permissions. `finish.md` does not open change requests by default, but keep `change-request-publish` in `ask` if the agent is ever extended to do so. + +#### `brainstorm.md` and `planner.md` + +Remove: + +```yaml + "bash .agents/skills/github-publish/scripts/push-branch.sh*": allow + "bash .agents/skills/gitlab-publish/scripts/push-branch.sh*": allow +``` + +Add: + +```yaml + "bash .agents/skills/git-publish/scripts/push-branch.sh*": allow +``` + +#### `review-plan.md` and `review-code.md` + +Remove: + +```yaml + "bash .agents/skills/github-pr-comments/scripts/fetch-pr-comments.sh *": allow + "bash .agents/skills/github-pr-comments/scripts/reply-to-pr-comment.sh *": allow + "bash .agents/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh*": allow + "bash .agents/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh*": ask +``` + +Add: + +```yaml + "bash .agents/skills/change-request-comments/scripts/fetch-comments.sh *": allow + "bash .agents/skills/change-request-comments/scripts/reply-to-comment.sh *": ask +``` + +Remove skill allows: + +```yaml + "github-pr-comments": allow + "gitlab-mr-comments": allow +``` + +Add: + +```yaml + "change-request-comments": allow +``` + +#### `bugfix.md` + +See Task 2. + +#### `core/opencode.json` + +Remove all base `glab` permission entries from `permission.bash`: + +```json + "glab mr create *": "ask", + "glab mr list *": "allow", + "glab mr view *": "allow", + "glab mr diff *": "allow", + "glab issue create *": "ask", + "glab issue view *": "allow", + "glab issue list *": "allow", + "glab issue update *": "ask" +``` + +Generic agents should not need provider CLI permissions in the base config. Any provider CLI access needed by `issue-tracker` scripts is covered by the agent frontmatter that invokes those scripts (OpenCode checks the immediate Bash command; the script's internal CLI calls are not surfaced to the permission layer). + +### Instruction changes + +Replace every host-detection block of the form: + +```markdown +Detect host from `git remote get-url origin`: +- GitHub (contains `github.com` or starts with `git@github.com:`) → use `github-publish` +- Otherwise → use `gitlab-publish` +``` + +with: + +```markdown +Push the branch with `bash .agents/skills/git-publish/scripts/push-branch.sh`. To open a change request, use `bash .agents/skills/change-request-publish/scripts/open-change-request.sh`. +``` + +In `review-plan.md` and `review-code.md`, replace references to `github-pr-comments` / `gitlab-mr-comments` skills with `change-request-comments`. + +### Verification + +```bash +rg -n "github-publish|gitlab-publish|github-pr-comments|gitlab-mr-comments|gh |glab|open-pr\.sh|open-mr\.sh|fetch-pr-comments|fetch-mr-comments" \ + core/opencode/agents + +jq '.permission.bash | keys' core/opencode.json +``` + +Expected result: no provider-specific leakage in generic agent files. `core/opencode.json` should no longer contain `glab` keys. + +### Commit boundary + +One commit: + +```text +Use neutral repository interfaces in OpenCode agents +``` + +--- + +## Task 3c — Neutralize Claude workflow skills + +### Files to update + +```text +core/claude/skills/brainstorm/SKILL.md +core/claude/skills/planner/SKILL.md +core/claude/skills/implement/SKILL.md +core/claude/skills/finish/SKILL.md +core/claude/skills/review-plan/SKILL.md +core/claude/skills/review-code/SKILL.md +core/claude/skills/bugfix/SKILL.md +``` + +### Description/frontmatter updates + +- `planner/SKILL.md`: change `ready for OpenCode handoff` → `ready for implementation handoff`. +- `review-plan/SKILL.md`: change `finalizes it into the OpenCode handoff document` → `finalizes it into the implementation handoff document`. +- `bugfix/SKILL.md`: change `produces a structured GitHub issue` → `produces a structured tracker issue` (also in body). + +### Shell guidance updates + +Replace every occurrence of: + +```markdown +Publish through the host-appropriate publish skill. Detect host from `git remote get-url origin`: +- GitHub (contains `github.com` or starts with `git@github.com:`) → `bash .claude/skills/github-publish/scripts/push-branch.sh` +- Otherwise → `bash .claude/skills/gitlab-publish/scripts/push-branch.sh` — GitLab is commonly self-hosted +Never hand-roll `git push`. +``` + +with: + +```markdown +Publish through the neutral git-publish skill: +- Push the current branch with `bash .claude/skills/git-publish/scripts/push-branch.sh`. +- Open a change request with `bash .claude/skills/change-request-publish/scripts/open-change-request.sh` when needed. +Never hand-roll `git push`. +``` + +Apply this in `brainstorm/SKILL.md`, `planner/SKILL.md`, `implement/SKILL.md`, `finish/SKILL.md`, and `review-plan/SKILL.md`. + +### Skill reference updates + +In `review-plan/SKILL.md` and `review-code/SKILL.md`: + +- Replace `Use github-pr-comments for GitHub remotes and gitlab-mr-comments for non-GitHub ...` with `Use change-request-comments for reading and drafting replies to change-request comments.` +- Replace references to `github-pr-comments` / `gitlab-mr-comments` with `change-request-comments`. + +### OpenCode internal reference updates + +Remove in generic workflow body text: + +```text +OpenCode handoff +@implement +@implement-task +``` + +Exceptions allowed only in an explicit cross-harness availability note. Keep such notes minimal and rephrase agent names as neutral harness references where possible: + +- `implement/SKILL.md` may keep a short cross-harness note such as: + ```markdown + This is the Claude Code implementation controller. OpenCode has an equivalent controller agent. + ``` + Do not keep `@implement` in normal workflow instructions. +- `review-code/SKILL.md` currently says: + ```markdown + In Claude Code, prefer writing the fix-plan handoff document and handing it to OpenCode (`@implement` / `@implement-task`) for TDD implementation. + ``` + Replace with: + ```markdown + In Claude Code, prefer writing the fix-plan handoff document and handing it to the OpenCode implement agent for TDD implementation. + ``` +- `planner/SKILL.md` currently says: + ```markdown + - Keep task boundaries small enough that the implementer executes without guessing. + - Implementation can run in either harness: Claude Code (`/implement`) or OpenCode (`@implement`). Pick one per branch. Suggest the user's preferred harness. + ``` + The second bullet is a cross-harness availability note and may stay, but remove `@implement` and replace with `the OpenCode implement agent`. + +### Verification + +```bash +rg -n "github-publish|gitlab-publish|github-pr-comments|gitlab-mr-comments|gh |glab|open-pr\.sh|open-mr\.sh|fetch-pr-comments|fetch-mr-comments" \ + core/claude/skills + +rg -n "OpenCode handoff|@implement|@implement-task" core/claude/skills +``` + +Expected result: no hits in generic workflow files, or each remaining hit is explicitly justified as a cross-harness availability note and not a provider implementation leak. + +### Commit boundary + +One commit: + +```text +Use neutral repository interfaces in Claude workflow skills +``` + +--- + +## Task 4a — Register new authored skills in installers + +### Files to update + +```text +scripts/init.sh +scripts/copy.sh +``` + +### Installer requirements + +Add these authored skills to both installer `AUTHORED_SKILLS` arrays: + +```text +git-publish +change-request-publish +change-request-comments +issue-tracker +``` + +Place them after `feature-documentation` and before `github-pr-comments`: + +```bash +AUTHORED_SKILLS=( + grill-with-docs + workflow-bug-analysis + workflow-brainstorming + workflow-planning + workflow-verification + feature-documentation + git-publish + change-request-publish + change-request-comments + issue-tracker + github-pr-comments + gitlab-mr-comments +) +``` + +Note: `github-publish` and `gitlab-publish` are removed from the list because `change-request-publish` now handles opening PRs/MRs directly. + +The existing frontmatter verification loop in `init.sh` will automatically verify `user-invocable: false` for the new skills. + +### Verification + +```bash +bash -n scripts/init.sh scripts/copy.sh +shellcheck scripts/init.sh scripts/copy.sh + +mkdir -p .temp/smoke-test +printf "1\n1\n.temp/smoke-test/target\n" | bash scripts/init.sh +ls -la .temp/smoke-test/target/.claude/skills | grep -E 'git-publish|change-request-publish|change-request-comments|issue-tracker' +rm -rf .temp/smoke-test +``` + +Expected result: the new symlinks exist in `.claude/skills/`. + +### Commit boundary + +One commit: + +```text +Register neutral repository skills in installers +``` + +--- + +## Task 4b — Update README and role docs + +### Files to update + +```text +README.md +core/docs/agents/review-plan.md +core/docs/agents/review-code.md +core/docs/agents/agent-workflow-extension.md +``` + +### README requirements + +- **Installed authored reusable skill list** (line 104): add the four new skills and remove `github-publish`/`gitlab-publish` because `change-request-publish` replaces them. + + Current: + ```text + `grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, `workflow-implementation`, `workflow-verification`, `feature-documentation`, `github-publish`, `github-pr-comments`, `gitlab-publish`, `gitlab-mr-comments` + ``` + + Updated: + ```text + `grill-with-docs`, `workflow-bug-analysis`, `workflow-brainstorming`, `workflow-planning`, `workflow-implementation`, `workflow-verification`, `feature-documentation`, `git-publish`, `change-request-publish`, `change-request-comments`, `issue-tracker`, `github-pr-comments`, `gitlab-mr-comments` + ``` + +- **Claude symlink table** (lines 131–142): add rows for the four new skills and remove the rows for `github-publish` and `gitlab-publish`. + +- **Recommended workflow wording**: update step 8 caption from `leave inline comments or GitHub review notes` to `leave inline comments or change-request review notes`. + +### Role doc requirements + +In `core/docs/agents/review-plan.md` and `core/docs/agents/review-code.md`, replace: + +```markdown +- `github-pr-comments` skill for reading and drafting replies to GitHub PR feedback +- `gitlab-mr-comments` skill for reading and drafting replies to GitLab MR feedback +``` + +with: + +```markdown +- `change-request-comments` skill for reading and drafting replies to change-request feedback +``` + +In `review-code.md`, the `Git diff or PR diff under review` bullet is generic enough to keep, but optionally rephrase to `Git diff or change-request diff under review` if desired. + +### Extension guide requirements + +Update `core/docs/agents/agent-workflow-extension.md`: + +- Rename `## GitHub PR comment workflow boundary` to `## Change-request comment workflow boundary`. +- Replace the bullet `The github-pr-comments skill owns reading PR comments, classifying comment target types, obtaining exact IDs, and posting approved replies through project scripts.` with: + ```markdown + - The `change-request-comments` skill owns reading change-request comments, classifying comment target types, obtaining exact IDs, and posting approved replies through project scripts. + - Provider-specific mechanics (`github-pr-comments`, `gitlab-mr-comments`, `gh`, `glab`) belong only in provider skills or neutral integration skills, never in generic workflow skills or agent instructions. + ``` +- Keep existing OpenCode/Claude boundary guidance. + +### Stack ripple check + +```bash +rg -n "github|gitlab|PR|MR|gh |glab" stacks/pnpm stacks/maven +``` + +Update stack overlays only if they contain affected workflow wording. The pre-flight check indicates no hits, so no overlay changes are expected. + +### Verification + +```bash +codespell README.md core/docs/agents + +rg -n "github-pr-comments|gitlab-mr-comments" README.md core/docs/agents +rg -n "GitHub PR comment workflow boundary" core/docs/agents +``` + +Expected result: the old skill names no longer appear in generic docs; only `change-request-comments` and neutral wording remain. + +### Commit boundary + +One commit: + +```text +Document neutral repository skills in README and role docs +``` + +--- + +## Task 4c — Update Claude settings and core/opencode.json + +### Files to update + +```text +core/claude/settings.json +core/opencode.json +``` + +### Claude settings requirements + +Replace provider-specific workflow-facing allow entries with neutral wrapper scripts. Keep provider CLI entries only as implementation support for the wrapper layer (read-only issue/comment list commands used by `issue-tracker` and `change-request-comments`). + +In `permissions.allow`, replace: + +```json + "Bash(bash .claude/skills/github-pr-comments/scripts/fetch-pr-comments.sh:*)", + "Bash(bash .claude/skills/workflow-bug-analysis/scripts/create-bug-issue.sh:*)", + "Bash(gh issue list:*)", + "Bash(gh issue view:*)", + "Bash(gh search issues:*)", + "Bash(bash .claude/skills/github-publish/scripts/push-branch.sh:*)", + "Bash(bash .claude/skills/gitlab-publish/scripts/push-branch.sh:*)", + "Bash(bash .claude/skills/gitlab-mr-comments/scripts/fetch-mr-comments.sh:*)", + "Bash(glab mr view:*)", + "Bash(glab mr diff:*)", + "Bash(glab mr list:*)", + "Bash(glab issue view:*)", + "Bash(glab issue list:*)", +``` + +with: + +```json + "Bash(bash .claude/skills/git-publish/scripts/push-branch.sh:*)", + "Bash(bash .claude/skills/change-request-comments/scripts/fetch-comments.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/create-issue.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/update-issue.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/find-duplicate-issues.sh:*)", + "Bash(gh issue list:*)", + "Bash(gh issue view:*)", + "Bash(gh search issues:*)", + "Bash(glab issue list:*)", + "Bash(glab issue view:*)", +``` + +Note: `change-request-publish` opens a PR/MR, which is a mutation. The existing settings kept `open-pr.sh`/`open-mr.sh` in `ask`, so keep `change-request-publish/scripts/open-change-request.sh` in `ask` for parity. + +In `permissions.ask`, replace: + +```json + "Bash(bash .claude/skills/github-pr-comments/scripts/reply-to-pr-comment.sh:*)", + "Bash(bash .claude/skills/workflow-bug-analysis/scripts/update-bug-issue.sh:*)", + "Bash(bash .claude/skills/github-publish/scripts/open-pr.sh:*)", + "Bash(bash .claude/skills/gitlab-publish/scripts/open-mr.sh:*)", + "Bash(bash .claude/skills/gitlab-mr-comments/scripts/reply-to-mr-comment.sh:*)", +``` + +with: + +```json + "Bash(bash .claude/skills/change-request-publish/scripts/open-change-request.sh:*)", + "Bash(bash .claude/skills/change-request-comments/scripts/reply-to-comment.sh:*)", + "Bash(bash .claude/skills/issue-tracker/scripts/update-issue.sh:*)", +``` + +Remove direct `gh`/`glab` mutation entries from `ask` unless the wrapper layer still needs them. The wrapper scripts call `gh pr create` / `glab mr create` internally; Claude Code permissions are checked against the top-level Bash command, not subprocesses, so only the wrapper script path needs to be permitted. Remove: + +```json + "Bash(gh pr create:*)", + "Bash(glab mr create:*)", + "Bash(glab mr note create:*)", + "Bash(glab issue create:*)", + "Bash(glab issue update:*)", +``` + +### `core/opencode.json` requirements + +Remove all `glab` base permission entries (see Task 3b for the exact list). + +### Verification + +```bash +jq . core/claude/settings.json > /dev/null +jq . core/opencode.json > /dev/null + +# Confirm neutral entries are present and old workflow-facing entries are gone +rg -n "github-publish|gitlab-publish|github-pr-comments|gitlab-mr-comments|create-bug-issue|update-bug-issue" core/claude/settings.json core/opencode.json +``` + +Expected result: only neutral wrapper script entries remain for workflow operations. Provider CLI entries remain only as read-only implementation support if still needed. + +### Commit boundary + +One commit: + +```text +Update permissions for neutral repository skills +``` + +--- + +## Task 5 — Add workflow boundary regression check + +### File to add + +```text +scripts/check-workflow-boundaries.sh +``` + +### Requirements + +The script must fail (exit non-zero) when generic workflow-facing files contain direct provider internals. + +#### Scan targets + +```text +core/agents/skills/workflow-* +core/claude/skills +core/opencode/agents +``` + +#### Disallowed patterns in generic workflow files + +```text +github-publish +gitlab-publish +github-pr-comments +gitlab-mr-comments +gh +glab +open-pr.sh +open-mr.sh +fetch-pr-comments.sh +fetch-mr-comments.sh +``` + +#### Allowed boundary locations + +Provider-specific references are permitted only in: + +```text +core/agents/skills/github-* +core/agents/skills/gitlab-* +core/agents/skills/git-publish +core/agents/skills/change-request-publish +core/agents/skills/change-request-comments +core/agents/skills/issue-tracker +scripts/init.sh +scripts/copy.sh +README.md +``` + +#### Implementation outline + +```bash +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +TARGETS=( + core/agents/skills/workflow-* + core/claude/skills + core/opencode/agents +) + +EXCLUDES=( + core/agents/skills/github-* + core/agents/skills/gitlab-* + core/agents/skills/git-publish + core/agents/skills/change-request-publish + core/agents/skills/change-request-comments + core/agents/skills/issue-tracker + scripts/init.sh + scripts/copy.sh + README.md +) + +PATTERNS=( + 'github-publish' + 'gitlab-publish' + 'github-pr-comments' + 'gitlab-mr-comments' + 'gh ' + 'glab ' + 'open-pr\.sh' + 'open-mr\.sh' + 'fetch-pr-comments\.sh' + 'fetch-mr-comments\.sh' +) + +# Build rg exclude globs +EXCLUDE_ARGS=() +for e in "${EXCLUDES[@]}"; do + EXCLUDE_ARGS+=(--glob="!$e") +done + +FAILED=0 +for pattern in "${PATTERNS[@]}"; do + if rg --line-number --glob='*' "${EXCLUDE_ARGS[@]}" "$pattern" "${TARGETS[@]}"; then + FAILED=1 + fi +done + +if [[ "$FAILED" -ne 0 ]]; then + echo "ERROR: provider-internal references found in generic workflow files." >&2 + exit 1 +fi + +echo "OK: no provider-internal leakage in generic workflow files." +``` + +#### Optional extension + +Add a second pass that scans Claude workflow skills for OpenCode internals (`OpenCode handoff`, `@implement`, `@implement-task`). Allow them only in explicit cross-harness availability notes. This is optional because Task 3c already verifies it with `rg`. + +### Verification + +```bash +bash -n scripts/check-workflow-boundaries.sh +shellcheck scripts/check-workflow-boundaries.sh +bash scripts/check-workflow-boundaries.sh +``` + +Expected result: script exits 0 after all Task 3 changes are complete; it should fail against the current `main` state (confirming it catches leakage). + +### Commit boundary + +One commit: + +```text +Add workflow provider boundary check +``` + +--- + +## Task 6 — Final verification and acceptance + +### Full verification + +```bash +bash -n scripts/init.sh scripts/copy.sh scripts/check-workflow-boundaries.sh +shellcheck scripts/init.sh scripts/copy.sh scripts/check-workflow-boundaries.sh +codespell README.md core/agents/skills core/claude/skills core/opencode/agents core/docs/agents +bash scripts/check-workflow-boundaries.sh +``` + +### Smoke `init.sh` + +```bash +mkdir -p .temp/smoke-init +printf "1\n1\n.temp/smoke-init/target\n" | bash scripts/init.sh +ls -la .temp/smoke-init/target/.claude/skills +``` + +Expected result: all four new skills appear as symlinks in `.claude/skills/`. + +### Smoke `copy.sh` + +```bash +mkdir -p .temp/smoke-copy/target +git -C .temp/smoke-copy/target init +touch .temp/smoke-copy/target/README.md +git -C .temp/smoke-copy/target add README.md +git -C .temp/smoke-copy/target commit -m "init" +printf "1\n1\n" | bash scripts/copy.sh .temp/smoke-copy/target +ls -la .temp/smoke-copy/target/.claude/skills +``` + +Expected result: all four new skills appear as symlinks in `.claude/skills/`. + +### Cleanup + +```bash +rm -rf .temp/smoke-init .temp/smoke-copy +``` + +### Acceptance criteria + +- Generic workflow skills no longer expose GitHub/GitLab mechanics. +- Provider-specific mechanics are isolated to provider skills or neutral integration skills. +- Old provider publish skills (`github-publish/`, `gitlab-publish/`) are removed; `change-request-publish` and `git-publish` are the only publishing paths. +- New authored skills are installed by `init.sh` and `copy.sh`. +- Claude symlinks resolve for all new authored skills. +- README skill lists and symlink table match actual installed assets. +- `core/docs/agents/review-plan.md`, `review-code.md`, and `agent-workflow-extension.md` use neutral skill names. +- `core/opencode.json` no longer contains provider-specific `glab` base permissions. +- `core/claude/settings.json` allows neutral wrapper scripts and no longer lists workflow-facing provider scripts. +- Stack overlays are checked for ripple effects. +- Boundary check prevents future provider-internal leakage. +- Bash scripts pass `bash -n` and `shellcheck`. +- Smoke installs succeed under `.temp/`. + +--- + +## Open questions / decisions + +1. **Provider publish skills.** `github-publish/` and `gitlab-publish/` are removed entirely. `git-publish` owns safe branch pushes and `change-request-publish` owns opening PRs/MRs. Provider CLI commands (`gh pr create`, `glab mr create`) live only inside `change-request-publish/scripts/open-change-request.sh`. + +2. **Default branch detection in `git-publish`.** Decision: protect `main|master|HEAD` for the initial implementation. This matches the existing provider scripts and is sufficient for the leakage fix. + +3. **Duplicate-check interface.** Decision: add `issue-tracker/scripts/find-duplicate-issues.sh` with `--title` and optional `--labels`. Return a compact list; callers decide what constitutes a duplicate. + +4. **`core/opencode.json` `glab` removal.** Decision: remove all base `glab` entries. Any OpenCode agent that still needs provider CLI access must declare it in its own frontmatter, but generic workflow agents should not. + +5. **Claude `change-request-publish` permission tier.** Decision: keep `change-request-publish/scripts/open-change-request.sh` in `ask` to match the current `open-pr.sh`/`open-mr.sh` behavior. If the workflow is later changed to auto-open change requests, move it to `allow`. + +--- + +## Pre-flight baseline + +Run these commands before starting implementation to confirm the current leakage: + +```bash +rg -n "github-publish|gitlab-publish|github-pr-comments|gitlab-mr-comments|gh |glab|open-pr\.sh|open-mr\.sh|fetch-pr-comments|fetch-mr-comments" \ + core/agents/skills/workflow-* core/claude/skills core/opencode/agents + +rg -n "OpenCode handoff|@implement|@implement-task" core/claude/skills + +jq '.permission.bash | keys' core/opencode.json +``` diff --git a/scripts/check-workflow-boundaries.sh b/scripts/check-workflow-boundaries.sh new file mode 100755 index 0000000..9ff2388 --- /dev/null +++ b/scripts/check-workflow-boundaries.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +TARGETS=( + core/agents/skills/workflow-* + core/claude/skills + core/opencode/agents +) + +EXCLUDES=( + core/agents/skills/github-* + core/agents/skills/gitlab-* + core/agents/skills/git-publish + core/agents/skills/change-request-publish + core/agents/skills/change-request-comments + core/agents/skills/issue-tracker + scripts/init.sh + scripts/copy.sh + README.md +) + +# Patterns that indicate provider-internal leakage. +# Focus on provider-specific skill names and write operations. +# Read-only CLI commands (gh issue view, glab issue list, etc.) are allowed +# as implementation support for the neutral wrapper scripts. +PATTERNS=( + 'github-publish' + 'gitlab-publish' + 'github-pr-comments' + 'gitlab-mr-comments' + 'open-pr\.sh' + 'open-mr\.sh' + 'fetch-pr-comments\.sh' + 'fetch-mr-comments\.sh' +) + +# Build rg exclude globs +EXCLUDE_ARGS=() +for e in "${EXCLUDES[@]}"; do + EXCLUDE_ARGS+=(--glob="!$e") +done + +FAILED=0 +for pattern in "${PATTERNS[@]}"; do + if rg --line-number --glob='*' "${EXCLUDE_ARGS[@]}" "$pattern" "${TARGETS[@]}"; then + FAILED=1 + fi +done + +if [[ "$FAILED" -ne 0 ]]; then + echo "ERROR: provider-internal references found in generic workflow files." >&2 + exit 1 +fi + +# --- Skill-internal script path check --- +# Per the Skill Boundary Rule, body text should use skill names, not internal +# script paths like .agents/skills//scripts/*.sh. Only check body text +# (after YAML frontmatter) for OpenCode agent files. + +SCRIPT_PATH_PATTERNS=( + '\.agents/skills/.*/scripts/' + '\.claude/skills/.*/scripts/' +) + +# Boundary skill directories where script usage is the documented interface +BOUNDARY_SKILLS=( + core/agents/skills/git-publish + core/agents/skills/change-request-publish + core/agents/skills/change-request-comments + core/agents/skills/issue-tracker +) + +# Expand target globs to actual files +TARGET_FILES=() +for target in "${TARGETS[@]}"; do + while IFS= read -r file; do + [[ -f "$file" ]] && TARGET_FILES+=("$file") + done < <(find "$target" -name '*.md' -type f 2>/dev/null) +done + +if [[ ${#TARGET_FILES[@]} -eq 0 ]]; then + echo "OK: no workflow files found to check." >&2 + exit 0 +fi + +for file in "${TARGET_FILES[@]}"; do + # Skip boundary skill docs + skip=false + for boundary in "${BOUNDARY_SKILLS[@]}"; do + if [[ "$file" == "$boundary"/* ]]; then + skip=true + break + fi + done + [[ "$skip" == true ]] && continue + + # For OpenCode agent files, only check body text (after second ---) + if [[ "$file" == core/opencode/agents/*.md ]]; then + body=$(awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}' "$file") + else + body=$(cat "$file") + fi + + for pattern in "${SCRIPT_PATH_PATTERNS[@]}"; do + if echo "$body" | rg -n "$pattern" 2>/dev/null; then + echo " ^ in $file" >&2 + FAILED=1 + fi + done +done + +if [[ "$FAILED" -ne 0 ]]; then + echo "ERROR: skill-internal script references found in workflow body text." >&2 + exit 1 +fi + +# --- Bare boundary-skill reference check --- +# Workflow-facing body text must use slash references (e.g. /git-publish) for +# boundary skills, not bare backticked names (e.g. `git-publish`). + +BOUNDARY_SKILL_NAMES=( + 'git-publish' + 'change-request-publish' + 'change-request-comments' + 'issue-tracker' +) + +for file in "${TARGET_FILES[@]}"; do + # Skip boundary skill docs + skip=false + for boundary in "${BOUNDARY_SKILLS[@]}"; do + if [[ "$file" == "$boundary"/* ]]; then + skip=true + break + fi + done + [[ "$skip" == true ]] && continue + + # For OpenCode agent files, only check body text (after second ---) + if [[ "$file" == core/opencode/agents/*.md ]]; then + body=$(awk 'BEGIN{n=0} /^---$/{n++; next} n>=2{print}' "$file") + else + body=$(cat "$file") + fi + + for skill_name in "${BOUNDARY_SKILL_NAMES[@]}"; do + # Match backtick-wrapped name without leading slash + if echo "$body" | rg -n "[^/]\`$skill_name\`" 2>/dev/null; then + echo " ^ bare reference in $file" >&2 + FAILED=1 + fi + # Also match start-of-line backtick-wrapped name without leading slash + if echo "$body" | rg -n "^\`$skill_name\`" 2>/dev/null; then + echo " ^ bare reference in $file" >&2 + FAILED=1 + fi + done +done + +if [[ "$FAILED" -ne 0 ]]; then + echo "ERROR: bare boundary-skill references found; use slash form (e.g. /git-publish)." >&2 + exit 1 +fi + +echo "OK: no provider-internal leakage in generic workflow files." diff --git a/scripts/copy.sh b/scripts/copy.sh index f14bc66..468cb51 100755 --- a/scripts/copy.sh +++ b/scripts/copy.sh @@ -407,10 +407,35 @@ for agent_file in "$STAGE/.opencode/agents/"*.md; do done # --------------------------------------------------------------------------- -# 11. .claude/skills/{brainstorm,bugfix,finish,planner,review-code,review-plan}/ +# 10b. Install .claude/agents/*.md # --------------------------------------------------------------------------- echo "" -CLAUDE_SKILLS=(brainstorm bugfix finish planner review-code review-plan) +for agent_file in "$STAGE/.claude/agents/"*.md; do + [[ -f "$agent_file" ]] || continue + fname="$(basename "$agent_file")" + dst="$TARGET/.claude/agents/$fname" + if [[ -e "$dst" ]]; then + if [[ "$SKILLS_MODE" == "override" ]]; then + mkdir -p "$TARGET/.claude/agents" + cp "$agent_file" "$dst" + COPIED+=(".claude/agents/$fname (overwritten)") + else + SKIPPED+=(".claude/agents/$fname (existing, skipped)") + fi + elif [[ "$SKILLS_MODE" == "skip" ]]; then + SKIPPED+=(".claude/agents/$fname (new, skipped: don't modify)") + else + mkdir -p "$TARGET/.claude/agents" + cp "$agent_file" "$dst" + COPIED+=(".claude/agents/$fname (new)") + fi +done + +# --------------------------------------------------------------------------- +# 11. .claude/skills/{brainstorm,bugfix,finish,implement,planner,review-code,review-plan}/ +# --------------------------------------------------------------------------- +echo "" +CLAUDE_SKILLS=(brainstorm bugfix finish implement planner review-code review-plan) for skill_name in "${CLAUDE_SKILLS[@]}"; do src="$STAGE/.claude/skills/$skill_name" dst="$TARGET/.claude/skills/$skill_name" @@ -443,7 +468,10 @@ AUTHORED_SKILLS=( workflow-planning workflow-verification feature-documentation - github-pr-comments + git-publish + change-request-publish + change-request-comments + issue-tracker ) for skill_name in "${AUTHORED_SKILLS[@]}"; do src="$STAGE/.agents/skills/$skill_name" diff --git a/scripts/init.sh b/scripts/init.sh index f0eec50..185240d 100755 --- a/scripts/init.sh +++ b/scripts/init.sh @@ -237,7 +237,10 @@ AUTHORED_SKILLS=( "workflow-planning" "workflow-verification" "feature-documentation" - "github-pr-comments" + "git-publish" + "change-request-publish" + "change-request-comments" + "issue-tracker" ) mkdir -p "$TARGET/.claude/skills" @@ -258,6 +261,23 @@ for skill_name in "${AUTHORED_SKILLS[@]}"; do fi done +# --------------------------------------------------------------------------- +# 7b. Install .claude/agents/*.md +# --------------------------------------------------------------------------- +echo "" +for agent_file in "$STAGE/.claude/agents/"*.md; do + [[ -f "$agent_file" ]] || continue + fname="$(basename "$agent_file")" + dst="$TARGET/.claude/agents/$fname" + if [[ -e "$dst" ]]; then + echo " Skipping .claude/agents/$fname (already exists)" + else + mkdir -p "$TARGET/.claude/agents" + cp "$agent_file" "$dst" + echo " Installed .claude/agents/$fname" + fi +done + # --------------------------------------------------------------------------- # 8. Install remote skills from merged lock file # --------------------------------------------------------------------------- diff --git a/scripts/publish-branch.sh b/scripts/publish-branch.sh deleted file mode 100755 index f855ba3..0000000 --- a/scripts/publish-branch.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -branch="$(git rev-parse --abbrev-ref HEAD)" - -if [ "$branch" = "main" ]; then - printf 'Refusing to publish protected branch: %s\n' "$branch" >&2 - exit 1 -fi - -git push -u origin "$branch" - -pr_url="$(gh pr list --head "$branch" --json url --jq '.[0].url')" -if [ -n "$pr_url" ]; then - printf '%s\n' "$pr_url" -else - gh pr create --fill -fi