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
28 changes: 27 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,32 @@ 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 and validates the refs actually being pushed (local AND
# remote side of each refspec), not merely HEAD — pushing a compliant
# branch to a non-compliant remote name must not slip through.
#
# No stdin/args are forwarded here on purpose: prek's git shim is what git
# invokes, so prek — not the hook — consumes git's `<local-ref> <local-sha>
# <remote-ref> <remote-sha>` lines, and it forwards neither those nor git's
# `<remote-name> <remote-url>` argv to the entry. It re-publishes the first
# pushed ref pair as PRE_COMMIT_LOCAL_BRANCH / PRE_COMMIT_REMOTE_BRANCH,
# which the script reads from the environment. See the script header.
- 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
189 changes: 189 additions & 0 deletions docs/abca-plugin/skills/pickup-issue/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
---
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").
- The governance repository resolves to a **fork** (see Step 0) — a fork's
issue tracker is writable by the contributor, so its `approved` label is
self-grantable and gates nothing.

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 0: Resolve the governance repository

**Every `gh` call below must be pinned with `--repo`.** CONTRIBUTING tells
contributors to "push to a fork and open a PR against `main`", so in the normal
workflow the local `origin` is the contributor's own fork. An unpinned
`gh issue view`/`edit`/`comment` resolves the repository from the git remotes,
which on a fork can land on the contributor's copy — where they can add the
`approved` label themselves and self-bypass this entire gate.

```bash
# Source of truth for issues and the `approved` label. Override to reuse this
# skill in another repository (see "Adapting this skill" at the end).
REPO="${ABCA_GOVERNANCE_REPO:-aws-samples/sample-autonomous-cloud-coding-agents}"

# Hard-fail if the governance repo is a fork — its `approved` label is
# self-grantable, so the gate would be decorative. This also stops
# ABCA_GOVERNANCE_REPO from being used as a bypass.
gh repo view "$REPO" --json isFork,nameWithOwner \
--jq 'if .isFork then error("governance repo \(.nameWithOwner) is a fork") else .nameWithOwner end'
```

## 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> --repo "$REPO" --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> --repo "$REPO" --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> --repo "$REPO" --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="${REPO%%/*}" -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> --repo "$REPO" --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).

## Adapting this skill to another repository

The gate itself is generic — only the policy it enforces is ABCA-specific. To
reuse it elsewhere, set one environment variable:

```bash
export ABCA_GOVERNANCE_REPO=<owner>/<repo>
```

That repointing is deliberately the *only* runtime knob, because it is the one
with a security consequence: point it at a repository whose labels the
contributor cannot grant themselves (Step 0 hard-fails on a fork for exactly
this reason). Everything else below is policy a forking project edits once in
its own copy of this file:

| Assumption | Where it appears | ABCA value |
|------------|------------------|------------|
| Governance repo | Step 0 (`$REPO`) | `aws-samples/sample-autonomous-cloud-coding-agents` |
| Approval label | Steps 2, 4 | `approved` |
| Branch-name pattern | Step 1 | `(feat\|fix\|chore\|docs)/<issue-number>-<desc>` |
| Blocker markers | Step 3 | `**UNRESOLVED:**` blocks; `**DEFERRED:** … tracked in #N` does not |
| Blocking-dependency source | Step 3 | GraphQL `trackedInIssues` (the dependency graph, not prose) |
| Start signal | Step 4 | a `Starting implementation.` issue comment |
| Governing document | throughout | [ADR-003](../../../decisions/ADR-003-contribution-governance.md) |

Keep the branch-name pattern in sync with `scripts/hooks/check-branch-name.mjs`
and the issue-reference keywords with `scripts/hooks/check-commit-msg.mjs` — the
skill and the hooks enforce two halves of the same policy.
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
Loading
Loading