forgedock 1.1.1 → 1.1.3

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 (174) hide show
  1. package/README.md +53 -8
  2. package/bin/batch-runner.mjs +339 -0
  3. package/bin/demo.mjs +92 -7
  4. package/bin/engine/phases.mjs +24 -2
  5. package/bin/engine/state.mjs +8 -3
  6. package/bin/engine-cli.mjs +177 -5
  7. package/bin/forgedock.mjs +409 -26
  8. package/bin/hooks/interactive-engine.mjs +509 -0
  9. package/bin/hooks/pre-tool-use.mjs +498 -0
  10. package/bin/hooks/subagent-stop-enforce.mjs +197 -0
  11. package/bin/journey.mjs +72 -14
  12. package/bin/registry.mjs +139 -0
  13. package/bin/report.mjs +447 -0
  14. package/bin/runner.mjs +70 -20
  15. package/bin/settings-hook.mjs +235 -1
  16. package/commands/adopt.md +4 -1
  17. package/commands/analytics.md +4 -1
  18. package/commands/audit-agents.md +110 -27
  19. package/commands/audit.md +20 -1
  20. package/commands/autopilot.md +518 -361
  21. package/commands/changelog.md +2 -1
  22. package/commands/ci-audit.md +2 -1
  23. package/commands/cleanup.md +161 -1
  24. package/commands/compat-audit.md +2 -1
  25. package/commands/deploy-info.md +58 -1
  26. package/commands/deploy-pr.md +485 -0
  27. package/commands/design-bench.md +5 -1
  28. package/commands/design-render-critique-loop.md +1 -1
  29. package/commands/diagnose.md +2 -1
  30. package/commands/explain.md +2 -1
  31. package/commands/fix-ci.md +577 -0
  32. package/commands/forge-stats.md +121 -2
  33. package/commands/forgedock-init.md +1 -1
  34. package/commands/geo-audit.md +3 -0
  35. package/commands/incident-response.md +2 -1
  36. package/commands/issue.md +23 -2
  37. package/commands/milestone.md +77 -12
  38. package/commands/optimize.md +2 -1
  39. package/commands/orchestrate/config.md +67 -0
  40. package/commands/orchestrate/phase-1-resolve.md +159 -0
  41. package/commands/orchestrate/phase-2-triage.md +126 -0
  42. package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
  43. package/commands/orchestrate/phase-3-dependency.md +591 -0
  44. package/commands/orchestrate/phase-4-execution.md +962 -0
  45. package/commands/orchestrate/phase-5-cleanup.md +47 -0
  46. package/commands/orchestrate/phase-6-report.md +198 -0
  47. package/commands/orchestrate/safety.md +62 -0
  48. package/commands/orchestrate.md +25 -2045
  49. package/commands/pipeline-health/config.md +34 -0
  50. package/commands/pipeline-health/phase-1-context.md +180 -0
  51. package/commands/pipeline-health/phase-2-metrics.md +1729 -0
  52. package/commands/pipeline-health/phase-3-analyze.md +204 -0
  53. package/commands/pipeline-health/phase-4-proposals.md +117 -0
  54. package/commands/pipeline-health/phase-5-report.md +284 -0
  55. package/commands/pipeline-health/phase-6-summary.md +52 -0
  56. package/commands/pipeline-health.md +25 -2401
  57. package/commands/pipeline-resume.md +24 -1
  58. package/commands/pipeline-status.md +65 -0
  59. package/commands/qa-sweep.md +4 -1
  60. package/commands/quality-gate.md +248 -10
  61. package/commands/recover-orphans.md +576 -0
  62. package/commands/replay.md +2 -1
  63. package/commands/review-pr-agents/api.md +135 -0
  64. package/commands/review-pr-agents/auth.md +137 -0
  65. package/commands/review-pr-agents/billing.md +101 -0
  66. package/commands/review-pr-agents/concurrency.md +145 -0
  67. package/commands/review-pr-agents/database.md +103 -0
  68. package/commands/review-pr-agents/frontend.md +89 -0
  69. package/commands/review-pr-agents/infra.md +385 -0
  70. package/commands/review-pr-agents/protocols.md +185 -0
  71. package/commands/review-pr-agents/scraper.md +185 -0
  72. package/commands/review-pr-agents/security.md +406 -0
  73. package/commands/review-pr-agents.md +45 -1722
  74. package/commands/review-pr-staging.md +196 -40
  75. package/commands/review-pr.md +591 -52
  76. package/commands/rollback.md +2 -1
  77. package/commands/scope.md +2 -1
  78. package/commands/security-audit.md +2 -1
  79. package/commands/signal-planner.md +623 -0
  80. package/commands/sync-ecosystem.md +1 -0
  81. package/commands/test-gate.md +17 -1
  82. package/commands/validate.md +2 -1
  83. package/commands/work-on/build/architect.md +28 -0
  84. package/commands/work-on/build/context.md +2 -0
  85. package/commands/work-on/build/implement.md +3 -1
  86. package/commands/work-on/build/validate.md +26 -7
  87. package/commands/work-on/build.md +163 -24
  88. package/commands/work-on/close.md +130 -3
  89. package/commands/work-on/decompose.md +19 -1
  90. package/commands/work-on/investigate.md +117 -2
  91. package/commands/work-on/review.md +24 -4
  92. package/commands/work-on-monolithic.md +7 -2
  93. package/commands/work-on.md +202 -4
  94. package/docs/BRAND.md +14 -7
  95. package/docs/CI.md +16 -2
  96. package/docs/CODEX.md +36 -1
  97. package/docs/CONFIG.md +5 -8
  98. package/docs/FORGE-PROTOCOL.md +15 -1064
  99. package/docs/anthropic-devrel-outreach.md +2 -2
  100. package/docs/articles/command-decomposition-token-savings.md +214 -0
  101. package/docs/articles/model-release-playbook.md +131 -0
  102. package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
  103. package/docs/claude-breakpoints.json +75 -5
  104. package/docs/design/abc-benchmark.md +1 -1
  105. package/docs/design/design-spec-schema.md +1 -1
  106. package/docs/design/divergent-generation.md +1 -1
  107. package/docs/design/eval-gate-runbook.md +178 -0
  108. package/docs/design/show-hn-draft.md +26 -1
  109. package/docs/design/token-cost-baseline-1298.md +59 -0
  110. package/docs/eval/README.md +120 -0
  111. package/docs/eval/corpus/seed-v1.json +6 -0
  112. package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
  113. package/docs/site/annotations-explained.md +24 -0
  114. package/docs/site/command-learning-path.md +51 -14
  115. package/docs/site/command-reference.md +386 -111
  116. package/docs/site/for-companies.md +95 -0
  117. package/docs/site/forge-annotation-protocol.md +12 -256
  118. package/docs/site/getting-started.md +43 -4
  119. package/docs/site/github-is-the-memory.md +113 -0
  120. package/docs/site/how-it-works.md +3 -13
  121. package/docs/site/index.md +9 -1
  122. package/docs/spec/eval-run-result.md +147 -0
  123. package/docs/spec/forge-protocol-v1.md +441 -18
  124. package/docs/spec/label-state-machine.md +55 -0
  125. package/docs/spec/review-protocol.md +158 -0
  126. package/docs/spec-graph-schema.md +28 -0
  127. package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
  128. package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
  129. package/examples/forgedock-demo/README.md +41 -22
  130. package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
  131. package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
  132. package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
  133. package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
  134. package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
  135. package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
  136. package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
  137. package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
  138. package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
  139. package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
  140. package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
  141. package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
  142. package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
  143. package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
  144. package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
  145. package/examples/forgedock-demo/scripts/smoke.js +18 -1
  146. package/examples/forgedock-demo/src/db.js +36 -11
  147. package/examples/forgedock-demo/src/routes/notes.js +106 -9
  148. package/examples/forgedock-demo/src/server.js +4 -1
  149. package/package.json +3 -1
  150. package/packages/protocol/LICENSE +21 -0
  151. package/packages/protocol/src/cli.js +135 -0
  152. package/packages/protocol/src/emit.js +146 -0
  153. package/packages/protocol/src/index.js +43 -0
  154. package/packages/protocol/src/parse.js +254 -0
  155. package/packages/protocol/src/types.js +171 -0
  156. package/packages/protocol/src/validate.js +122 -0
  157. package/scripts/bench-topology-cost.mjs +372 -0
  158. package/scripts/check-command-docs-drift.sh +198 -0
  159. package/scripts/check-protocol-restatements.sh +176 -0
  160. package/scripts/check-registry/README.md +72 -0
  161. package/scripts/check-registry/manifest.json +6 -0
  162. package/scripts/code-index.sh +454 -0
  163. package/scripts/conformance-check.mjs +168 -0
  164. package/scripts/doctor-pipeline-state.sh +484 -0
  165. package/scripts/eval-gate-baseline.json +17 -0
  166. package/scripts/eval-gate-scorecard.mjs +242 -0
  167. package/scripts/eval-scorecard-aggregator.mjs +252 -0
  168. package/scripts/flaky-quarantine.sh +241 -0
  169. package/scripts/forge-annotation.sh +567 -0
  170. package/scripts/issue-dedup.sh +236 -0
  171. package/scripts/validate-annotation-node.mjs +233 -0
  172. package/scripts/worktree-lifecycle.sh +3 -3
  173. package/templates/devdocs/agent/using-forgedock.md +14 -0
  174. package/docs/site/.vitepress/config.mjs +0 -85
@@ -1,31 +1,33 @@
1
1
  ---
2
- description: Autonomous platform improvement cycle — recon, triage, fix, report. Runs recon+triage by default; pass --fix to also pick up and fix top issues. Human gates all deploys.
3
- argument-hint: [--fix | --recon-only | --fix --limit 5 | --dry-run]
2
+ description: Autonomous deploy loop — runs until zero open issues remain. Detects pipeline state and resumes from any position. Fully autonomous after invocation.
3
+ argument-hint: [--dry-run | --recon-only]
4
+ install: extras
4
5
  ---
6
+ <!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
7
+ <!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
5
8
 
6
- # /autopilot — Recursive Platform Improvement Cycle
9
+ # /autopilot — Autonomous Deploy Loop
7
10
 
8
- **Input**: $ARGUMENTS (default: recon + triage + report, no fixing)
11
+ **Input**: $ARGUMENTS (default: full autonomous loop until zero open issues remain)
9
12
 
10
13
  **Config variables used by this command** (set in `forge.yaml`):
11
14
  - `{CREDENTIALS_FILE}` ← `paths.credentials.file` (optional) — path to credentials YAML for analytics APIs
12
- - `{SERVER_SSH}` ← `services.server_ssh` (optional) — SSH target for production server health checks (e.g., `ubuntu@1.2.3.4`)
13
- - `{OPS_INBOX_PATH}` ← `services.ops_inbox_path` (optional) — path on production server to a directory of open work-item files (e.g. `/srv/ops/inbox`)
14
- - `{BILLING_ENABLED}` ← `billing.enabled` (optional, default `false`) — set to `true` to enable Stripe data collection in the Analytics Snapshot agent
15
+ - `{SERVER_SSH}` ← `services.server_ssh` (optional) — SSH target for production server health checks
16
+ - `{OPS_INBOX_PATH}` ← `services.ops_inbox_path` (optional) — path on production server to ops work-item files
17
+ - `{BILLING_ENABLED}` ← `billing.enabled` (optional, default `false`) — set to `true` to enable Stripe data in Analytics Snapshot
15
18
 
16
- Resolve `{BILLING_ENABLED}` from `forge.yaml` before executing any phase:
19
+ **NEVER use plan mode (EnterPlanMode).**
20
+ **NEVER use the Agent tool** — autopilot dispatches all work via `Skill(...)` calls only. The Agent tool bypasses the Skill pipeline's label state machine, investigation comments, and structured review — leaving no audit trail.
17
21
 
18
- ```bash
19
- BILLING_ENABLED=$(yq '.billing.enabled // false' "$(git rev-parse --show-toplevel)/forge.yaml" 2>/dev/null || echo 'false')
20
- ```
22
+ <!-- FORGE:SPEC_LOADED — autopilot.md loaded and active. Agent is bound by this spec. -->
21
23
 
22
- You are an autonomous improvement engine for this project. Your job is to **find what's wrong, create trackable issues, and optionally fix the highest-impact ones** — all in a single cycle. Every cycle leaves the platform measurably better than before.
24
+ You are a fully autonomous deploy loop for this project. Your job is to **detect the current pipeline state, work through all open issues from highest to lowest priority, and deploy everything — without stopping for user confirmation**. Invoking `/autopilot` is the authorization to run to completion.
23
25
 
24
- **This is designed to run repeatedly.** Each cycle builds on the last — issues created in cycle N get fixed in cycle N+1. The platform compounds improvements over time.
26
+ **This command overrides the standard "never merge to main" rule.** `/autopilot` IS the authorized deploy system. It ships staging→main and milestone→staging→main as part of normal operation.
25
27
 
26
- **NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
28
+ **This command resumes from wherever the pipeline is stuck.** It always reads current GitHub state before taking any action.
27
29
 
28
- **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`. User can override with `--model <name>`. Pass the resolved model in every `Agent`/`Task` tool call. Each agent prompt is scoped to a specific data source or issue — the model executes the explicit steps without needing broad inference.
30
+ **Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`.
29
31
 
30
32
  ---
31
33
 
@@ -33,387 +35,613 @@ You are an autonomous improvement engine for this project. Your job is to **find
33
35
 
34
36
  | Flag | Effect |
35
37
  |------|--------|
36
- | (none) | Phases 1-3 only (recon, triage, report) |
37
- | `--fix` | Also run Phase 4 (pick top issues, fix via /work-on) |
38
- | `--fix --limit N` | Fix at most N issues (default: 3) |
39
- | `--recon-only` | Phase 1 only (data collection, no issue creation) |
40
- | `--dry-run` | Run everything but don't create issues or PRs — just report what would happen |
38
+ | (none) | Full autonomous loop — state detection → recon → fast lane → milestone → report |
39
+ | `--dry-run` | Run all phases but do NOT create issues, merge PRs, or modify state — report only |
40
+ | `--recon-only` | Phase 0 (state detection) and Phase 1 (recon) only — no loop execution |
41
41
 
42
- Parse `$ARGUMENTS` and set these variables:
43
- ```
44
- MODE = "full" | "recon-only" | "fix" # derived from flags
45
- DO_FIX = true if --fix present
46
- FIX_LIMIT = N from --limit N, default 3
47
- DRY_RUN = true if --dry-run present
42
+ Parse `$ARGUMENTS` and set:
43
+ ```bash
44
+ DRY_RUN=false
45
+ RECON_ONLY=false
46
+
47
+ for arg in $ARGUMENTS; do
48
+ case "$arg" in
49
+ --dry-run) DRY_RUN=true ;;
50
+ --recon-only) RECON_ONLY=true ;;
51
+ esac
52
+ done
48
53
  ```
49
54
 
50
55
  ---
51
56
 
52
- ## Phase 0: Cycle Context
53
-
54
- **Goal**: Understand what happened since the last cycle.
57
+ ## Config Preamble (MANDATORY — run before any phase)
55
58
 
56
- ### 0A: Timestamp & baseline
59
+ Read all `forge.yaml` config variables before any logic runs:
57
60
 
58
61
  ```bash
59
- echo "=== Autopilot Cycle: $(date -u +%Y-%m-%dT%H:%M:%SZ) ==="
62
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
63
+ if [ ! -f "$CONFIG_FILE" ]; then
64
+ echo "ERROR: forge.yaml not found. Run: npx forgedock init"
65
+ exit 1
66
+ fi
60
67
 
61
- # Portable ISO-8601 date arithmetic using python3 (works on Windows, macOS, Linux).
62
- # Replaces GNU-only `date -u -d 'N days ago'` which fails on Windows and macOS.
63
- DATE_3D_AGO=$(python3 -c "from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=3)).strftime('%Y-%m-%dT%H:%M:%SZ'))")
64
- DATE_7D_AGO=$(python3 -c "from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=7)).strftime('%Y-%m-%dT%H:%M:%SZ'))")
65
- DATE_1D_AGO=$(python3 -c "from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%SZ'))")
68
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' "$CONFIG_FILE" 2>/dev/null)
69
+ GH_FLAG="-R $GH_REPO"
70
+ REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE" 2>/dev/null || git rev-parse --show-toplevel)
71
+ STAGING_BRANCH=$(yq '.branches.staging // "staging"' "$CONFIG_FILE" 2>/dev/null || echo "staging")
72
+ DEFAULT_BRANCH=$(yq '.branches.default // "main"' "$CONFIG_FILE" 2>/dev/null || echo "main")
66
73
 
67
- # Recent closed issues (last 3 days) — what was fixed recently?
68
- gh issue list --state closed --json number,title,labels,closedAt \
69
- --jq '[.[] | select(.closedAt > "'"$DATE_3D_AGO"'")] | length' 2>/dev/null || echo "0"
74
+ # Optional config — gracefully absent
75
+ CREDENTIALS_FILE=$(yq '.paths.credentials.file // ""' "$CONFIG_FILE" 2>/dev/null || echo '')
76
+ SERVER_SSH=$(yq '.services.server_ssh // ""' "$CONFIG_FILE" 2>/dev/null || echo '')
77
+ OPS_INBOX_PATH=$(yq '.services.ops_inbox_path // ""' "$CONFIG_FILE" 2>/dev/null || echo '')
78
+ BILLING_ENABLED=$(yq '.billing.enabled // false' "$CONFIG_FILE" 2>/dev/null || echo 'false')
70
79
 
71
- # Open issue count by priority
72
- gh issue list --state open --label "priority:P0" --json number --jq 'length'
73
- gh issue list --state open --label "priority:P1" --json number --jq 'length'
74
- gh issue list --state open --label "priority:P2" --json number --jq 'length'
80
+ echo "Autopilot: repo=$GH_REPO staging=$STAGING_BRANCH default=$DEFAULT_BRANCH dry_run=$DRY_RUN recon_only=$RECON_ONLY"
81
+ ```
75
82
 
76
- # Stale issues (open, no workflow label, older than 7 days)
77
- gh issue list --state open --limit 200 --json number,title,labels,createdAt \
78
- --jq '[.[] | select(
79
- (.labels | map(.name) | any(startswith("workflow:")) | not) and
80
- (.createdAt < "'"$DATE_7D_AGO"'")
81
- )] | length'
83
+ ---
82
84
 
83
- # Failed CI runs in last 24h
84
- gh run list --limit 30 --json conclusion,createdAt \
85
- --jq '[.[] | select(.conclusion == "failure" and .createdAt > "'"$DATE_1D_AGO"'")] | length'
86
- ```
85
+ ## Phase 0: State Detection
87
86
 
88
- Store these as `BASELINE` metrics for the cycle report.
87
+ **Goal**: Read current GitHub state and determine where the pipeline is. Always start here — never assume clean state.
89
88
 
90
- ### 0B: Check for open P0s
89
+ ### 0A: Detect open staging→main PR
91
90
 
92
91
  ```bash
93
- gh issue list --state open --label "priority:P0" --json number,title --jq '.[] | "#\(.number) \(.title)"'
94
- ```
92
+ STAGING_TO_MAIN_PR=$(gh pr list $GH_FLAG \
93
+ --head "$STAGING_BRANCH" \
94
+ --base "$DEFAULT_BRANCH" \
95
+ --state open \
96
+ --json number,title,headRefOid \
97
+ --jq '.[0] // empty' 2>/dev/null)
95
98
 
96
- If any P0 issues exist:
97
- - **STOP the normal cycle**
98
- - Print: `P0 issue(s) open — autopilot prioritizing these above all else`
99
- - Set `FIX_TARGETS` to the P0 issue numbers
100
- - Skip to Phase 4 (fix mode, regardless of flags)
101
-
102
- ---
99
+ if [ -n "$STAGING_TO_MAIN_PR" ]; then
100
+ STAGING_PR_NUMBER=$(echo "$STAGING_TO_MAIN_PR" | jq -r '.number')
101
+ echo "STATE: Open staging→main PR #$STAGING_PR_NUMBER detected"
102
+ else
103
+ STAGING_PR_NUMBER=""
104
+ echo "STATE: No open staging→main PR"
105
+ fi
106
+ ```
103
107
 
104
- ## Phase 1: Recon (Parallel Data Collection)
108
+ ### 0B: Detect open milestone→staging PRs
105
109
 
106
- **Goal**: Pull signals from every available source. Launch ALL collectors simultaneously.
110
+ ```bash
111
+ MILESTONE_PRS=$(gh pr list $GH_FLAG \
112
+ --base "$STAGING_BRANCH" \
113
+ --state open \
114
+ --json number,title,headRefName \
115
+ --jq '[.[] | select(.headRefName | startswith("milestone/"))]' 2>/dev/null || echo '[]')
107
116
 
108
- ### 1A: Launch parallel recon agents
117
+ MILESTONE_PR_COUNT=$(echo "$MILESTONE_PRS" | jq 'length' 2>/dev/null || echo '0')
118
+ echo "STATE: $MILESTONE_PR_COUNT open milestone→staging PR(s)"
119
+ ```
109
120
 
110
- Spawn these as **background agents** (all `run_in_background=true`, all `model="sonnet"`):
121
+ ### 0C: Detect in-flight issues (stuck in intermediate workflow states)
111
122
 
112
- **Agent: Production Health**
113
- ```
114
- Check production system health:
115
- 1. SSH to check for open ops-inbox items: if {SERVER_SSH} is configured, run: ssh {SERVER_SSH} "ls {OPS_INBOX_PATH}/*-open-* 2>/dev/null" — skip this step if SERVER_SSH or OPS_INBOX_PATH is not set in forge.yaml
116
- 2. If any exist, cat each one and summarize
117
- 3. Check container health via MCP: get_production_status, run_production_health_check
118
- 4. Check recent error logs: get_production_logs for api and worker (last 100 lines), grep for ERROR/CRITICAL
119
- 5. Return: memo summaries, unhealthy containers, error patterns with counts
123
+ ```bash
124
+ INFLIGHT_ISSUES=$(gh issue list $GH_FLAG \
125
+ --state open \
126
+ --limit 200 \
127
+ --json number,title,labels \
128
+ --jq '[.[] | select(.labels | map(.name) | any(. == "workflow:building" or . == "workflow:in-review"))] | length' \
129
+ 2>/dev/null || echo '0')
130
+ echo "STATE: $INFLIGHT_ISSUES in-flight issue(s) (workflow:building or workflow:in-review)"
120
131
  ```
121
132
 
122
- **Agent: Scraping Intelligence**
123
- ```
124
- Pull scraping performance data:
125
- 1. Read scrape_diagnostics summary from admin API or production logs
126
- 2. Check Redis for tier feedback stats: tier success/fail rates across domains
127
- 3. Look for patterns: domains with >50% failure rate, tiers with degraded performance
128
- 4. Return: top failing domains (count + tier), tier pass rates, any new anti-bot patterns
129
- ```
133
+ ### 0D: Detect staging vs main delta
130
134
 
131
- **Agent: CI/CD Health**
132
- ```
133
- Check CI pipeline health:
134
- 1. gh run list --limit 30 — count failures by workflow name
135
- 2. For each failed run: gh run view {id} --json jobs — identify which job failed
136
- 3. Check if ecosystem-sync is consistently failing (known issue vs new)
137
- 4. Return: failure rate, recurring failures, new failures
135
+ ```bash
136
+ git fetch origin "$STAGING_BRANCH" "$DEFAULT_BRANCH" 2>/dev/null || true
137
+ STAGING_AHEAD=$(git rev-list --count "origin/${DEFAULT_BRANCH}..origin/${STAGING_BRANCH}" 2>/dev/null || echo '0')
138
+ echo "STATE: staging is $STAGING_AHEAD commit(s) ahead of $DEFAULT_BRANCH"
138
139
  ```
139
140
 
140
- **Agent: Issue Backlog Health**
141
- ```
142
- Analyze GitHub issue backlog:
143
- 1. gh issue list --state open --limit 200 — full list with labels, milestones, dates
144
- 2. Count by: priority (P0-P3), type (bug/feature/etc), milestone, age
145
- 3. Find stale issues: open > 14 days, no workflow label, no recent comments
146
- 4. Find orphaned issues: workflow:in-review but no open PR
147
- 5. Find blocked issues: depends on another open issue
148
- 6. Return: backlog summary, stale issues list, orphans, blockers
149
- ```
141
+ ### 0E: Count open unmilestoned issues
150
142
 
151
- **Agent: Analytics Snapshot** (lightweight — not the full /analytics audit)
152
- ```
153
- Quick analytics pulse — just the key metrics, not a full audit:
154
- 1. Read credentials from {CREDENTIALS_FILE} (set via paths.credentials.file in forge.yaml)
155
- 2. GSC: mcp__gsc__search_analytics for last 7 days — total clicks, impressions, avg position
156
- 3. Stripe (only if {BILLING_ENABLED} is true): mcp__stripe__retrieve_balance — current balance.
157
- Skip this step entirely if {BILLING_ENABLED} is false — do NOT call any Stripe MCP tools.
158
- 4. Return: clicks trend (up/down), revenue (or "N/A — billing.enabled: false" if skipped), notable changes
143
+ ```bash
144
+ OPEN_FAST_LANE=$(gh issue list $GH_FLAG \
145
+ --state open \
146
+ --limit 200 \
147
+ --json number,title,milestone,labels \
148
+ --jq '[.[] | select(
149
+ .milestone == null and
150
+ (.labels | map(.name) | any(. == "workflow:merged" or . == "workflow:invalid" or . == "workflow:decomposed") | not)
151
+ )] | length' \
152
+ 2>/dev/null || echo '0')
153
+ echo "STATE: $OPEN_FAST_LANE open unmilestoned issue(s) for fast lane"
159
154
  ```
160
155
 
161
- ### 1B: Collect results
156
+ ### 0F: Summarize detected state
162
157
 
163
- Wait for all agents to complete. Aggregate into a `RECON_DATA` object:
164
158
  ```
165
- RECON_DATA = {
166
- production: { memos, unhealthy_containers, error_patterns },
167
- scraping: { failing_domains, tier_rates, new_patterns },
168
- ci: { failure_rate, recurring_failures, new_failures },
169
- backlog: { summary, stale_issues, orphans, blockers },
170
- analytics: { clicks_trend, revenue, notable_changes }
171
- }
159
+ ## Current Pipeline State
160
+
161
+ | Signal | Value |
162
+ |--------|-------|
163
+ | staging→main PR open | ${STAGING_PR_NUMBER:-none} |
164
+ | milestone→staging PRs open | $MILESTONE_PR_COUNT |
165
+ | In-flight issues (stuck) | $INFLIGHT_ISSUES |
166
+ | staging commits ahead of $DEFAULT_BRANCH | $STAGING_AHEAD |
167
+ | Open fast-lane issues | $OPEN_FAST_LANE |
172
168
  ```
173
169
 
174
- If `MODE == "recon-only"`, print RECON_DATA as a formatted report and **stop here**.
170
+ **Resume logic**:
171
+ - If `INFLIGHT_ISSUES > 0` → call `/recover-orphans` first in Phase 1A before running recon
172
+ - If `STAGING_PR_NUMBER` is set → that deploy is in progress; `/deploy-pr` will detect and resume it
173
+ - Otherwise → proceed through phases in order
175
174
 
176
175
  ---
177
176
 
178
- ## Phase 2: Triage & Issue Creation
177
+ ## Phase 1: Recon
178
+
179
+ **Goal**: Surface signals that need new GitHub issues. Lightweight — CI health, issue backlog, optional analytics pulse.
179
180
 
180
- **Goal**: Convert recon signals into actionable, deduplicated GitHub issues.
181
+ If `RECON_ONLY=true`, print the recon report and **stop after this phase**.
181
182
 
182
- ### 2A: Deduplication check
183
+ ### 1A: Recover orphaned pipeline state (MANDATORY — run before recon)
183
184
 
184
- Before creating ANY issue, search for existing open issues that cover the same problem:
185
+ Orphaned issues (stuck in workflow:building or workflow:in-review without an active agent) cause loop contamination. Recover them first so the loop sees accurate open issue counts.
185
186
 
186
187
  ```bash
187
- # For each finding, search existing issues
188
- gh issue list --state open --limit 200 --json number,title,labels \
189
- --jq '.[] | "\(.number) \(.title)"'
188
+ RECOVER_ORPHANS_AVAILABLE=$(ls ~/.claude/commands/recover-orphans.md 2>/dev/null && echo "true" || echo "false")
189
+
190
+ if [ "$INFLIGHT_ISSUES" -gt 0 ]; then
191
+ if [ "$RECOVER_ORPHANS_AVAILABLE" = "true" ] && [ "$DRY_RUN" = "false" ]; then
192
+ echo "Recovering $INFLIGHT_ISSUES orphaned pipeline issue(s)..."
193
+ Skill("recover-orphans", args="")
194
+ elif [ "$DRY_RUN" = "true" ]; then
195
+ echo "[DRY-RUN] Would invoke: Skill(recover-orphans)"
196
+ else
197
+ echo "WARNING: /recover-orphans not installed — cannot auto-recover orphaned issues. Install it first."
198
+ fi
199
+ else
200
+ echo "No in-flight issues — skipping orphan recovery"
201
+ fi
190
202
  ```
191
203
 
192
- **Rule**: If an existing open issue covers the finding (even partially), DO NOT create a duplicate. Instead, add a comment to the existing issue with the new data point.
204
+ ### 1B: CI/CD Health
193
205
 
194
- ### 2B: Classify and prioritize findings
206
+ ```bash
207
+ DATE_1D_AGO=$(python3 -c "from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%SZ'))")
208
+
209
+ RECENT_FAILURES=$(gh run list $GH_FLAG --limit 30 --json conclusion,createdAt,workflowName \
210
+ --jq "[.[] | select(.conclusion == \"failure\" and .createdAt > \"$DATE_1D_AGO\")] | length" \
211
+ 2>/dev/null || echo "0")
212
+
213
+ RECENT_RUNS=$(gh run list $GH_FLAG --limit 30 --json conclusion,createdAt \
214
+ --jq "[.[] | select(.createdAt > \"$DATE_1D_AGO\")] | length" \
215
+ 2>/dev/null || echo "0")
216
+
217
+ echo "CI (24h): $RECENT_FAILURES/$RECENT_RUNS failures"
218
+
219
+ # Recurring failures (same workflow failing multiple times)
220
+ RECURRING=$(gh run list $GH_FLAG --limit 30 --json conclusion,workflowName \
221
+ --jq '[.[] | select(.conclusion == "failure")] | group_by(.workflowName) | .[] | select(length > 1) | "\(length)x \(.[0].workflowName)"' \
222
+ 2>/dev/null || echo '')
223
+ [ -n "$RECURRING" ] && echo "Recurring CI failures: $RECURRING"
224
+ ```
225
+
226
+ ### 1C: Issue Backlog Health
227
+
228
+ ```bash
229
+ DATE_14D_AGO=$(python3 -c "from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=14)).strftime('%Y-%m-%dT%H:%M:%SZ'))")
230
+
231
+ # Count by priority
232
+ P0_COUNT=$(gh issue list $GH_FLAG --state open --limit 200 --json labels \
233
+ --jq '[.[] | select(.labels | map(.name) | any(. == "P0"))] | length' 2>/dev/null || echo "0")
234
+ P1_COUNT=$(gh issue list $GH_FLAG --state open --limit 200 --json labels \
235
+ --jq '[.[] | select(.labels | map(.name) | any(. == "P1"))] | length' 2>/dev/null || echo "0")
236
+ P2_COUNT=$(gh issue list $GH_FLAG --state open --limit 200 --json labels \
237
+ --jq '[.[] | select(.labels | map(.name) | any(. == "P2"))] | length' 2>/dev/null || echo "0")
195
238
 
196
- For each finding from RECON_DATA, assign:
239
+ # Stale issues (open >14d, no workflow label, no milestone)
240
+ STALE_COUNT=$(gh issue list $GH_FLAG --state open --limit 200 --json number,labels,createdAt,milestone \
241
+ --jq "[.[] | select(
242
+ (.labels | map(.name) | any(startswith(\"workflow:\")) | not) and
243
+ (.createdAt < \"$DATE_14D_AGO\") and
244
+ .milestone == null
245
+ )] | length" 2>/dev/null || echo "0")
197
246
 
198
- | Signal | Priority | Type |
199
- |--------|----------|------|
200
- | Open ops-inbox item | P1 | bug |
201
- | Container unhealthy | P0 | bug |
202
- | Error spike (>5x normal) | P1 | bug |
203
- | Tier degradation (>20% drop) | P2 | bug |
204
- | CI consistently failing | P2 | bug |
205
- | Stale issue (>14 days) | — | (comment, don't create new issue) |
206
- | Orphaned workflow state | — | (fix via /cleanup, not new issue) |
207
- | Analytics decline (>20% week-over-week) | P2 | improvement |
208
- | New anti-bot pattern detected | P2 | feature |
247
+ echo "Backlog: P0=$P0_COUNT P1=$P1_COUNT P2=$P2_COUNT stale=$STALE_COUNT"
248
+ ```
209
249
 
210
- ### 2C: Create issues
250
+ ### 1D: Analytics Pulse (optional — forge.yaml-gated)
211
251
 
212
- For each finding that passes dedup check, create a GitHub issue:
252
+ ```bash
253
+ # Only run if CREDENTIALS_FILE is configured and the file exists
254
+ if [ -n "$CREDENTIALS_FILE" ] && [ -f "$CREDENTIALS_FILE" ]; then
255
+ echo "Analytics pulse: credentials at $CREDENTIALS_FILE — checking GSC..."
256
+ # GSC: last 7 days via mcp__gsc__search_analytics (if MCP available)
257
+ # If MCP unavailable, log and skip — do not block
258
+ ANALYTICS_AVAILABLE=true
259
+ else
260
+ echo "Analytics pulse: SKIPPED — paths.credentials.file not set in forge.yaml or file absent"
261
+ ANALYTICS_AVAILABLE=false
262
+ fi
263
+
264
+ # Stripe balance: only if billing.enabled is true AND credentials are present
265
+ if [ "$BILLING_ENABLED" = "true" ] && [ "$ANALYTICS_AVAILABLE" = "true" ]; then
266
+ echo "Billing analytics: checking Stripe balance..."
267
+ # mcp__stripe__retrieve_balance — skip gracefully if MCP unavailable
268
+ else
269
+ echo "Billing analytics: SKIPPED — billing.enabled is false (or credentials absent)"
270
+ fi
271
+ ```
272
+
273
+ ### 1E: Create issues from recon findings
274
+
275
+ For each finding (recurring CI failures, critical stale issue patterns):
276
+
277
+ **Deduplication check first** — search existing open issues before creating:
278
+ ```bash
279
+ EXISTING_ISSUES=$(gh issue list $GH_FLAG --state open --limit 200 --json number,title \
280
+ --jq '.[] | "\(.number) \(.title)"' 2>/dev/null)
281
+ ```
213
282
 
283
+ For new findings that have no existing open duplicate:
214
284
  ```bash
215
- gh issue create \
216
- --title "{fix|feat|refactor}: {concise description}" \
217
- --label "{priority},{type}" \
218
- --body "$(cat <<'ISSUE_EOF'
285
+ if [ "$DRY_RUN" = "false" ]; then
286
+ gh issue create $GH_FLAG \
287
+ --title "fix: {finding_description}" \
288
+ --label "P2,bug" \
289
+ --body "$(cat <<'ISSUE_EOF'
219
290
  ## Problem
220
291
 
221
- {Description of the finding with specific data points. What's wrong or what's missing.}
292
+ {Description of the finding with specific data points.}
222
293
 
223
294
  ## Root Cause (if known)
224
295
 
225
- {What's causing the issue — specific code path, config gap, or behavior. If unknown: "Root cause unknown — investigation needed."}
296
+ {Specific root cause or "Root cause unknown — investigation needed."}
226
297
 
227
298
  ## Affected Files
228
299
 
229
300
  Files that need changes:
230
301
  1. `{filepath}` — {what needs to change}
231
- 2. `{filepath}` — {what needs to change}
232
302
 
233
303
  ## Acceptance Criteria
234
304
 
235
305
  - [ ] {Specific, testable criterion}
236
- - [ ] No regression in {related feature}
237
306
 
238
307
  ## Context
239
308
 
240
- Found by \`/autopilot\` cycle on {DATE}.
309
+ Found by \`/autopilot\` cycle on $(date -u +%Y-%m-%dT%H:%M:%SZ).
241
310
 
242
311
  ## Evidence
243
312
 
244
- {Logs, metrics, error messages — concrete data, not speculation}
313
+ {Concrete data — log lines, failure counts, metrics}
314
+ ISSUE_EOF
315
+ )"
316
+ else
317
+ echo "[DRY-RUN] Would create issue: fix: {finding_description}"
318
+ fi
319
+ ```
245
320
 
246
- ## Suggested Approach
321
+ Store created issue numbers in `RECON_ISSUES` array for spec-edit impact analysis.
247
322
 
248
- {Brief suggestion — the /work-on investigation will validate this}
323
+ If `RECON_ONLY=true`, print recon report and **stop here**:
249
324
 
250
- ---
251
- *Created by autopilot recon cycle. Will be validated before any fix is applied.*
252
- ISSUE_EOF
253
- )"
254
325
  ```
326
+ ## Autopilot Recon Report — $(date -u +%Y-%m-%dT%H:%M:%SZ)
255
327
 
256
- **DRY_RUN check**: If `DRY_RUN == true`, print what would be created but don't actually create issues.
328
+ ### Pipeline State
329
+ - staging→main PR: ${STAGING_PR_NUMBER:-none detected}
330
+ - In-flight issues (orphans recovered): $INFLIGHT_ISSUES
331
+ - Open fast-lane issues: $OPEN_FAST_LANE
257
332
 
258
- ### 2D: Quick hygiene (inline, no separate /cleanup invocation)
333
+ ### CI/CD (24h)
334
+ - Failure rate: $RECENT_FAILURES/$RECENT_RUNS
335
+ - Recurring: ${RECURRING:-none}
259
336
 
260
- While we have the backlog data, fix obvious hygiene issues directly:
337
+ ### Issue Backlog
338
+ - Open: P0=$P0_COUNT P1=$P1_COUNT P2=$P2_COUNT
339
+ - Stale (>14d, no workflow): $STALE_COUNT
261
340
 
262
- ```bash
263
- # Close orphaned issues (merged PR but still open)
264
- for orphan in {ORPHAN_LIST}; do
265
- gh issue close $orphan --comment "Auto-closed by autopilot: linked PR was already merged."
266
- done
341
+ ### Actions Taken
342
+ - Orphans recovered: $INFLIGHT_ISSUES
343
+ - Issues created from recon: ${#RECON_ISSUES[@]}
267
344
 
268
- # Fix stale labels on closed issues
269
- for stale in {STALE_LABEL_LIST}; do
270
- gh issue edit $stale --add-label "workflow:merged" --remove-label "workflow:in-review,workflow:building" 2>/dev/null || true
271
- done
345
+ Run without --recon-only to execute the full autonomous loop.
272
346
  ```
273
347
 
274
- Store list of created issues as `CREATED_ISSUES`.
275
-
276
348
  ---
277
349
 
278
- ## Phase 3: Cycle Report
350
+ ## Phase 2: Fast Lane Loop
279
351
 
280
- **Goal**: Present findings and actions taken to the user.
352
+ **Goal**: Work through all open unmilestoned issues until zero remain. Loop until the count is 0 or a safety cap is hit.
281
353
 
282
- Print a structured report:
354
+ **Overrides "never merge to main"**: This phase deploys staging→main after each iteration. `/autopilot` is the authorized deploy system.
283
355
 
284
- ```markdown
285
- ## Autopilot Cycle Report — {DATE}
356
+ ```bash
357
+ FAST_LANE_ITERATIONS=0
358
+ MAX_FAST_LANE_ITERATIONS=20 # safety cap — prevents infinite loop on stuck state
359
+ FAST_LANE_DEPLOYS=0
360
+ FAST_LANE_FINDINGS_BOUNCED=0
286
361
 
287
- ### Production Health
288
- - Containers: {all healthy / N unhealthy}
289
- - Open ops-inbox items: {count}
290
- - Error patterns: {summary or "none"}
362
+ echo "=== Fast Lane Loop ==="
291
363
 
292
- ### Scraping Performance
293
- - Tier pass rates: T1 {X}%, T2 {X}%, T3 {X}%, T4 {X}%
294
- - Top failing domains: {list or "all healthy"}
295
- - New patterns: {any or "none detected"}
364
+ while true; do
365
+ FAST_LANE_ITERATIONS=$((FAST_LANE_ITERATIONS + 1))
296
366
 
297
- ### CI/CD
298
- - Failure rate (24h): {X}/{Y} runs
299
- - Recurring failures: {list or "none"}
367
+ if [ "$FAST_LANE_ITERATIONS" -gt "$MAX_FAST_LANE_ITERATIONS" ]; then
368
+ echo "Fast lane loop: reached max iterations ($MAX_FAST_LANE_ITERATIONS) — breaking to prevent infinite loop"
369
+ break
370
+ fi
300
371
 
301
- ### Issue Backlog
302
- - Open: {P0: N, P1: N, P2: N, P3: N}
303
- - Stale (>14d): {count}
304
- - Orphaned: {count fixed}
372
+ # Re-query open unmilestoned issues — always re-read, never trust a cached count
373
+ OPEN_UNMILESTONED=$(gh issue list $GH_FLAG \
374
+ --state open \
375
+ --limit 200 \
376
+ --json number,title,milestone,labels \
377
+ --jq '[.[] | select(
378
+ .milestone == null and
379
+ (.labels | map(.name) | any(. == "workflow:merged" or . == "workflow:invalid" or . == "workflow:decomposed") | not)
380
+ )] | length' \
381
+ 2>/dev/null || echo '0')
382
+
383
+ echo "Fast lane iteration $FAST_LANE_ITERATIONS: $OPEN_UNMILESTONED open unmilestoned issue(s)"
384
+
385
+ if [ "$OPEN_UNMILESTONED" -eq 0 ]; then
386
+ echo "Fast lane: zero open unmilestoned issues — loop complete"
387
+ break
388
+ fi
305
389
 
306
- ### Analytics Pulse
307
- - Clicks (7d): {N} ({trend})
308
- - Revenue: ${N} (or "N/A — billing.enabled: false" if Stripe step was skipped)
390
+ # Step 1: Orchestrate all open fast-lane issues in parallel via /orchestrate
391
+ echo "=== Fast Lane Iteration $FAST_LANE_ITERATIONS: Orchestrating $OPEN_UNMILESTONED issues ==="
392
+ if [ "$DRY_RUN" = "false" ]; then
393
+ Skill("orchestrate", args="fast-lane")
394
+ else
395
+ echo "[DRY-RUN] Would invoke: Skill(orchestrate, fast-lane)"
396
+ fi
309
397
 
310
- ### Actions Taken
311
- - Issues created: {count} ({list with numbers})
312
- - Issues commented: {count}
313
- - Orphans closed: {count}
314
- - Stale labels fixed: {count}
315
- - Spec-edit impact analyses logged: {count} (FORGE:AUTOPILOT_IMPACT — blast radius surfaced before spec fixes)
316
-
317
- ### Recommended Next Steps
318
- {Prioritized list of what the user should look at or deploy}
319
- ```
398
+ # Step 2: Deploy staging→main if staging has new commits
399
+ git fetch origin "$STAGING_BRANCH" "$DEFAULT_BRANCH" 2>/dev/null || true
400
+ STAGING_AHEAD_NOW=$(git rev-list --count "origin/${DEFAULT_BRANCH}..origin/${STAGING_BRANCH}" 2>/dev/null || echo '0')
401
+
402
+ if [ "$STAGING_AHEAD_NOW" -gt 0 ]; then
403
+ echo "staging is $STAGING_AHEAD_NOW commit(s) ahead — deploying via /deploy-pr..."
404
+ if [ "$DRY_RUN" = "false" ]; then
405
+ DEPLOY_RESULT=$(Skill("deploy-pr", args="staging"))
406
+ # Parse structured JSON result from deploy-pr
407
+ DEPLOY_STATUS=$(echo "$DEPLOY_RESULT" | jq -r '.status // empty' 2>/dev/null || echo '')
408
+ [ -z "$DEPLOY_STATUS" ] && DEPLOY_STATUS=$(echo "$DEPLOY_RESULT" | grep -oE '"status":"[^"]*"' | grep -oE '"[^"]*"$' | tr -d '"' || echo 'unknown')
409
+ echo "Deploy status: $DEPLOY_STATUS"
410
+ [ "$DEPLOY_STATUS" = "merged" ] && FAST_LANE_DEPLOYS=$((FAST_LANE_DEPLOYS + 1))
411
+ else
412
+ echo "[DRY-RUN] Would invoke: Skill(deploy-pr, staging)"
413
+ fi
414
+ else
415
+ echo "staging is not ahead of $DEFAULT_BRANCH — no deploy needed this iteration"
416
+ fi
320
417
 
321
- If `DO_FIX == false`, **stop here**. Print:
322
- ```
323
- Run `/autopilot --fix` to also pick up and fix the top {FIX_LIMIT} issues.
418
+ # Step 3: Recover any newly orphaned issues before next iteration
419
+ if [ "$RECOVER_ORPHANS_AVAILABLE" = "true" ] && [ "$DRY_RUN" = "false" ]; then
420
+ Skill("recover-orphans", args="--since 2")
421
+ elif [ "$DRY_RUN" = "true" ]; then
422
+ echo "[DRY-RUN] Would invoke: Skill(recover-orphans, --since 2)"
423
+ fi
424
+
425
+ # Count review findings that bounced to fast lane (unmilestoned review-finding issues)
426
+ NEW_FINDINGS=$(gh issue list $GH_FLAG \
427
+ --state open \
428
+ --limit 200 \
429
+ --json number,milestone,labels \
430
+ --jq '[.[] | select(.milestone == null and (.labels | map(.name) | any(. == "review-finding")))] | length' \
431
+ 2>/dev/null || echo '0')
432
+ [ "$NEW_FINDINGS" -gt 0 ] && FAST_LANE_FINDINGS_BOUNCED=$((FAST_LANE_FINDINGS_BOUNCED + NEW_FINDINGS))
433
+ echo "End of iteration $FAST_LANE_ITERATIONS — review findings in fast lane: $NEW_FINDINGS"
434
+ done
435
+
436
+ echo "Fast lane complete: $FAST_LANE_ITERATIONS iteration(s), $FAST_LANE_DEPLOYS deploy(s)"
324
437
  ```
325
438
 
326
439
  ---
327
440
 
328
- ## Phase 4: Fix (Optional, requires --fix flag)
441
+ ## Phase 3: Milestone Loop
329
442
 
330
- **Goal**: Pick the highest-impact fixable issues and run them through `/work-on`.
443
+ **Goal**: Ship each milestone with open issues, in order of completion percentage (highest first). Skip milestones at 0%.
331
444
 
332
- ### 4A: Select fix targets
445
+ **Each milestone cycle**: orchestrate all open issues in the milestone → ship milestone branch to staging → deploy staging to main.
333
446
 
334
- If `FIX_TARGETS` was set by Phase 0 (P0 issues), use those. Otherwise:
335
-
336
- Pick the top `FIX_LIMIT` issues by this priority:
337
- 1. P0 issues (always first)
338
- 2. P1 bugs (production-impacting)
339
- 3. Issues created by THIS cycle (freshest signal)
340
- 4. P2 bugs with `validated` or `needs-validation` label
341
- 5. Oldest P1 issues (prevent aging)
447
+ ```bash
448
+ MILESTONE_ITERATIONS=0
449
+ MILESTONE_DEPLOYS=0
450
+
451
+ echo "=== Milestone Loop ==="
452
+
453
+ # Get all open milestones with completion percentages
454
+ MILESTONES=$(gh api "repos/$GH_REPO/milestones" \
455
+ --jq '[.[] | {
456
+ number: .number,
457
+ title: .title,
458
+ slug: (.title | ascii_downcase | gsub("[^a-z0-9]+"; "-") | ltrimstr("-") | rtrimstr("-")),
459
+ open_issues: .open_issues,
460
+ closed_issues: .closed_issues,
461
+ completion_pct: (if (.open_issues + .closed_issues) > 0 then ((.closed_issues * 100) / (.open_issues + .closed_issues) | floor) else 0 end)
462
+ }] | sort_by(-.completion_pct)' \
463
+ 2>/dev/null || echo '[]')
464
+
465
+ MILESTONE_COUNT=$(echo "$MILESTONES" | jq 'length' 2>/dev/null || echo '0')
466
+ echo "Found $MILESTONE_COUNT milestone(s)"
467
+
468
+ echo "$MILESTONES" | jq -r '.[] | "\(.completion_pct)% — \(.title) (\(.open_issues) open, \(.closed_issues) closed)"'
469
+
470
+ # Process each milestone — sorted by completion% descending, skip 0%
471
+ echo "$MILESTONES" | jq -c '.[]' | while IFS= read -r milestone; do
472
+ MS_TITLE=$(echo "$milestone" | jq -r '.title')
473
+ MS_SLUG=$(echo "$milestone" | jq -r '.slug')
474
+ MS_OPEN=$(echo "$milestone" | jq -r '.open_issues')
475
+ MS_PCT=$(echo "$milestone" | jq -r '.completion_pct')
476
+
477
+ if [ "$MS_PCT" -eq 0 ]; then
478
+ echo "Skipping milestone '$MS_TITLE' (0% complete — no issues closed yet)"
479
+ continue
480
+ fi
342
481
 
343
- **Exclude** from fix targets:
344
- - Issues with `needs-human` label
345
- - Issues in milestones (feature lane — don't autopilot feature work)
346
- - Issues already in `workflow:building` or `workflow:in-review`
347
- - Issues with open dependencies
482
+ echo "=== Processing milestone: '$MS_TITLE' ($MS_PCT% complete, $MS_OPEN open) ==="
483
+
484
+ # Step 1: Orchestrate open issues in this milestone
485
+ if [ "$MS_OPEN" -gt 0 ]; then
486
+ if [ "$DRY_RUN" = "false" ]; then
487
+ Skill("orchestrate", args="milestone $MS_SLUG")
488
+ else
489
+ echo "[DRY-RUN] Would invoke: Skill(orchestrate, milestone $MS_SLUG)"
490
+ fi
491
+ else
492
+ echo "Milestone '$MS_TITLE' has no open issues — checking if branch needs deploy"
493
+ fi
348
494
 
349
- ```bash
350
- # Get candidates
351
- gh issue list --state open --label "bug" --limit 50 --json number,title,labels,createdAt \
352
- --jq 'sort_by(.labels | map(select(.name | startswith("priority:P"))) | .[0].name) | .[:{FIX_LIMIT}] | .[] | "#\(.number) \(.title)"'
353
- ```
495
+ # Step 2: Ship milestone branch → staging via /deploy-pr
496
+ MILESTONE_BRANCH="milestone/$MS_SLUG"
497
+ MILESTONE_BRANCH_EXISTS=$(git ls-remote --exit-code origin "$MILESTONE_BRANCH" >/dev/null 2>&1 && echo "true" || echo "false")
498
+
499
+ if [ "$MILESTONE_BRANCH_EXISTS" = "true" ]; then
500
+ echo "Shipping $MILESTONE_BRANCH → staging..."
501
+ if [ "$DRY_RUN" = "false" ]; then
502
+ MS_RESULT=$(Skill("deploy-pr", args="$MILESTONE_BRANCH"))
503
+ MS_STATUS=$(echo "$MS_RESULT" | jq -r '.status // empty' 2>/dev/null || echo '')
504
+ [ -z "$MS_STATUS" ] && MS_STATUS=$(echo "$MS_RESULT" | grep -oE '"status":"[^"]*"' | grep -oE '"[^"]*"$' | tr -d '"' || echo 'unknown')
505
+ echo "Milestone ship status: $MS_STATUS"
506
+
507
+ if [ "$MS_STATUS" = "merged" ]; then
508
+ MILESTONE_DEPLOYS=$((MILESTONE_DEPLOYS + 1))
509
+
510
+ # Step 3: After milestone merges to staging, deploy staging → main
511
+ git fetch origin "$STAGING_BRANCH" "$DEFAULT_BRANCH" 2>/dev/null || true
512
+ STAGING_AHEAD_MS=$(git rev-list --count "origin/${DEFAULT_BRANCH}..origin/${STAGING_BRANCH}" 2>/dev/null || echo '0')
513
+ if [ "$STAGING_AHEAD_MS" -gt 0 ]; then
514
+ echo "Milestone merged to staging — deploying staging → $DEFAULT_BRANCH..."
515
+ MAIN_RESULT=$(Skill("deploy-pr", args="staging"))
516
+ MAIN_STATUS=$(echo "$MAIN_RESULT" | jq -r '.status // empty' 2>/dev/null || echo 'unknown')
517
+ echo "Main deploy status: $MAIN_STATUS"
518
+ [ "$MAIN_STATUS" = "merged" ] && FAST_LANE_DEPLOYS=$((FAST_LANE_DEPLOYS + 1))
519
+ fi
520
+ fi
521
+ else
522
+ echo "[DRY-RUN] Would invoke: Skill(deploy-pr, $MILESTONE_BRANCH)"
523
+ echo "[DRY-RUN] If merged, would invoke: Skill(deploy-pr, staging)"
524
+ fi
525
+ else
526
+ echo "Milestone branch '$MILESTONE_BRANCH' not found on remote — skipping deploy"
527
+ fi
354
528
 
355
- ### 4B: Present fix plan to user
529
+ MILESTONE_ITERATIONS=$((MILESTONE_ITERATIONS + 1))
356
530
 
357
- **MANDATORY CHECKPOINT — do NOT proceed without user confirmation.**
531
+ # Step 4: Review findings from milestones are unmilestoned — they loop back to fast lane automatically
532
+ MS_FINDINGS=$(gh issue list $GH_FLAG \
533
+ --state open \
534
+ --limit 50 \
535
+ --json number,milestone,labels \
536
+ --jq '[.[] | select(.milestone == null and (.labels | map(.name) | any(. == "review-finding")))] | length' \
537
+ 2>/dev/null || echo '0')
538
+ [ "$MS_FINDINGS" -gt 0 ] && echo "NOTE: $MS_FINDINGS review finding(s) now in fast lane — processed next cycle"
539
+ done
358
540
 
359
- ```markdown
360
- ## Autopilot Fix Plan
541
+ echo "Milestone loop complete: $MILESTONE_ITERATIONS milestone(s) processed, $MILESTONE_DEPLOYS milestone ship(s)"
542
+ ```
361
543
 
362
- I'll run `/work-on` for these {N} issues:
544
+ ---
363
545
 
364
- | # | Title | Priority | Estimated Scope |
365
- |---|-------|----------|-----------------|
366
- | {number} | {title} | {priority} | {small/medium/large} |
546
+ ## Phase 4: Final Report
367
547
 
368
- Each issue goes through: investigate → validate → build → review → merge to staging.
369
- Nothing merges to `main` — you deploy when ready.
548
+ Print a summary of everything done in this autopilot run:
370
549
 
371
- **Proceed?** (yes / adjust / skip)
550
+ ```bash
551
+ CYCLE_END=$(date -u +%Y-%m-%dT%H:%M:%SZ)
552
+
553
+ # Re-check final open issue count
554
+ FINAL_OPEN=$(gh issue list $GH_FLAG --state open --limit 200 --json number --jq 'length' 2>/dev/null || echo '0')
555
+ FINAL_FAST_LANE=$(gh issue list $GH_FLAG --state open --limit 200 --json number,milestone \
556
+ --jq '[.[] | select(.milestone == null)] | length' 2>/dev/null || echo '0')
557
+
558
+ echo ""
559
+ echo "╔═══════════════════════════════════════════════════╗"
560
+ echo "║ Autopilot Complete ║"
561
+ echo "╠═══════════════════════════════════════════════════╣"
562
+ echo "║ ║"
563
+
564
+ cat <<REPORT
565
+ ## Autopilot Cycle Report — $CYCLE_END
566
+
567
+ ### Pipeline State at Start
568
+ - staging→main PR: ${STAGING_PR_NUMBER:-none}
569
+ - In-flight issues recovered: $INFLIGHT_ISSUES
570
+ - Open fast-lane issues at start: $OPEN_FAST_LANE
571
+ - Milestones found: ${MILESTONE_COUNT:-0}
572
+
573
+ ### Recon
574
+ - CI failures (24h): $RECENT_FAILURES/$RECENT_RUNS
575
+ - Recurring failures: ${RECURRING:-none}
576
+ - Issues created from recon: ${#RECON_ISSUES[@]:-0}
577
+
578
+ ### Fast Lane
579
+ - Iterations: $FAST_LANE_ITERATIONS
580
+ - Deploys (staging→$DEFAULT_BRANCH): $FAST_LANE_DEPLOYS
581
+ - Review findings bounced to fast lane: $FAST_LANE_FINDINGS_BOUNCED
582
+
583
+ ### Milestone Loop
584
+ - Milestones processed: $MILESTONE_ITERATIONS
585
+ - Milestone ships: $MILESTONE_DEPLOYS
586
+
587
+ ### Final State
588
+ - Open issues: $FINAL_OPEN total ($FINAL_FAST_LANE unmilestoned)
589
+ REPORT
590
+
591
+ if [ "$FINAL_FAST_LANE" -eq 0 ]; then
592
+ echo ""
593
+ echo "Zero open unmilestoned issues remain. Pipeline is clean."
594
+ else
595
+ echo ""
596
+ echo "$FINAL_FAST_LANE unmilestoned issue(s) remain. These may require human review (needs-human label) or have open dependencies:"
597
+ gh issue list $GH_FLAG --state open --limit 20 --json number,title,labels,milestone \
598
+ --jq '.[] | select(.milestone == null) | " #\(.number) \(.title) [\(.labels | map(.name) | join(","))]"' \
599
+ 2>/dev/null || true
600
+ fi
601
+
602
+ echo ""
603
+ echo "╚═══════════════════════════════════════════════════╝"
372
604
  ```
373
605
 
374
- Wait for user response. If they adjust, re-select. If they skip, end the cycle.
375
-
376
- ### 4B.5: Spec-Edit Impact Analysis (MANDATORY before any spec-touching fix) <!-- Added: forge#870 -->
606
+ ---
377
607
 
378
- **Goal**: Before autopilot dispatches a fix that edits ForgeDock's own command specs (`commands/*.md`), surface the blast radius of the change so the edit doesn't silently break downstream consumers (every spec that reads a changed FORGE annotation, every command in a changed sub-phase chain, every script/label a touched node feeds).
608
+ ## Spec-Edit Impact Analysis (MANDATORY before any spec-touching recon fix)
379
609
 
380
- This step **consumes the read-only query interface** shipped by the Spec Knowledge Graph (`scripts/graph-query.sh`, subcommand `impact`). It does **not** reimplement graph traversal — the blast radius is computed once, in `graph-query.sh`, and autopilot only reads its output.
610
+ <!-- Added: forge#870 -->
381
611
 
382
- **When it runs**: for each approved fix target whose affected files (from the issue body's `## Affected Files` section, or the recon finding that created it) include any path matching `commands/*.md`. Non-spec fixes skip this step entirely.
612
+ Before autopilot dispatches a recon-created fix (via /orchestrate → /work-on) that edits ForgeDock's own command specs (`commands/*.md`), surface the blast radius via `graph-query.sh`. This prevents spec edits from silently breaking downstream consumers.
383
613
 
384
- **Graceful degradation**: `graph-query.sh` ships only with the Spec Knowledge Graph. If it is absent (fast-lane repos, or a checkout predating the milestone), **skip with a noted reason — never block the cycle**.
614
+ **When it runs**: for each issue in `RECON_ISSUES` whose affected files include any path matching `commands/*.md`.
385
615
 
386
616
  ```bash
387
617
  GRAPH_QUERY="$(git rev-parse --show-toplevel)/scripts/graph-query.sh"
618
+ RECON_ISSUES=${RECON_ISSUES:-()}
388
619
 
389
- # For each approved fix target that edits commands/*.md:
390
- for issue in $SPEC_FIX_TARGETS; do
620
+ for issue in "${RECON_ISSUES[@]}"; do
621
+ AFFECTED_SPECS=$(gh issue view "$issue" $GH_FLAG --json body --jq '.body' \
622
+ | grep -oE '`commands/[^`]+\.md`' | tr -d '`' | head -5)
623
+ if [ -z "$AFFECTED_SPECS" ]; then continue; fi
624
+
625
+ IMPACT_OUTPUT=""
391
626
  if [ ! -x "$GRAPH_QUERY" ]; then
392
- echo "skip impact analysis for #$issue — graph-query.sh not present (Spec Knowledge Graph not installed)"
393
- continue
627
+ IMPACT_OUTPUT="(skipped — Spec Knowledge Graph not installed)"
628
+ echo "skip impact analysis for #$issue — graph-query.sh not present"
629
+ else
630
+ for spec_file in $AFFECTED_SPECS; do
631
+ NODE=$(basename "$spec_file" .md)
632
+ NODE_IMPACT=$(bash "$GRAPH_QUERY" impact "$NODE" --human 2>/dev/null || echo "(no edges for $NODE)")
633
+ IMPACT_OUTPUT="${IMPACT_OUTPUT}### Impact of $NODE:
634
+ ${NODE_IMPACT}
635
+
636
+ "
637
+ done
394
638
  fi
395
639
 
396
- # Derive the spec node(s) the fix changes. Prefer the FORGE annotation or
397
- # command/sub-phase named in the issue's Affected Files / recon finding.
398
- # graph-query.sh normalizes all of these forms:
399
- # FORGE:CONTRACT == CONTRACT == ann:FORGE:CONTRACT
400
- # work-on:build:implement == cmd:work-on:build:implement
401
- # classify-lane.sh == script:classify-lane.sh
402
- # workflow:merged == label:workflow:merged
403
- for node in $CHANGED_SPEC_NODES; do
404
- echo "### Impact of changing $node (fix #$issue)"
405
- bash "$GRAPH_QUERY" impact "$node" --human
406
- done
407
- done
408
- ```
409
-
410
- **Log the result in a FORGE annotation.** Post one `FORGE:AUTOPILOT_IMPACT` comment per spec-touching fix target on its issue, BEFORE invoking `/work-on` in 4C:
411
-
412
- ```bash
413
- gh issue comment $issue --body "<!-- FORGE:AUTOPILOT_IMPACT -->
640
+ if [ "$DRY_RUN" = "false" ]; then
641
+ gh issue comment "$issue" $GH_FLAG --body "<!-- FORGE:AUTOPILOT_IMPACT -->
414
642
  ## Spec-Edit Impact Analysis
415
643
 
416
- **Changed spec node(s)**: \`$CHANGED_SPEC_NODES\`
644
+ **Changed spec node(s)**: \`$AFFECTED_SPECS\`
417
645
  **Query**: \`graph-query.sh impact <node> --human\` (Spec Knowledge Graph, read-only)
418
646
 
419
647
  ### Blast Radius (downstream consumers of this change)
@@ -421,108 +649,37 @@ gh issue comment $issue --body "<!-- FORGE:AUTOPILOT_IMPACT -->
421
649
  $IMPACT_OUTPUT
422
650
  \`\`\`
423
651
 
424
- Every command/spec listed above reads the changed annotation or sits in the changed sub-phase chain. The \`/work-on\` build for this fix MUST keep these consumers working — verify the changed field/marker is still produced in the shape they expect.
652
+ Every command/spec listed above reads the changed annotation or sits in the changed sub-phase chain. The \`/work-on\` build for this fix MUST keep these consumers working.
425
653
 
426
654
  <!-- FORGE:AUTOPILOT_IMPACT:COMPLETE -->"
655
+ else
656
+ echo "[DRY-RUN] Would post FORGE:AUTOPILOT_IMPACT on issue #$issue"
657
+ fi
658
+ done
427
659
  ```
428
660
 
429
- If `graph-query.sh` was absent, post the same annotation with the blast-radius block set to `(skipped — Spec Knowledge Graph not installed)` so the skip is auditable.
430
-
431
- **Worked example** — autopilot picks up a fix that renames a field inside the `FORGE:CONTRACT` annotation. Before dispatching `/work-on`, it runs:
432
-
433
- ```bash
434
- $ graph-query.sh impact FORGE:CONTRACT --human
435
- Impact (blast radius) of changing ann:FORGE:CONTRACT:
436
- cmd:milestone
437
- cmd:orchestrate
438
- cmd:resume
439
- cmd:review-pr
440
- cmd:work-on
441
- cmd:work-on-monolithic
442
- cmd:work-on:build
443
- cmd:work-on:build:implement
444
- cmd:work-on:review
445
- ```
446
-
447
- The summary makes the nine downstream consumers explicit before the edit lands, so the contract-field rename is reviewed against every reader — not discovered as a broken consumer later.
448
-
449
- **`FORGE:AUTOPILOT_IMPACT`** is a leaf annotation: autopilot is its only writer and no downstream pipeline phase consumes it. It exists to record the blast radius on the issue thread for human and reviewer visibility.
450
-
451
- ### 4C: Execute fixes
452
-
453
- For each approved fix target, invoke `/work-on` via the Skill tool:
454
-
455
- ```
456
- Skill(skill: "work-on", args: "{ISSUE_NUMBER}")
457
- ```
458
-
459
- Run them **sequentially** (not parallel) — each `/work-on` invocation is heavyweight and benefits from full context. If one fails, continue to the next.
460
-
461
- After each `/work-on` completes, record the result:
462
- ```
463
- FIX_RESULTS.push({
464
- issue: NUMBER,
465
- outcome: "merged" | "invalid" | "failed" | "needs-human",
466
- pr: PR_NUMBER or null,
467
- branch: BRANCH_NAME
468
- })
469
- ```
470
-
471
- ### 4D: Fix summary
472
-
473
- ```markdown
474
- ## Fix Results
475
-
476
- | Issue | Outcome | PR |
477
- |-------|---------|-----|
478
- | #{N} | {outcome} | #{PR} |
479
-
480
- **Merged to staging**: {count}
481
- **Needs attention**: {list}
482
- **Spec-edit impact analyses**: {count} blast-radius summaries logged (FORGE:AUTOPILOT_IMPACT) before spec fixes
483
-
484
- When ready to deploy, merge `staging` → `main` via GitHub.
485
- ```
661
+ **`FORGE:AUTOPILOT_IMPACT`** is a leaf annotation: autopilot is its only writer and no downstream pipeline phase consumes it. It records blast radius on the issue thread for human and reviewer visibility.
486
662
 
487
663
  ---
488
664
 
489
665
  ## Safety Rules
490
666
 
491
- 1. **NEVER merge to `main`** — all fixes go to `staging`. User deploys manually.
492
- 2. **NEVER skip investigation** — every fix goes through full `/work-on` (investigate → validate → build → review → merge).
493
- 3. **NEVER fix `needs-human` issues** — these require human judgment (legal, external services, etc.).
494
- 4. **NEVER fix milestone issues** — feature work is scoped to milestones, not autopilot.
495
- 5. **NEVER create duplicate issues** — always dedup against existing open issues first.
496
- 6. **NEVER run more than FIX_LIMIT fixes per cycle** — prevent runaway resource consumption.
497
- 7. **If a fix fails, move on** — don't retry. Log it and let the next cycle or human handle it.
498
- 8. **DRY_RUN means NO side effects** — no issues created, no PRs, no label changes. Report only.
499
- 9. **P0 overrides everything** — if P0 exists, skip normal recon and fix P0 immediately.
500
- 10. **Always gate fixes on user approval** — Phase 4B checkpoint is non-negotiable.
501
-
502
- ---
503
-
504
- ## Recursion: How Cycles Compound
505
-
506
- Each cycle feeds the next:
507
- ```
508
- Cycle N: CI recon detects recurring lint failures across 3 files → autopilot triage creates issue #X
509
- Cycle N+1: autopilot picks up #X → /work-on adds pre-commit hook → merged to staging
510
- Cycle N+2: CI recon shows lint failures resolved — no new issue needed
511
- ```
512
-
513
- ```
514
- Cycle N: /analytics finds CTR dropped on /pricing → creates issue #Y
515
- Cycle N+1: autopilot picks up #Y → /work-on rewrites SERP snippet → merged
516
- Cycle N+2: /analytics shows CTR recovered → improvement confirmed
517
- ```
518
-
519
- The platform gets better every cycle. Issues that aren't picked up age and rise in priority. Issues that keep recurring indicate a deeper structural problem — autopilot will eventually create a "meta" issue about the pattern.
667
+ 1. **Overrides "never merge to main"** — `/autopilot` IS the authorized deploy system. staging→main is normal operation.
668
+ 2. **Never skip investigation** — all issues go through full `/work-on` pipeline (via /orchestrate) before any fix lands.
669
+ 3. **Never process `needs-human` issues** — /orchestrate and /work-on skip these automatically.
670
+ 4. **Never create duplicate issues** — always dedup against existing open issues before creating.
671
+ 5. **Loop safety cap** — max 20 fast-lane iterations prevents infinite loops on stuck state.
672
+ 6. **DRY_RUN means NO side effects** — no issues created, no PRs merged, no labels changed. Report only.
673
+ 7. **Graceful sub-skill degradation** — if /recover-orphans is not installed, log a warning and continue.
674
+ 8. **State always re-read** — never trust a cached issue count. Re-query GitHub at each loop iteration.
675
+ 9. **deploy-pr result is authoritative** — if status is not "merged", do not assume the deploy succeeded. Log and continue the loop.
520
676
 
521
677
  ---
522
678
 
523
679
  ## Operational Notes
524
680
 
525
- - **Runtime**: Recon-only ~3-5 min. Full cycle with fixes ~15-45 min depending on issue count.
526
- - **Token budget**: Recon is cheap (mostly MCP + API calls). Fixes are expensive (full /work-on per issue).
527
- - **Idempotent**: Safe to run multiple times — dedup prevents duplicate issues, /work-on resumes from checkpoints.
528
- - **Pairs with /loop**: Run `/loop 4h /autopilot` for continuous improvement, or `/loop 4h /autopilot --fix --limit 2` for autonomous fixing with a human checkpoint every cycle.
681
+ - **Runtime**: Recon-only ~2-3 min. Full cycle time depends on open issue count — typically 20-90 min per loop.
682
+ - **Token budget**: Recon is cheap (mostly API calls). Each orchestrate+deploy cycle is expensive (full /work-on per issue via /orchestrate).
683
+ - **Idempotent**: Safe to run multiple times — /work-on resumes from pipeline checkpoints, /deploy-pr detects existing open PRs.
684
+ - **Pairs with /loop**: Run `/loop 4h /autopilot` for continuous improvement cycles.
685
+ - **Event-driven complement**: `/autopilot` is a periodic loop. For targeted signal-driven response (metric regression, incident, GEO gap), use `/signal-planner` instead — it converts a specific signal into a dependency-ordered issue DAG, executes via `/orchestrate`, and verifies the originating signal is resolved after the work merges. Use `/autopilot` for scheduled sweeps; use `/signal-planner` for targeted responses.