@mmerterden/multi-agent-pipeline 11.0.0 → 11.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/install/_common.mjs +9 -1
  3. package/install/claude.mjs +12 -3
  4. package/install/templates/claude-hooks.json +18 -1
  5. package/package.json +1 -1
  6. package/pipeline/commands/multi-agent/refs/features/autopilot-circuit-breaker.md +32 -0
  7. package/pipeline/commands/multi-agent/refs/phases/modes.md +1 -0
  8. package/pipeline/commands/multi-agent/refs/picker-contract.md +1 -1
  9. package/pipeline/commands/multi-agent/sync.md +2 -2
  10. package/pipeline/scripts/agent-guard.py +74 -0
  11. package/pipeline/scripts/agent-guard.sh +48 -0
  12. package/pipeline/scripts/build-stack-plugins.mjs +1 -1
  13. package/pipeline/scripts/fixtures/install-layout.tsv +4 -4
  14. package/pipeline/scripts/scan-agent-config.sh +107 -0
  15. package/pipeline/scripts/smoke-agent-guard.sh +74 -0
  16. package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +36 -0
  17. package/pipeline/scripts/smoke-config-hygiene.sh +58 -0
  18. package/pipeline/scripts/smoke-gate-hooks.sh +18 -0
  19. package/pipeline/skills/.skills-index.json +47 -2
  20. package/pipeline/skills/shared/external/agent-introspection-debugging/SKILL.md +69 -0
  21. package/pipeline/skills/shared/external/backlog/BACKLOG.md +32 -0
  22. package/pipeline/skills/shared/external/backlog/SKILL.md +49 -0
  23. package/pipeline/skills/shared/external/council/SKILL.md +69 -0
  24. package/pipeline/skills/shared/external/search-first/SKILL.md +76 -0
  25. package/pipeline/skills/shared/external/skill-creator/SKILL.md +48 -0
  26. package/pipeline/skills/shared/external/skill-creator/audit.md +59 -0
  27. package/pipeline/skills/shared/external/skill-creator/checklist.md +32 -0
  28. package/pipeline/skills/shared/external/skill-creator/examples.md +65 -0
  29. package/pipeline/skills/shared/external/skill-creator/label-check.md +43 -0
  30. package/pipeline/skills/shared/external/skill-creator/scripts/audit-panel.js +83 -0
  31. package/pipeline/skills/shared/external/skill-creator/template.md +67 -0
  32. package/pipeline/skills/skills-index.md +6 -1
package/CHANGELOG.md CHANGED
@@ -14,6 +14,58 @@ Internal file-layout changes that don't affect the slash-command surface are sti
14
14
 
15
15
  ---
16
16
 
17
+ ## [11.2.0] - 2026-07-07
18
+
19
+ Skill mining + an install-safety fix.
20
+
21
+ - **Fix (data-loss): `install.js` no longer clobbers user-owned rules.** `rules/`
22
+ is authored/evolved locally and is deliberately never synced back to the repo
23
+ (privacy). The installer used to `wipe + copy` it on every update, which
24
+ reverted local edits (including the git attribution rule). Rules now install
25
+ write-if-missing: new baseline rules are added, existing local rules are
26
+ preserved untouched. New `copyDir({ skipExisting })` option backs this.
27
+ - **Two skills mined from the corporate iOS toolkit and genericized into
28
+ `ai-common-engineering-toolkit` (v0.1.2):**
29
+ - `skill-creator` — the house rules for authoring skills (description-first
30
+ discovery, lean SKILL.md as an index, progressive disclosure, reference vs
31
+ workflow vs tool layers, no-prefix naming, grow-from-failure), with a
32
+ template, pre-ship checklist, good/bad examples, and a multi-architect audit
33
+ script. All corporate references scrubbed.
34
+ - `backlog` — a deferred-work registry where every deferred job lands with its
35
+ full spec + origin + unblock condition (deferred -> ready -> in-progress ->
36
+ done, never deleted), so deferral never means loss.
37
+
38
+ ## [11.1.0] - 2026-07-07
39
+
40
+ Harness-hardening pass: four techniques adapted from studying cross-harness agent
41
+ operating systems, all additive and opt-in.
42
+
43
+ - **Guard hooks (`agent-guard.sh`)** — a new PreToolUse Bash gate that turns two
44
+ prompt-level rules into deterministic, OS-enforced blocks: AI/assistant
45
+ attribution in a commit message, and force-push to a protected branch
46
+ (main/master/develop). Fail-open on any internal error, never executes the
47
+ inspected command (decision core is `agent-guard.py`, shlex-tokenized only),
48
+ no network, no secret output. Wired into `install/templates/claude-hooks.json`
49
+ alongside the existing secret scan; `multi-agent:setup` offers to merge it.
50
+ Backed by `smoke-agent-guard.sh` (19 behavioral cases incl. injection safety).
51
+ - **Config-hygiene gate (`scan-agent-config.sh`)** — a dependency-free, read-only
52
+ audit of the shipped config surface (hook templates, preferences template,
53
+ agent defs, MCP helper scripts) for hardcoded secrets, permission-bypass flags,
54
+ eval-bearing hook commands, blanket `Bash(*)` allows, and pipe-to-shell /
55
+ unpinned-npx install patterns. Runs in the release VERIFY step; blocks on any
56
+ HIGH finding. Backed by `smoke-config-hygiene.sh` (real surface clean +
57
+ planted-bad detection).
58
+ - **Autopilot circuit-breaker** — `refs/features/autopilot-circuit-breaker.md`
59
+ defines the sanctioned autopilot pause: halt and hand back to the user on a
60
+ no-progress stall, an identical repeated failure, a rework storm, cost drift
61
+ past the `costBudget` ceiling, or a merge/rebase conflict. Continuing
62
+ unattended off the happy path is the less safe choice.
63
+ - **Three technique skills** added to `ai-common-engineering-toolkit`
64
+ (v0.1.1): `council` (multi-voice adversarial decision), `search-first`
65
+ (research-before-coding with an adopt/extend/compose/build matrix), and
66
+ `agent-introspection-debugging` (capture -> diagnose -> contained-recovery ->
67
+ report, with an honesty guard against fake auto-heal claims).
68
+
17
69
  ## [11.0.0] - 2026-07-07
18
70
 
19
71
  Breaking: the two issue creators `generate-task` and `generate-bug` are merged
@@ -59,7 +59,7 @@ export function ensureDir(dir) {
59
59
  * @param {{ exclude?: Array<string|RegExp>, useSymlinks?: boolean }} [opts]
60
60
  */
61
61
  export function copyDir(src, dest, opts = {}) {
62
- const { exclude = [], useSymlinks = false } = opts;
62
+ const { exclude = [], useSymlinks = false, skipExisting = false } = opts;
63
63
  if (useSymlinks) {
64
64
  symlinkDir(src, dest);
65
65
  return;
@@ -69,6 +69,14 @@ export function copyDir(src, dest, opts = {}) {
69
69
  return;
70
70
  }
71
71
  ensureDir(dest);
72
+ // skipExisting: never overwrite a file that already exists in dest. Used for
73
+ // user-owned trees (rules/) so an update adds new baseline files but preserves
74
+ // the user's locally-evolved copies. cpSync with force:false + errorOnExist:false
75
+ // silently skips existing files.
76
+ if (skipExisting) {
77
+ cpSync(src, dest, { recursive: true, force: false, errorOnExist: false });
78
+ return;
79
+ }
72
80
  if (exclude.length === 0) {
73
81
  cpSync(src, dest, { recursive: true, force: true });
74
82
  return;
@@ -108,9 +108,18 @@ function installRules(pipelineSrc, dest, useSymlinks) {
108
108
  console.log(" [Claude Code] Installing rules...");
109
109
  const rulesSrc = join(pipelineSrc, "rules");
110
110
  if (!existsSync(rulesSrc)) return;
111
- wipeDir(dest);
112
- copyDir(rulesSrc, dest, { useSymlinks });
113
- console.log(` -> ${countFiles(rulesSrc)} files copied to ${dest}`);
111
+ // rules/ is USER-OWNED: users evolve their global rules locally and the sync
112
+ // deliberately never pushes rules/ back to the repo (privacy). So an update
113
+ // must NOT wipe+overwrite them (that reverts local edits, including the git
114
+ // attribution rule). Install only files that do not already exist; leave the
115
+ // user's existing rules untouched. (Symlink mode is a dev choice and keeps its
116
+ // link-replace behavior.)
117
+ if (useSymlinks) {
118
+ copyDir(rulesSrc, dest, { useSymlinks });
119
+ } else {
120
+ copyDir(rulesSrc, dest, { skipExisting: true });
121
+ }
122
+ console.log(` -> rules present (existing local rules preserved)`);
114
123
  }
115
124
 
116
125
  function installSchemas(pipelineSrc, dest, useSymlinks) {
@@ -1,5 +1,5 @@
1
1
  {
2
- "_readme": "Recommended Claude Code hooks for multi-agent-pipeline. Merge the `hooks` object into your ~/.claude/settings.json to make the secret scan a HARD pre-commit gate (exit 2 blocks the commit) rather than an advisory step. This is the one gate that is naturally a PreToolUse hook: it scans the staged diff with no extra arguments. The other deterministic gates (evidence, consensus, intent, learnings) are invoked by the pipeline phases with run-specific arguments (a build-log path, the triage JSON, the free-text input), so they are phase-enforced by contract, not OS-hookable. multi-agent:setup offers to merge this block.",
2
+ "_readme": "Recommended Claude Code hooks for multi-agent-pipeline. Merge the `hooks` object into your ~/.claude/settings.json to make these deterministic, OS-enforced PreToolUse gates real (exit 2 blocks the tool call) rather than prompt-level hopes. Two gates ship here: (1) a staged-diff secret scan on git commit (pre-commit-check.sh); (2) an agent-guard on git commit + git push (agent-guard.sh) that blocks AI/assistant attribution in commit messages and force-push to a protected branch (main/master/develop). Both scripts are self-contained, fail-open on internal error, never execute the inspected command, and need no run-specific arguments, which is why they are naturally PreToolUse hooks. The other deterministic gates (evidence, consensus, intent, learnings) take run-specific arguments and are phase-enforced by the pipeline instead. multi-agent:setup offers to merge this block.",
3
3
  "hooks": {
4
4
  "PreToolUse": [
5
5
  {
@@ -10,6 +10,23 @@
10
10
  "command": "bash $HOME/.claude/scripts/pre-commit-check.sh",
11
11
  "timeout": 15,
12
12
  "statusMessage": "Scanning staged changes for secrets..."
13
+ },
14
+ {
15
+ "type": "command",
16
+ "command": "bash $HOME/.claude/scripts/agent-guard.sh",
17
+ "timeout": 10,
18
+ "statusMessage": "Checking commit for attribution..."
19
+ }
20
+ ]
21
+ },
22
+ {
23
+ "matcher": "Bash(git push:*)",
24
+ "hooks": [
25
+ {
26
+ "type": "command",
27
+ "command": "bash $HOME/.claude/scripts/agent-guard.sh",
28
+ "timeout": 10,
29
+ "statusMessage": "Checking push safety..."
13
30
  }
14
31
  ]
15
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "11.0.0",
3
+ "version": "11.2.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code and Copilot CLI. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Fable triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -0,0 +1,32 @@
1
+ # Feature: Autopilot Circuit-Breaker
2
+
3
+ **Pattern**: autopilot runs with zero interaction, which is exactly when a silent failure loop is most expensive - an agent can burn a budget re-attempting the same broken fix, or thrash between two phases, with nobody watching. A circuit-breaker converts "keep going no matter what" into "keep going until a defined unsafe condition, then halt and hand back to the user." This is the sanctioned autopilot pause (same class as the Phase 7 channels pause): the run stops, records why, and waits for an explicit `resume`.
4
+
5
+ **Gated by `prefs.global.autopilotCircuitBreaker`** (default: enabled; thresholds tunable). Halting is always safe, so the breaker itself defaults on. Disable per-run only with an explicit override. Complements, does not replace, the existing autopilot safety rules (build-fail max 3 retries, Phase 4 blocking-finding rework, destructive-op confirmations).
6
+
7
+ ## Trip conditions
8
+
9
+ Any one trips the breaker. All are evaluated from `agent-state.json` + telemetry, no extra model calls.
10
+
11
+ | # | Trigger | Detection | Default threshold |
12
+ |---|---|---|---|
13
+ | 1 | **No-progress stall** | two consecutive phase checkpoints with no forward transition (same `phase` + `step` + `reworkCount`, no new artifact/commit) | 2 checkpoints |
14
+ | 2 | **Identical repeated failure** | same normalized build-error signature, or the same Phase 4 finding fingerprint, recurs across consecutive Phase 3 rework cycles | 2 cycles |
15
+ | 3 | **Rework storm** | Phase 4 -> Phase 3 rework cycles exceed the cap (distinct from the build-retry cap) | `maxReworkCycles` = 3 |
16
+ | 4 | **Cost drift** | cumulative spend crosses the `costBudget` ceiling, or the projected next-phase spend would exceed it, after model-fallback has already downgraded | `costBudget` ceiling |
17
+ | 5 | **Merge/rebase conflict** | Phase 6 push blocked by a conflict that requires history reconciliation | any conflict |
18
+
19
+ Trigger 2 is the key addition over the plain build-retry cap: a build can "fail differently" three times (legitimate iteration) or "fail identically" twice (stuck). Only the identical-failure case is a stall; the retry cap catches the rest.
20
+
21
+ ## Action on trip
22
+
23
+ 1. Set `agent-state.json.circuitBreaker = {tripped: true, trigger: <#>, detail, checkpoint}` and flip `autopilot` handling to paused (the run does not continue unattended).
24
+ 2. Emit one actionable line per the progress contract: what tripped, the evidence (error signature / cycle count / spend vs ceiling), and the single next action (`resume #N` after a fix, or `kill #N`).
25
+ 3. Never auto-resolve the underlying cause - no force-anything, no conflict auto-merge, no budget self-raise. The breaker hands control back; it does not paper over the problem.
26
+ 4. `resume #N` clears the tripped flag and continues from the recorded checkpoint. If the same trigger fires again immediately, the breaker re-trips (no silent bypass).
27
+
28
+ ## Why this is the right autopilot exception
29
+
30
+ Autopilot's contract is "no interaction on the happy path." The circuit-breaker fires only off the happy path, where continuing unattended is the *less* safe choice: repeating a proven-broken action, or spending past a ceiling the user set, is not autonomy, it is a runaway. Halting with a precise reason is cheaper than the tokens (and trust) a silent loop burns.
31
+
32
+ Inspired by autonomous-loop operators that gate on explicit stop conditions (no-progress, identical-stack-trace repetition, cost drift, conflict blocking), adapted to this pipeline's phase/rework/state model.
@@ -35,6 +35,7 @@ Autopilot mode skips interactive confirmations and runs the pipeline end-to-end
35
35
  - Phase 4 Review -> if blocking finding, returns to Phase 3, auto fix + rebuild (safety)
36
36
  - Kill/Purge confirmations -> destructive operations always ask
37
37
  - Build fail -> auto fix + rebuild (max 3 retries). After 3 retries still failing -> pause, ask user
38
+ - **Circuit-breaker** -> autopilot halts (records reason, waits for `resume`) on a no-progress stall, an identical repeated failure, a rework storm, cost drift past the `costBudget` ceiling, or a merge/rebase conflict. Full wiring: `refs/features/autopilot-circuit-breaker.md`. This is the sanctioned autopilot pause - continuing unattended off the happy path is the less safe choice.
38
39
  - **Phase 7 channels dispatch** -> always pauses for multi-select. Rationale: Jira/Confluence are externally visible; silently posting wrong-tone content leaks team-visible artifacts. See below.
39
40
 
40
41
  **State tracking**: `agent-state.json` gets `"autopilot": true`. Autopilot continues on resume as well.
@@ -56,6 +56,6 @@ In autopilot, `ask_choice` resolves to `default` (or the safe first option) with
56
56
 
57
57
  ## Deterministic gates note
58
58
 
59
- Claude Code's `PreToolUse` exit-2 hook is the one HARD blocking gate: the secret scan runs on every `git commit` and a non-zero exit blocks it. The recommended hook block ships at `install/templates/claude-hooks.json`; `multi-agent:setup` offers to merge it into `~/.claude/settings.json`. Only the secret scan is naturally a PreToolUse hook (it needs no run-specific arguments) - the other deterministic gates (evidence, consensus, intent, learnings) are invoked by the pipeline phases with per-run arguments (a build-log path, the triage JSON, the free-text input), so they are phase-enforced by contract, not OS-hookable.
59
+ Claude Code's `PreToolUse` exit-2 hooks are the HARD blocking gates. Two ship, both needing no run-specific arguments so they are naturally hookable: (1) `pre-commit-check.sh` scans the staged diff on every `git commit` and blocks on a detected secret; (2) `agent-guard.sh` runs on `git commit` + `git push` and blocks AI/assistant attribution in a commit message and force-push to a protected branch (main/master/develop). Both are self-contained, fail-open on internal error, and never execute the inspected command. The recommended hook block ships at `install/templates/claude-hooks.json`; `multi-agent:setup` offers to merge it into `~/.claude/settings.json`. The other deterministic gates (evidence, consensus, intent, learnings) are invoked by the pipeline phases with per-run arguments (a build-log path, the triage JSON, the free-text input), so they are phase-enforced by contract, not OS-hookable.
60
60
 
61
61
  Copilot CLI has no `PreToolUse` equivalent, so the secret scan there is workflow-enforced (run as a phase step, not OS-blocked) plus a CI smoke-gate step.
@@ -148,7 +148,7 @@ If nothing is stale → report "All targets up to date" and stop.
148
148
  5. **Files NOT synced** (local-only, may contain personal data):
149
149
  - `~/.claude/multi-agent-preferences.json`
150
150
  - `~/.claude/CLAUDE.md`, `~/.claude/rules/`, `~/.claude/knowledge/`
151
- - `~/.claude/scripts/` - EXCEPT `pre-commit-check.sh` and `build-stack-plugins.mjs` (generic, synced)
151
+ - `~/.claude/scripts/` - EXCEPT `pre-commit-check.sh`, `agent-guard.sh`, `agent-guard.py`, and `build-stack-plugins.mjs` (generic, synced)
152
152
  - `~/.claude/settings.json`
153
153
 
154
154
  6. **Cross-platform smoke gate** (final step of the REPO sync, before push):
@@ -256,7 +256,7 @@ When invoked with the `release` argument:
256
256
 
257
257
  ```
258
258
  1. REPO Claude Code -> pipeline repo (genericized)
259
- 2. VERIFY Personal data scan -> 0 results
259
+ 2. VERIFY Personal data scan -> 0 results; config-hygiene scan (scan-agent-config.sh) -> 0 HIGH
260
260
  3. LINT + TEST ESLint + smoke tests
261
261
  4. VERSION package.json version bump (patch/minor/major)
262
262
  5. Commit + TAG git commit + git tag v{VERSION}
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """Decision core for agent-guard.sh (see that file for the hook contract).
3
+
4
+ Reads the PreToolUse payload JSON on stdin and prints exactly one decision:
5
+ BLOCK_ATTRIB | BLOCK_FORCE | OK
6
+
7
+ Never executes the command. shlex.split only tokenizes shell-like text; a
8
+ `$(...)` in the command is a literal token, never a subshell. Any error prints
9
+ OK (fail-open) so a guard bug cannot break a legitimate tool call.
10
+ """
11
+ import json
12
+ import os
13
+ import re
14
+ import shlex
15
+ import sys
16
+
17
+ # --- Rule data (extend here; this is the hookify-style rule surface) ----------
18
+ PROTECTED = {"main", "master", "develop"}
19
+ REMOTES = {"origin", "upstream"}
20
+ ATTRIBUTION = re.compile(
21
+ r"co-authored-by:\s*(claude|anthropic|opus|sonnet|haiku|fable)"
22
+ r"|noreply@anthropic\.com"
23
+ r"|generated with\s+.*claude"
24
+ r"|\U0001F916\s*generated"
25
+ r"|claude code <",
26
+ re.IGNORECASE,
27
+ )
28
+ FORCE = re.compile(r"(^|\s)(--force(\b|$)|--force-with-lease|-\S*f\S*)(\s|$)")
29
+
30
+
31
+ def decide(cmd: str) -> str:
32
+ low = cmd.lower()
33
+
34
+ # Rule 1: AI attribution inside a git commit.
35
+ if re.search(r"git\s+commit", low) and ATTRIBUTION.search(cmd):
36
+ return "BLOCK_ATTRIB"
37
+
38
+ # Rule 2: force-push to a protected branch.
39
+ if re.search(r"git\s+push", low) and FORCE.search(cmd):
40
+ try:
41
+ toks = shlex.split(cmd)
42
+ except Exception:
43
+ toks = cmd.split()
44
+ if any(t in PROTECTED or t.split(":")[-1] in PROTECTED for t in toks):
45
+ return "BLOCK_FORCE"
46
+ # non-flag args after `push`; only a remote (or nothing) means a bare push
47
+ args, seen = [], False
48
+ for t in toks:
49
+ if t == "push":
50
+ seen = True
51
+ continue
52
+ if seen and not t.startswith("-"):
53
+ args.append(t)
54
+ nonremote = [a for a in args if a not in REMOTES]
55
+ if not nonremote and os.environ.get("CUR_BRANCH", "") in PROTECTED:
56
+ return "BLOCK_FORCE"
57
+
58
+ return "OK"
59
+
60
+
61
+ def main() -> None:
62
+ try:
63
+ data = json.load(sys.stdin)
64
+ cmd = (data.get("tool_input") or {}).get("command", "")
65
+ if not isinstance(cmd, str) or not cmd:
66
+ print("OK")
67
+ return
68
+ print(decide(cmd))
69
+ except Exception:
70
+ print("OK")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ # agent-guard.sh - PreToolUse Bash guard for the multi-agent pipeline.
3
+ #
4
+ # Turns two prompt-level rules into deterministic, OS-enforced gates:
5
+ # 1. No AI/assistant attribution in git commit messages (Co-Authored-By: Claude,
6
+ # "Generated with Claude Code", robot emoji, Anthropic no-reply address).
7
+ # 2. No force-push to a protected branch (main / master / develop).
8
+ #
9
+ # Contract (Claude Code PreToolUse hook):
10
+ # - Reads the tool-call JSON on stdin: {"tool_input":{"command":"..."}, ...}.
11
+ # - Exit 2 -> BLOCK the tool call (reason on stderr, shown to the model).
12
+ # - Exit 0 -> allow.
13
+ #
14
+ # Safety design (fail-OPEN):
15
+ # - The command string is ONLY parsed/pattern-matched, NEVER executed or eval'd
16
+ # (the decision core is agent-guard.py; shlex tokenizes without running).
17
+ # - Any internal error (bad JSON, missing python3/helper, empty input) -> exit 0.
18
+ # A guard bug must never break a legitimate tool call.
19
+ # - No network, no file writes, no secret values printed.
20
+ # - The single exec is a read-only `git rev-parse` to learn the current branch.
21
+
22
+ set -u
23
+
24
+ HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || true)"
25
+ HELPER="$HERE/agent-guard.py"
26
+ [ -f "$HELPER" ] || exit 0
27
+ command -v python3 >/dev/null 2>&1 || exit 0
28
+
29
+ PAYLOAD="$(cat 2>/dev/null || true)"
30
+ [ -z "$PAYLOAD" ] && exit 0
31
+
32
+ CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
33
+
34
+ DECISION="$(printf '%s' "$PAYLOAD" | CUR_BRANCH="$CUR_BRANCH" python3 "$HELPER" 2>/dev/null || true)"
35
+
36
+ case "$DECISION" in
37
+ BLOCK_ATTRIB)
38
+ echo "BLOCKED by agent-guard: the commit message carries AI/assistant attribution." >&2
39
+ echo "Remove any 'Co-Authored-By: Claude', 'Generated with Claude Code', robot-emoji, or" >&2
40
+ echo "anthropic no-reply trailer. Commits are authored solely as the user's git identity." >&2
41
+ exit 2 ;;
42
+ BLOCK_FORCE)
43
+ echo "BLOCKED by agent-guard: force-push to a protected branch (main/master/develop) is not allowed." >&2
44
+ echo "Rewriting shared history is a data-loss risk. Push a normal commit, or force-push a feature branch." >&2
45
+ exit 2 ;;
46
+ *)
47
+ exit 0 ;;
48
+ esac
@@ -36,7 +36,7 @@ const EXTERNAL = join(PIPE_ROOT, 'pipeline/skills/shared/external');
36
36
  const DRY = args.includes('--dry-run');
37
37
 
38
38
  const COMMON_PLUGIN = 'ai-common-engineering-toolkit';
39
- const COMMON_SKILLS = ['accessibility-compliance-accessibility-audit', 'firebase', 'humanizer'];
39
+ const COMMON_SKILLS = ['accessibility-compliance-accessibility-audit', 'firebase', 'humanizer', 'council', 'search-first', 'agent-introspection-debugging', 'skill-creator', 'backlog'];
40
40
 
41
41
  // Apple/Xcode-only skills that match no stack pattern → iOS plugin only.
42
42
  const APPLE_ONLY = [
@@ -5,12 +5,12 @@
5
5
  .claude/multi-agent-preferences.json 1
6
6
  .claude/rules 12
7
7
  .claude/schemas 23
8
- .claude/scripts 174
8
+ .claude/scripts 180
9
9
  .claude/settings.json 1
10
- .claude/skills 560
10
+ .claude/skills 572
11
11
  .copilot/agents 8
12
12
  .copilot/copilot-instructions.md 1
13
13
  .copilot/lib 24
14
14
  .copilot/schemas 23
15
- .copilot/scripts 174
16
- .copilot/skills 598
15
+ .copilot/scripts 180
16
+ .copilot/skills 609
@@ -0,0 +1,107 @@
1
+ #!/usr/bin/env bash
2
+ # scan-agent-config.sh - config-hygiene gate for the shipped agent surface.
3
+ #
4
+ # Audits the config the pipeline SHIPS (install templates, preferences template,
5
+ # agent definitions, MCP helper scripts) for security problems before release:
6
+ # HIGH - hardcoded secrets; hook commands that eval/execute inspected input;
7
+ # permission bypass flags baked into a shipped template.
8
+ # MEDIUM - unpinned/remote-install supply-chain patterns (curl|bash, npx -y);
9
+ # blanket Bash(*) permission allow.
10
+ #
11
+ # Read-only. Never prints a secret value (reports file + rule only). Self-contained
12
+ # (no external scanner dependency). Exit 1 if any HIGH finding, else 0.
13
+ #
14
+ # Inspired by config-audit tools like ecc-agentshield, but rewritten as a
15
+ # first-party, dependency-free gate over this repo's own shipped surface.
16
+
17
+ set -uo pipefail
18
+
19
+ # ROOT defaults to the repo root; SCAN_ROOT overrides it (used by the smoke test
20
+ # to point at a fixture tree of planted-bad configs).
21
+ ROOT="${SCAN_ROOT:-$(cd "$(dirname "$0")/../.." && pwd)}"
22
+
23
+ HIGH=0; MED=0
24
+ high() { HIGH=$((HIGH+1)); echo " [HIGH] $1"; }
25
+ med() { MED=$((MED+1)); echo " [MEDIUM] $1"; }
26
+
27
+ # Shipped config surface (globs expanded safely; missing paths are skipped).
28
+ TARGETS=()
29
+ add() { [ -e "$1" ] && TARGETS+=("$1"); }
30
+ add "$ROOT/install/templates/claude-hooks.json"
31
+ add "$ROOT/install/templates/copilot-instructions.md"
32
+ add "$ROOT/pipeline/preferences-template.json"
33
+ for f in "$ROOT"/pipeline/agents/*.md; do add "$f"; done
34
+ for f in "$ROOT"/pipeline/skills/figma-common/figma-setup/scripts/*.py \
35
+ "$ROOT"/pipeline/skills/figma-ios/figma-to-component/scripts/*.py; do add "$f"; done
36
+ add "$ROOT/pipeline/scripts/agent-guard.sh"
37
+ add "$ROOT/pipeline/scripts/pre-commit-check.sh"
38
+
39
+ rel() { echo "${1#$ROOT/}"; }
40
+
41
+ echo "→ scanning ${#TARGETS[@]} shipped config files"
42
+
43
+ for f in "${TARGETS[@]}"; do
44
+ [ -f "$f" ] || continue
45
+ r="$(rel "$f")"
46
+
47
+ # --- HIGH: hardcoded secrets (high-signal prefixes only; report file, not value)
48
+ if grep -EqI '(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{60,}|xox[baprs]-[A-Za-z0-9-]{12,}|sk_live_[A-Za-z0-9]{20,}|AIza[0-9A-Za-z_-]{35}|npm_[A-Za-z0-9]{36}|glpat-[A-Za-z0-9_-]{20,}|AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----' "$f" 2>/dev/null; then
49
+ high "hardcoded secret / token in $r"
50
+ fi
51
+ if grep -Eq '"type"[[:space:]]*:[[:space:]]*"service_account"' "$f" 2>/dev/null; then
52
+ high "embedded service-account JSON in $r"
53
+ fi
54
+
55
+ # --- HIGH: permission bypass baked into a shipped template
56
+ if grep -Eq 'skipDangerousModePermissionPrompt"?[[:space:]]*:[[:space:]]*true|--dangerously-skip-permissions|"permissions"[[:space:]]*:[[:space:]]*"?(allow-all|bypass)' "$f" 2>/dev/null; then
57
+ high "permission bypass flag in shipped template: $r"
58
+ fi
59
+
60
+ # --- MEDIUM: blanket Bash allow
61
+ if grep -Eq '"Bash\(\*\)"|"Bash\(:\*\)"|Bash\(\*:\*\)' "$f" 2>/dev/null; then
62
+ med "blanket Bash(*) permission in $r"
63
+ fi
64
+
65
+ # --- MEDIUM: remote-install supply-chain patterns
66
+ if grep -Eq 'curl[^|]*\|[[:space:]]*(sudo[[:space:]]+)?(ba)?sh|wget[^|]*\|[[:space:]]*(ba)?sh' "$f" 2>/dev/null; then
67
+ med "pipe-to-shell remote install (curl|bash) in $r"
68
+ fi
69
+ if grep -Eq '(^|[^A-Za-z])npx[[:space:]]+-y[[:space:]]|npx[[:space:]]+--yes[[:space:]]' "$f" 2>/dev/null; then
70
+ med "unpinned 'npx -y' auto-install in $r"
71
+ fi
72
+ done
73
+
74
+ # --- HIGH: hook commands must only invoke vetted scripts, never inline eval -----
75
+ HOOKS="$ROOT/install/templates/claude-hooks.json"
76
+ if [ -f "$HOOKS" ] && command -v python3 >/dev/null 2>&1; then
77
+ bad="$(python3 - "$HOOKS" <<'PY' 2>/dev/null || true
78
+ import json, re, sys
79
+ try:
80
+ t = json.load(open(sys.argv[1]))
81
+ except Exception:
82
+ sys.exit(0)
83
+ bad = []
84
+ for _event, entries in (t.get("hooks") or {}).items():
85
+ for e in entries:
86
+ for h in e.get("hooks", []):
87
+ c = h.get("command", "")
88
+ # allow only: `bash $HOME/.claude/scripts/<name>` (our vetted scripts)
89
+ if not re.fullmatch(r'bash \$HOME/\.claude/scripts/[A-Za-z0-9._-]+', c.strip()):
90
+ bad.append(c)
91
+ if re.search(r'\beval\b|\$\(|`', c):
92
+ bad.append(c)
93
+ print("\n".join(bad))
94
+ PY
95
+ )"
96
+ if [ -n "$bad" ]; then
97
+ high "hooks template has a non-vetted or eval-bearing hook command"
98
+ fi
99
+ fi
100
+
101
+ echo ""
102
+ echo "══ config-hygiene: $HIGH high, $MED medium ══"
103
+ if [ "$HIGH" -gt 0 ]; then
104
+ echo "FAIL: high-severity config findings must be fixed before release."
105
+ exit 1
106
+ fi
107
+ exit 0
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-agent-guard.sh - behavioral contract for the agent-guard PreToolUse hook.
3
+ #
4
+ # Verifies the guard blocks (exit 2) AI-attribution commits and force-push to a
5
+ # protected branch, allows (exit 0) everything else, fails open on bad input, and
6
+ # NEVER executes the inspected command (injection safety).
7
+ #
8
+ # Runs the guard from the repo copy in a throwaway git repo whose branch we set,
9
+ # so the "bare push on a protected branch" path is exercised deterministically.
10
+ #
11
+ # Exit 0 = all pass, 1 = any failure.
12
+
13
+ set -uo pipefail
14
+
15
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
16
+ GUARD="$ROOT/pipeline/scripts/agent-guard.sh"
17
+ command -v python3 >/dev/null 2>&1 || { echo "error: python3 required" >&2; exit 127; }
18
+ [ -f "$GUARD" ] || { echo "FAIL: agent-guard.sh missing at $GUARD" >&2; exit 1; }
19
+ [ -f "$ROOT/pipeline/scripts/agent-guard.py" ] || { echo "FAIL: agent-guard.py missing" >&2; exit 1; }
20
+
21
+ PASS=0; FAIL=0
22
+ pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
23
+ fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
24
+
25
+ # throwaway repo on a protected branch so `git rev-parse` inside the guard = main
26
+ WORK="$(mktemp -d)"
27
+ trap 'rm -rf "$WORK"' EXIT
28
+ # init on main WITH a commit so `git rev-parse --abbrev-ref HEAD` resolves to main
29
+ # (an unborn branch reports HEAD, which would not exercise the bare-push path)
30
+ ( cd "$WORK" \
31
+ && git init -q \
32
+ && git symbolic-ref HEAD refs/heads/main \
33
+ && git -c user.email=t@t -c user.name=t commit -q --allow-empty -m init ) 2>/dev/null
34
+
35
+ J() { python3 -c 'import json,sys; print(json.dumps({"tool_input":{"command":sys.argv[1]}}))' "$1"; }
36
+ run() { # label expected-exit command-string
37
+ local got
38
+ ( cd "$WORK" && printf '%s' "$(J "$3")" | bash "$GUARD" >/dev/null 2>&1 )
39
+ got=$?
40
+ if [ "$got" = "$2" ]; then pass "[$got] $1"; else fail "expected $2 got $got: $1"; fi
41
+ }
42
+
43
+ echo "→ blocks (exit 2)"
44
+ run "commit Co-Authored-By: Claude" 2 'git commit -m "x
45
+
46
+ Co-Authored-By: Claude <noreply@anthropic.com>"'
47
+ run "commit robot-emoji" 2 'git commit -m "x
48
+
49
+ 🤖 Generated with Claude Code"'
50
+ run "push --force origin main" 2 'git push --force origin main'
51
+ run "push -f origin develop" 2 'git push -f origin develop'
52
+ run "push --force-with-lease master" 2 'git push --force-with-lease origin master'
53
+ run "bare push -f on main" 2 'git push -f'
54
+
55
+ echo "→ allows (exit 0)"
56
+ run "normal commit" 0 'git commit -m "feat: normal"'
57
+ run "push no force" 0 'git push origin main'
58
+ run "force push feature branch" 0 'git push --force origin feature/DEMO-1'
59
+ run "force push tag" 0 'git push -f origin v1.2.3'
60
+ run "non-git command" 0 'ls -la /tmp'
61
+ run "empty command" 0 ''
62
+
63
+ echo "→ fail-open on bad input"
64
+ ( cd "$WORK" && printf 'not-json{' | bash "$GUARD" >/dev/null 2>&1 ); [ $? -eq 0 ] && pass "malformed json -> allow" || fail "malformed json blocked"
65
+ ( cd "$WORK" && printf '' | bash "$GUARD" >/dev/null 2>&1 ); [ $? -eq 0 ] && pass "empty stdin -> allow" || fail "empty stdin blocked"
66
+
67
+ echo "→ injection safety (inspected command must NOT execute)"
68
+ SENT="$WORK/sentinel"; : > "$SENT"
69
+ ( cd "$WORK" && printf '%s' "$(J "git commit -m \"\$(rm -f '$SENT')\"")" | bash "$GUARD" >/dev/null 2>&1 )
70
+ [ -f "$SENT" ] && pass "sentinel survived (no execution)" || fail "sentinel deleted - command executed"
71
+
72
+ echo ""
73
+ echo "══ agent-guard smoke: $PASS passed, $FAIL failed ══"
74
+ [ "$FAIL" -eq 0 ]
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # smoke-autopilot-circuit-breaker.sh - wiring contract for the autopilot
3
+ # circuit-breaker feature: the doc exists, enumerates the five trip conditions,
4
+ # and is referenced from the modes "what never skips" list.
5
+ #
6
+ # Exit 0 = all pass, 1 = any failure.
7
+
8
+ set -uo pipefail
9
+
10
+ ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
11
+ DOC="$ROOT/pipeline/commands/multi-agent/refs/features/autopilot-circuit-breaker.md"
12
+ MODES="$ROOT/pipeline/commands/multi-agent/refs/phases/modes.md"
13
+
14
+ PASS=0; FAIL=0
15
+ pass() { PASS=$((PASS+1)); echo " ✓ $1"; }
16
+ fail() { FAIL=$((FAIL+1)); echo " ✗ $1"; }
17
+
18
+ [ -f "$DOC" ] && pass "feature doc exists" || fail "feature doc missing"
19
+ [ -f "$MODES" ] && pass "modes doc exists" || fail "modes doc missing"
20
+
21
+ echo "→ five trip conditions documented"
22
+ for t in "No-progress stall" "Identical repeated failure" "Rework storm" "Cost drift" "Merge/rebase conflict"; do
23
+ grep -Fq "$t" "$DOC" && pass "trigger: $t" || fail "trigger missing: $t"
24
+ done
25
+
26
+ echo "→ action + safety invariants"
27
+ grep -Fq "resume" "$DOC" && pass "documents resume path" || fail "no resume path"
28
+ grep -Eiq "never auto-resolve|does not paper over|no force-anything" "$DOC" && pass "no-self-heal invariant present" || fail "missing no-self-heal invariant"
29
+
30
+ echo "→ wired into modes 'what never skips'"
31
+ grep -Fq "autopilot-circuit-breaker.md" "$MODES" && pass "modes references the feature doc" || fail "modes does not reference the feature"
32
+ grep -Fiq "Circuit-breaker" "$MODES" && pass "modes lists circuit-breaker under never-skip" || fail "modes missing circuit-breaker bullet"
33
+
34
+ echo ""
35
+ echo "══ autopilot-circuit-breaker smoke: $PASS passed, $FAIL failed ══"
36
+ [ "$FAIL" -eq 0 ]