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.
- package/README.md +53 -8
- package/bin/batch-runner.mjs +339 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +24 -2
- package/bin/engine/state.mjs +8 -3
- package/bin/engine-cli.mjs +177 -5
- package/bin/forgedock.mjs +409 -26
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +72 -14
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +70 -20
- package/bin/settings-hook.mjs +235 -1
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +518 -361
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +58 -1
- package/commands/deploy-pr.md +485 -0
- package/commands/design-bench.md +5 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/fix-ci.md +577 -0
- package/commands/forge-stats.md +121 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +23 -2
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2045
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +248 -10
- package/commands/recover-orphans.md +576 -0
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +591 -52
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +3 -1
- package/commands/work-on/build/validate.md +26 -7
- package/commands/work-on/build.md +163 -24
- package/commands/work-on/close.md +130 -3
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +24 -4
- package/commands/work-on-monolithic.md +7 -2
- package/commands/work-on.md +202 -4
- package/docs/BRAND.md +14 -7
- package/docs/CI.md +16 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +5 -8
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +120 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +386 -111
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +43 -4
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +9 -1
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +3 -1
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +254 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/worktree-lifecycle.sh +3 -3
- package/templates/devdocs/agent/using-forgedock.md +14 -0
- package/docs/site/.vitepress/config.mjs +0 -85
package/commands/autopilot.md
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Autonomous
|
|
3
|
-
argument-hint: [--
|
|
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 —
|
|
9
|
+
# /autopilot — Autonomous Deploy Loop
|
|
7
10
|
|
|
8
|
-
**Input**: $ARGUMENTS (default:
|
|
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
|
|
13
|
-
- `{OPS_INBOX_PATH}` ← `services.ops_inbox_path` (optional) — path on production server to
|
|
14
|
-
- `{BILLING_ENABLED}` ← `billing.enabled` (optional, default `false`) — set to `true` to enable Stripe data
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
**
|
|
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**:
|
|
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) |
|
|
37
|
-
| `--
|
|
38
|
-
| `--
|
|
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
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
##
|
|
53
|
-
|
|
54
|
-
**Goal**: Understand what happened since the last cycle.
|
|
57
|
+
## Config Preamble (MANDATORY — run before any phase)
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
Read all `forge.yaml` config variables before any logic runs:
|
|
57
60
|
|
|
58
61
|
```bash
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
+
**Goal**: Read current GitHub state and determine where the pipeline is. Always start here — never assume clean state.
|
|
89
88
|
|
|
90
|
-
###
|
|
89
|
+
### 0A: Detect open staging→main PR
|
|
91
90
|
|
|
92
91
|
```bash
|
|
93
|
-
gh
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
108
|
+
### 0B: Detect open milestone→staging PRs
|
|
105
109
|
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
+
### 0C: Detect in-flight issues (stuck in intermediate workflow states)
|
|
111
122
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
###
|
|
156
|
+
### 0F: Summarize detected state
|
|
162
157
|
|
|
163
|
-
Wait for all agents to complete. Aggregate into a `RECON_DATA` object:
|
|
164
158
|
```
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
181
|
+
If `RECON_ONLY=true`, print the recon report and **stop after this phase**.
|
|
181
182
|
|
|
182
|
-
###
|
|
183
|
+
### 1A: Recover orphaned pipeline state (MANDATORY — run before recon)
|
|
183
184
|
|
|
184
|
-
|
|
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
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
|
|
204
|
+
### 1B: CI/CD Health
|
|
193
205
|
|
|
194
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
250
|
+
### 1D: Analytics Pulse (optional — forge.yaml-gated)
|
|
211
251
|
|
|
212
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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.
|
|
292
|
+
{Description of the finding with specific data points.}
|
|
222
293
|
|
|
223
294
|
## Root Cause (if known)
|
|
224
295
|
|
|
225
|
-
{
|
|
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
|
|
309
|
+
Found by \`/autopilot\` cycle on $(date -u +%Y-%m-%dT%H:%M:%SZ).
|
|
241
310
|
|
|
242
311
|
## Evidence
|
|
243
312
|
|
|
244
|
-
{
|
|
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
|
-
|
|
321
|
+
Store created issue numbers in `RECON_ISSUES` array for spec-edit impact analysis.
|
|
247
322
|
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
###
|
|
333
|
+
### CI/CD (24h)
|
|
334
|
+
- Failure rate: $RECENT_FAILURES/$RECENT_RUNS
|
|
335
|
+
- Recurring: ${RECURRING:-none}
|
|
259
336
|
|
|
260
|
-
|
|
337
|
+
### Issue Backlog
|
|
338
|
+
- Open: P0=$P0_COUNT P1=$P1_COUNT P2=$P2_COUNT
|
|
339
|
+
- Stale (>14d, no workflow): $STALE_COUNT
|
|
261
340
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
|
350
|
+
## Phase 2: Fast Lane Loop
|
|
279
351
|
|
|
280
|
-
**Goal**:
|
|
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
|
-
|
|
354
|
+
**Overrides "never merge to main"**: This phase deploys staging→main after each iteration. `/autopilot` is the authorized deploy system.
|
|
283
355
|
|
|
284
|
-
```
|
|
285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
293
|
-
|
|
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
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
-
|
|
313
|
-
|
|
314
|
-
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
441
|
+
## Phase 3: Milestone Loop
|
|
329
442
|
|
|
330
|
-
**Goal**:
|
|
443
|
+
**Goal**: Ship each milestone with open issues, in order of completion percentage (highest first). Skip milestones at 0%.
|
|
331
444
|
|
|
332
|
-
|
|
445
|
+
**Each milestone cycle**: orchestrate all open issues in the milestone → ship milestone branch to staging → deploy staging to main.
|
|
333
446
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
529
|
+
MILESTONE_ITERATIONS=$((MILESTONE_ITERATIONS + 1))
|
|
356
530
|
|
|
357
|
-
|
|
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
|
-
|
|
360
|
-
|
|
541
|
+
echo "Milestone loop complete: $MILESTONE_ITERATIONS milestone(s) processed, $MILESTONE_DEPLOYS milestone ship(s)"
|
|
542
|
+
```
|
|
361
543
|
|
|
362
|
-
|
|
544
|
+
---
|
|
363
545
|
|
|
364
|
-
|
|
365
|
-
|---|-------|----------|-----------------|
|
|
366
|
-
| {number} | {title} | {priority} | {small/medium/large} |
|
|
546
|
+
## Phase 4: Final Report
|
|
367
547
|
|
|
368
|
-
|
|
369
|
-
Nothing merges to `main` — you deploy when ready.
|
|
548
|
+
Print a summary of everything done in this autopilot run:
|
|
370
549
|
|
|
371
|
-
|
|
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
|
-
|
|
375
|
-
|
|
376
|
-
### 4B.5: Spec-Edit Impact Analysis (MANDATORY before any spec-touching fix) <!-- Added: forge#870 -->
|
|
606
|
+
---
|
|
377
607
|
|
|
378
|
-
|
|
608
|
+
## Spec-Edit Impact Analysis (MANDATORY before any spec-touching recon fix)
|
|
379
609
|
|
|
380
|
-
|
|
610
|
+
<!-- Added: forge#870 -->
|
|
381
611
|
|
|
382
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
393
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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)**: \`$
|
|
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
|
|
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
|
-
|
|
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. **
|
|
492
|
-
2. **
|
|
493
|
-
3. **
|
|
494
|
-
4. **
|
|
495
|
-
5. **
|
|
496
|
-
6. **
|
|
497
|
-
7. **
|
|
498
|
-
8. **
|
|
499
|
-
9. **
|
|
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
|
|
526
|
-
- **Token budget**: Recon is cheap (mostly
|
|
527
|
-
- **Idempotent**: Safe to run multiple times —
|
|
528
|
-
- **Pairs with /loop**: Run `/loop 4h /autopilot` for continuous improvement
|
|
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.
|