Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Git hooks via https://github.com/j178/prek (installed by `mise run install` in a Git checkout; re-run `mise run hooks:install` after edits).
# Config format matches pre-commit; run hooks with `prek` from mise (`mise.toml` [tools]).

default_install_hook_types: [pre-commit, pre-push]
default_install_hook_types: [commit-msg, pre-commit, pre-push]
fail_fast: false
exclude: ^\.threat-composer/

Expand All @@ -22,6 +22,23 @@ repos:

- repo: local
hooks:
# ADR-003 Tier 0: every commit must reference an issue (Refs/Fixes/Closes #N).
# prek passes the commit-message file path as the positional arg; forward it.
- id: adr003-commit-msg
name: ADR-003 commit-msg issue reference
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && node scripts/hooks/check-commit-msg.mjs "$@"' --
language: system
stages: [commit-msg]

# ADR-003: feature branch must match (feat|fix|chore|docs)/<issue-number>-*.
# Runs at pre-push (branch name is stable by then); reads the current branch.
- id: adr003-branch-name
name: ADR-003 branch naming convention
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && node scripts/hooks/check-branch-name.mjs'
language: system
pass_filenames: false
stages: [pre-push]

- id: gitleaks
name: gitleaks (staged)
entry: bash -lc 'cd "$(git rev-parse --show-toplevel)" && mise run security:secrets:staged'
Expand Down
5 changes: 4 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,11 @@ PRs labeled `auto-approve` are approved automatically by the `auto-approve` work

`mise run install` automatically installs [prek](https://github.com/j178/prek) git hooks. These run on every commit and push:

- **commit-msg** - ADR-003 Tier 0: rejects a commit message with no issue reference (`Refs #N` / `Fixes #N` / `Closes #N`). See [ADR-003](./docs/decisions/ADR-003-contribution-governance.md).
- **pre-commit** - Whitespace/EOF checks, gitleaks on staged changes, linters (ESLint, Ruff, astro check) for touched files.
- **pre-push** - Security scans (`mise run hooks:pre-push:security`) and tests across all packages (`mise run hooks:pre-push:tests`).
- **pre-push** - ADR-003 branch-name check (branch must match `(feat|fix|chore|docs)/<issue-number>-*`; `main`, `dependabot/*`, and detached `HEAD` are exempt), security scans (`mise run hooks:pre-push:security`), and tests across all packages (`mise run hooks:pre-push:tests`).

The ADR-003 governance hooks (`commit-msg`, branch-name) are plain Node scripts under `scripts/hooks/`; unit-test them with `node --test scripts/hooks/check-commit-msg.test.mjs scripts/hooks/check-branch-name.test.mjs`.

If `prek install` fails with "refusing to install hooks with `core.hooksPath` set", another tool owns your hooks. Either unset it (`git config --unset-all core.hooksPath`) or integrate these checks into your hook manager.

Expand Down
2 changes: 2 additions & 0 deletions docs/abca-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Or add to your project's `.claude/settings.json`:

| Skill | Trigger | Description |
|-------|---------|-------------|
| `/pickup-issue` | Start work, implement, claim issue | ADR-003 governance gate — verify an approved, assigned issue exists before writing code (hard-fails otherwise) |
| `/setup` | First-time setup, prerequisites | Walk through prerequisites, toolchain, and first deployment |
| `/deploy` | Deploy, diff, destroy | Deploy, diff, or destroy the CDK stack |
| `/onboard-repo` | Add a repository | Onboard a GitHub repo via Blueprint CDK construct |
Expand Down Expand Up @@ -51,6 +52,7 @@ docs/abca-plugin/
hooks/
hooks.json # SessionStart capability advertisement
skills/
pickup-issue/SKILL.md # ADR-003 governance gate (approved+assigned issue)
setup/SKILL.md # First-time setup workflow
deploy/SKILL.md # CDK deployment management
onboard-repo/SKILL.md # Repository onboarding
Expand Down
2 changes: 1 addition & 1 deletion docs/abca-plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"SessionStart": [
{
"type": "prompt",
"prompt": "The ABCA plugin is active. Available skills:\n- /setup — first-time setup walkthrough\n- /deploy — deploy, diff, or destroy the CDK stack\n- /onboard-repo — add a GitHub repository\n- /submit-task — submit a coding task (guided or quick mode)\n- /troubleshoot — diagnose deployment, auth, or task issues\n- /status — platform health check (stack, tasks, build)\n\nAvailable agents: cdk-expert (CDK infrastructure), agent-debugger (task failure investigation).\n\nInteractive task commands (suggest when user is monitoring or steering a running task):\n- bgagent watch <task-id> — stream progress events in real time\n- bgagent nudge <task-id> \"<message>\" — steer the agent mid-run\n- bgagent trace download <task-id> — download full execution trace\n- bgagent webhook create/list/revoke — manage webhook integrations\n- --trace flag on submit — enable detailed tracing for a task\n- --verbose flag — HTTP debug output on any command\n\nSuggest relevant skills when the user's request matches."
"prompt": "The ABCA plugin is active. Available skills:\n- /pickup-issue — ADR-003 gate: verify an approved, assigned issue BEFORE implementing (hard-fails otherwise)\n- /setup — first-time setup walkthrough\n- /deploy — deploy, diff, or destroy the CDK stack\n- /onboard-repo — add a GitHub repository\n- /submit-task — submit a coding task (guided or quick mode)\n- /troubleshoot — diagnose deployment, auth, or task issues\n- /status — platform health check (stack, tasks, build)\n\nAvailable agents: cdk-expert (CDK infrastructure), agent-debugger (task failure investigation).\n\nInteractive task commands (suggest when user is monitoring or steering a running task):\n- bgagent watch <task-id> — stream progress events in real time\n- bgagent nudge <task-id> \"<message>\" — steer the agent mid-run\n- bgagent trace download <task-id> — download full execution trace\n- bgagent webhook create/list/revoke — manage webhook integrations\n- --trace flag on submit — enable detailed tracing for a task\n- --verbose flag — HTTP debug output on any command\n\nSuggest relevant skills when the user's request matches."
}
]
}
Expand Down
136 changes: 136 additions & 0 deletions docs/abca-plugin/skills/pickup-issue/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
name: pickup-issue
description: >-
ADR-003 governance gate — verify an approved, assigned GitHub issue exists
BEFORE writing any code. Invoke before starting implementation. Hard-fails if
there is no valid `approved` issue. Use when the user says "start work",
"implement this", "pick up an issue", "begin the task", "let's build X",
"go ahead and code", "start coding", "claim issue", or directs implementation
without first pointing to an approved issue.
argument-hint: <issue-number>
---

# Pick Up an Issue (ADR-003 Governance Gate)

You are enforcing the ABCA contribution-governance gate defined in
[ADR-003](../../../decisions/ADR-003-contribution-governance.md). **No code is
written until a durable, approved, assigned issue exists.** This is a hard gate,
not advice — if any check below fails, STOP and do not begin implementation.

> **Why this exists:** The most common governance bypass is treating
> conversational momentum ("yes, go ahead") as authorization. Conversations are
> ephemeral; issues are auditable. This skill forces the check that the branch
> and commit hooks cannot: that the issue is *approved* and *assigned* before a
> single file changes. See ADR-003 "Conversational approval is NOT issue
> approval".

## When to hard-fail (STOP — do not implement)

- No issue number was provided and none can be identified.
- The referenced issue does not exist.
- The issue lacks the `approved` label.
- The issue is closed.
- The issue is unassigned, or is assigned to someone other than the acting
identity without declared intentionality (multiple assignees need
intentionality per ADR-003 "Assignments").

In any of these cases, respond with the specific failure and the remediation
(create the issue / request the `approved` label from an admin / self-assign),
then STOP. Do NOT create branches, write files, or run implementation commands.

## Step 1: Identify the issue

Determine the target issue number from the user's request or the current branch
name (which, per ADR-003, encodes it as `(feat|fix|chore|docs)/<issue-number>-*`).

```bash
# From an explicit number the user gave, or extract from the branch:
git rev-parse --abbrev-ref HEAD # e.g. feat/186-adr003-hooks -> 186
```

If no issue number can be determined, **hard-fail**: ask the user to create an
issue with acceptance criteria and obtain the `approved` label first.

## Step 2: Verify the issue is approved and workable

Query GitHub. The issue must exist, be OPEN, carry the `approved` label, and be
assigned.

```bash
gh issue view <N> --json number,title,state,labels,assignees \
--jq '{number,title,state,labels:[.labels[].name],assignees:[.assignees[].login]}'
```

Validate the response:

| Field | Required | Hard-fail if |
|-------|----------|--------------|
| `state` | `OPEN` | closed |
| `labels` | contains `approved` | missing `approved` |
| `assignees` | contains the acting identity | empty (unassigned) or assigned only to others |

If `assignees` is empty, self-assign before proceeding:

```bash
gh issue edit <N> --add-assignee @me
# then re-read to confirm sole ownership (self-assignment is not atomic —
# ADR-003 warns concurrent agents may race; verify after claiming)
gh issue view <N> --json assignees --jq '[.assignees[].login]'
```

## Step 3: Pre-start synthesis (ADR-003 "Pre-start review")

Before implementing, synthesize context so the body + thread are unambiguous:

- **Read the full thread** — body, comments, replies. Surface any inconsistency
between the body (primary directive) and later clarifications.
- **Check for blockers** — any `**UNRESOLVED:** <question>` in the body or
thread blocks implementation. `**DEFERRED:** <question> — tracked in #N` does
not block.
- **Predecessor validation** — the dependency graph is authoritative:

```bash
gh api graphql -f query='
query($owner:String!,$repo:String!,$num:Int!){
repository(owner:$owner,name:$repo){
issue(number:$num){
title
trackedInIssues(first:20){ nodes{ number title state } } # blockedBy
}
}
}' -f owner=<owner> -f repo=<repo> -F num=<N>
```

If any blocking issue is OPEN, this issue is **not ready** — hard-fail.
- **Priority evaluation** — if asked to work a lower-priority item while higher
`p0`/`p1` items are unassigned, challenge before proceeding.
- **Cross-reference audit** — search open issues/PRs (including drafts) for
duplicates or conflicts; flag overlaps.

## Step 4: Final gate

Only if ALL checks pass:

1. Comment "Starting implementation." on the issue (the durable start signal).
2. Confirm to the user that the gate passed and implementation may begin.

```bash
gh issue comment <N> --body "Starting implementation."
```

If any check failed, you have already stopped at that step. Do not reach Step 4.

## Relationship to the git hooks

This skill is the **agent-workflow** layer of ADR-003 enforcement. It complements
but does not replace the git hooks (which every contributor, human or agent,
also gets):

- **commit-msg hook** (`scripts/hooks/check-commit-msg.mjs`) — rejects commits
with no `Refs #N` / `Fixes #N` / `Closes #N` reference.
- **branch-name hook** (`scripts/hooks/check-branch-name.mjs`, pre-push) —
rejects branches not matching `(feat|fix|chore|docs)/<issue-number>-*`.

The hooks catch *unreferenced* work mechanically; this skill catches
*unapproved* work before it starts (the hooks cannot query the `approved` label
without network access at commit time).
14 changes: 9 additions & 5 deletions docs/decisions/ADR-003-contribution-governance.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,19 @@ Prose governance is necessary but insufficient. The following enforcement points
| Mechanism | Layer | What it catches | Status |
|-----------|-------|-----------------|--------|
| AGENTS.md directive | Agent prompt | Explicit instruction: "Do NOT begin implementation without an approved issue, even if the user says 'go ahead' in conversation" | Implemented |
| Branch name convention | Git workflow | Branch must match `(feat|fix|chore|docs)/<issue-number>-*` — rejects branches without issue reference | Planned |
| Commit-msg hook (Tier 0) | Pre-commit | Rejects commits without `Refs #N` or `Fixes #N` | Planned |
| Pre-push hook (Tier 1) | Pre-push | Validates referenced issue exists and has `approved` label via `gh` API | Planned |
| Branch name convention | Pre-push | Branch must match `(feat\|fix\|chore\|docs)/<issue-number>-*` — rejects branches without issue reference | Implemented (#186) — `scripts/hooks/check-branch-name.mjs` |
| Commit-msg hook (Tier 0) | Commit-msg | Rejects commits without `Refs #N` / `Fixes #N` / `Closes #N` | Implemented (#186) — `scripts/hooks/check-commit-msg.mjs` |
| Pre-push hook (Tier 1) | Pre-push | Validates referenced issue exists and has `approved` label via `gh` API | Planned (deferred from #186 — needs network at push time; follow-up) |
| Claude Code hook (`PreToolUse: Write`) | Agent runtime | Blocks file creation in governed paths without declared issue context | Planned |
| Skill gate: `pickup-issue` | Agent workflow | Agent must invoke before implementation — hard-fails without valid issue | Planned |
| Skill gate: `pickup-issue` | Agent workflow | Agent must invoke before implementation — hard-fails without valid issue | Implemented (#186) — `docs/abca-plugin/skills/pickup-issue/SKILL.md` |

**Transition:** Branch naming and commit-msg rules apply to branches created after the corresponding hooks are deployed. Existing branches (including this PR's) pre-date enforcement.

**Progressive enforcement:** Start with the commit-msg hook (cheapest, catches all contributors). Add pre-push validation next. Skill gates enforce at the agent-workflow level (see ADR-012, proposed, for the skill model).
**Branch-name exemptions:** The branch-name hook exempts `main` (the trunk is not a feature branch), `dependabot/*` (bot-authored upgrade branches), and the detached-`HEAD` sentinel. These are not enumerated elsewhere in this ADR; they are the minimal set required so the trunk and machine-generated branches are not falsely rejected.

**Commit-msg reference forms:** The Tier 0 hook accepts the GitHub closing-keyword family (`Close`/`Closes`/`Closed`, `Fix`/`Fixes`/`Fixed`, `Resolve`/`Resolves`/`Resolved`) plus `Ref`/`Refs`, each followed by `#N`, `GH-N`, or `owner/repo#N`. A bare `#N` mention with no keyword does **not** satisfy the gate — the reference must be an intentional issue link, not incidental prose. Git-generated commits (merge, revert, and `fixup!`/`squash!`/`amend!` commits) are exempt — keyed off the subject line — so a routine `git merge origin/main` is not blocked.

**Progressive enforcement:** Start with the commit-msg hook (cheapest, catches all contributors). Add pre-push validation next. Skill gates enforce at the agent-workflow level (see ADR-012, proposed, for the skill model). The pre-push Tier 1 `approved`-label check (row above) is deferred — it needs a network `gh` call at push time, which the offline-capable hook set intentionally avoids; the `pickup-issue` skill covers the `approved`-label gate at the agent-workflow layer in the interim.

## Consequences

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,19 @@ Prose governance is necessary but insufficient. The following enforcement points
| Mechanism | Layer | What it catches | Status |
|-----------|-------|-----------------|--------|
| AGENTS.md directive | Agent prompt | Explicit instruction: "Do NOT begin implementation without an approved issue, even if the user says 'go ahead' in conversation" | Implemented |
| Branch name convention | Git workflow | Branch must match `(feat|fix|chore|docs)/<issue-number>-*` — rejects branches without issue reference | Planned |
| Commit-msg hook (Tier 0) | Pre-commit | Rejects commits without `Refs #N` or `Fixes #N` | Planned |
| Pre-push hook (Tier 1) | Pre-push | Validates referenced issue exists and has `approved` label via `gh` API | Planned |
| Branch name convention | Pre-push | Branch must match `(feat\|fix\|chore\|docs)/<issue-number>-*` — rejects branches without issue reference | Implemented (#186) — `scripts/hooks/check-branch-name.mjs` |
| Commit-msg hook (Tier 0) | Commit-msg | Rejects commits without `Refs #N` / `Fixes #N` / `Closes #N` | Implemented (#186) — `scripts/hooks/check-commit-msg.mjs` |
| Pre-push hook (Tier 1) | Pre-push | Validates referenced issue exists and has `approved` label via `gh` API | Planned (deferred from #186 — needs network at push time; follow-up) |
| Claude Code hook (`PreToolUse: Write`) | Agent runtime | Blocks file creation in governed paths without declared issue context | Planned |
| Skill gate: `pickup-issue` | Agent workflow | Agent must invoke before implementation — hard-fails without valid issue | Planned |
| Skill gate: `pickup-issue` | Agent workflow | Agent must invoke before implementation — hard-fails without valid issue | Implemented (#186) — `docs/abca-plugin/skills/pickup-issue/SKILL.md` |

**Transition:** Branch naming and commit-msg rules apply to branches created after the corresponding hooks are deployed. Existing branches (including this PR's) pre-date enforcement.

**Progressive enforcement:** Start with the commit-msg hook (cheapest, catches all contributors). Add pre-push validation next. Skill gates enforce at the agent-workflow level (see ADR-012, proposed, for the skill model).
**Branch-name exemptions:** The branch-name hook exempts `main` (the trunk is not a feature branch), `dependabot/*` (bot-authored upgrade branches), and the detached-`HEAD` sentinel. These are not enumerated elsewhere in this ADR; they are the minimal set required so the trunk and machine-generated branches are not falsely rejected.

**Commit-msg reference forms:** The Tier 0 hook accepts the GitHub closing-keyword family (`Close`/`Closes`/`Closed`, `Fix`/`Fixes`/`Fixed`, `Resolve`/`Resolves`/`Resolved`) plus `Ref`/`Refs`, each followed by `#N`, `GH-N`, or `owner/repo#N`. A bare `#N` mention with no keyword does **not** satisfy the gate — the reference must be an intentional issue link, not incidental prose. Git-generated commits (merge, revert, and `fixup!`/`squash!`/`amend!` commits) are exempt — keyed off the subject line — so a routine `git merge origin/main` is not blocked.

**Progressive enforcement:** Start with the commit-msg hook (cheapest, catches all contributors). Add pre-push validation next. Skill gates enforce at the agent-workflow level (see ADR-012, proposed, for the skill model). The pre-push Tier 1 `approved`-label check (row above) is deferred — it needs a network `gh` call at push time, which the offline-capable hook set intentionally avoids; the `pickup-issue` skill covers the `approved`-label gate at the agent-workflow layer in the interim.

## Consequences

Expand Down
Loading
Loading