forgedock 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -2
- package/bin/engine/phases.mjs +178 -28
- package/bin/engine/state.mjs +39 -7
- package/bin/engine-cli.mjs +63 -2
- package/bin/engine.mjs +25 -4
- package/bin/forgedock.mjs +235 -4
- package/bin/hooks/pre-tool-use.mjs +26 -12
- package/bin/journey.mjs +103 -11
- package/commands/forge-stats.md +5 -4
- package/commands/issue.md +5 -1
- package/commands/orchestrate/phase-4-execution.md +2 -0
- package/commands/qa-sweep.md +17 -0
- package/commands/replay.md +16 -2
- package/commands/review-pr-agents/spec-cli.md +179 -0
- package/commands/review-pr.md +49 -0
- package/commands/upgrade-deps.md +518 -0
- package/commands/work-on/build/validate.md +61 -1
- package/commands/work-on/build.md +9 -1
- package/commands/work-on/decompose.md +8 -1
- package/docs/AIDER.md +264 -0
- package/docs/AUTOPILOT-CRON.md +130 -0
- package/docs/CONFIG.md +36 -0
- package/docs/CURSOR.md +135 -0
- package/docs/OPENCODE.md +312 -0
- package/docs/demo.gif +0 -0
- package/docs/eval/README.md +4 -4
- package/docs/launch/show-hn.md +3 -3
- package/docs/site/architecture.md +180 -0
- package/docs/site/command-reference.md +17 -0
- package/docs/site/configuration.md +243 -0
- package/docs/site/for-companies.md +4 -4
- package/docs/site/forge-annotation-protocol.md +1 -1
- package/docs/site/github-is-the-memory.md +2 -2
- package/docs/site/how-it-works.md +1 -1
- package/docs/site/public/llms-full.txt +380 -0
- package/docs/site/public/llms.txt +35 -0
- package/docs/spec-graph-schema.md +3 -3
- package/package.json +2 -2
- package/packages/protocol/README.md +75 -0
- package/packages/protocol/src/card.js +114 -0
- package/packages/protocol/src/cli.js +4 -77
- package/packages/protocol/src/emit.js +14 -3
- package/packages/protocol/src/index.js +2 -1
- package/packages/protocol/src/parse.js +17 -8
- package/packages/protocol/src/types.js +1 -1
- package/packages/protocol/src/validate.js +11 -0
- package/scripts/check-command-side-effects.sh +261 -0
- package/scripts/check-spec-markers.sh +273 -0
- package/scripts/classify-lane.sh +29 -7
- package/scripts/forge-run.sh +322 -0
- package/templates/cross-repo-trigger.yml +282 -0
- package/templates/cursor/forge.mdc +148 -0
- package/templates/github-actions/forgedock-autopilot.yml +113 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# ForgeDock Cross-Repo Trigger — Workflow Template
|
|
2
|
+
#
|
|
3
|
+
# Installation
|
|
4
|
+
# ─────────────
|
|
5
|
+
# Copy this file into your PARENT repo at:
|
|
6
|
+
# .github/workflows/cross-repo-trigger.yml
|
|
7
|
+
#
|
|
8
|
+
# When a PR merges in this repo, the workflow reads forge.yaml to find satellite
|
|
9
|
+
# repos that have trigger_on_merge: true, checks whether touched files match each
|
|
10
|
+
# satellite's trigger_paths globs, and creates a structured investigation issue in
|
|
11
|
+
# every matching satellite repo.
|
|
12
|
+
#
|
|
13
|
+
# Token requirements
|
|
14
|
+
# ──────────────────
|
|
15
|
+
# GITHUB_TOKEN (provided automatically by GitHub Actions) only has write access
|
|
16
|
+
# to THIS repo. To create issues in satellite repos you need a Personal Access
|
|
17
|
+
# Token (PAT) with "issues: write" scope on each satellite repo:
|
|
18
|
+
#
|
|
19
|
+
# Repo Settings → Secrets and variables → Actions → New repository secret
|
|
20
|
+
# Name: FORGE_PAT
|
|
21
|
+
# Value: ghp_... (your PAT)
|
|
22
|
+
#
|
|
23
|
+
# The workflow uses FORGE_PAT when set; it falls back to GITHUB_TOKEN otherwise.
|
|
24
|
+
# For same-org repos you may not need a PAT if GITHUB_TOKEN already has cross-repo
|
|
25
|
+
# write access (org-level permission inheritance). Cross-org or private satellite
|
|
26
|
+
# repos always require a PAT.
|
|
27
|
+
#
|
|
28
|
+
# forge.yaml configuration
|
|
29
|
+
# ─────────────────────────
|
|
30
|
+
# Each satellite that should receive cross-repo triggers must set trigger_on_merge:
|
|
31
|
+
#
|
|
32
|
+
# repos:
|
|
33
|
+
# satellites:
|
|
34
|
+
# - prefix: "sdk"
|
|
35
|
+
# repo: "acme-org/acme-python-sdk"
|
|
36
|
+
# staging_branch: "main"
|
|
37
|
+
# local_path: "/home/youruser/projects/acme-python-sdk"
|
|
38
|
+
# trigger_on_merge: true
|
|
39
|
+
# trigger_paths:
|
|
40
|
+
# - "src/api/**"
|
|
41
|
+
# - "types/**"
|
|
42
|
+
# auto_run: false # reserved — see ForgeDock docs for auto_run setup
|
|
43
|
+
#
|
|
44
|
+
# Fields:
|
|
45
|
+
# trigger_on_merge — boolean; set true to enable triggering for this satellite
|
|
46
|
+
# trigger_paths — list of glob strings (GitHub Actions glob syntax); trigger
|
|
47
|
+
# fires only when at least one changed file matches; omit to
|
|
48
|
+
# trigger on every merge
|
|
49
|
+
# auto_run — boolean; reserved for future auto_run handler; currently no-op
|
|
50
|
+
#
|
|
51
|
+
# Limits and guards
|
|
52
|
+
# ─────────────────
|
|
53
|
+
# - Max 10 satellite issues created per merge event (rate limit guard)
|
|
54
|
+
# - Circular dependency guard: skips satellites whose repo matches this repo
|
|
55
|
+
# - Satellites with trigger_on_merge: false (or absent) are skipped
|
|
56
|
+
# - If forge.yaml is absent, the workflow exits cleanly with an informational log
|
|
57
|
+
|
|
58
|
+
name: ForgeDock Cross-Repo Trigger
|
|
59
|
+
|
|
60
|
+
on:
|
|
61
|
+
pull_request:
|
|
62
|
+
types: [closed]
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
trigger:
|
|
66
|
+
name: Fire cross-repo satellite triggers
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
|
|
69
|
+
# Only run when the PR was actually merged (not just closed)
|
|
70
|
+
if: github.event.pull_request.merged == true
|
|
71
|
+
|
|
72
|
+
permissions:
|
|
73
|
+
contents: read # Required: read forge.yaml from checked-out repo
|
|
74
|
+
issues: write # Required: fallback if this repo is a self-referencing satellite
|
|
75
|
+
|
|
76
|
+
env:
|
|
77
|
+
# Use FORGE_PAT when set (cross-repo access); fall back to GITHUB_TOKEN.
|
|
78
|
+
# Assign via env so it is available to all steps without repeating the expression.
|
|
79
|
+
FORGE_TOKEN: ${{ secrets.FORGE_PAT || github.token }}
|
|
80
|
+
|
|
81
|
+
steps:
|
|
82
|
+
- name: Checkout repository
|
|
83
|
+
uses: actions/checkout@v4
|
|
84
|
+
|
|
85
|
+
- name: Check forge.yaml exists
|
|
86
|
+
id: forge_check
|
|
87
|
+
run: |
|
|
88
|
+
if [ -f "forge.yaml" ]; then
|
|
89
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
90
|
+
echo "forge.yaml found"
|
|
91
|
+
else
|
|
92
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
93
|
+
echo "forge.yaml not found — no satellite configuration to process. Exiting cleanly."
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
- name: Parse forge.yaml and get changed files
|
|
97
|
+
id: forge_parse
|
|
98
|
+
if: steps.forge_check.outputs.exists == 'true'
|
|
99
|
+
env:
|
|
100
|
+
GH_TOKEN: ${{ env.FORGE_TOKEN }}
|
|
101
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
102
|
+
UPSTREAM_REPO: ${{ github.repository }}
|
|
103
|
+
run: |
|
|
104
|
+
# Get changed files from the merged PR
|
|
105
|
+
CHANGED_FILES_JSON=$(gh pr view "$PR_NUMBER" -R "$UPSTREAM_REPO" --json files --jq '[.files[].path]')
|
|
106
|
+
echo "Changed files: $CHANGED_FILES_JSON"
|
|
107
|
+
echo "changed_files_json=$CHANGED_FILES_JSON" >> "$GITHUB_OUTPUT"
|
|
108
|
+
|
|
109
|
+
# Parse satellite list from forge.yaml using Python3 (always available on ubuntu-latest)
|
|
110
|
+
SATELLITES_JSON=$(python3 -c "
|
|
111
|
+
import yaml, json, sys
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
with open('forge.yaml', 'r') as f:
|
|
115
|
+
config = yaml.safe_load(f)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(json.dumps([]), end='')
|
|
118
|
+
sys.exit(0)
|
|
119
|
+
|
|
120
|
+
repos = config.get('repos', {}) or {}
|
|
121
|
+
satellites = repos.get('satellites', []) or []
|
|
122
|
+
|
|
123
|
+
result = []
|
|
124
|
+
for sat in satellites:
|
|
125
|
+
if not isinstance(sat, dict):
|
|
126
|
+
continue
|
|
127
|
+
repo = sat.get('repo', '')
|
|
128
|
+
trigger_on_merge = sat.get('trigger_on_merge', False)
|
|
129
|
+
trigger_paths = sat.get('trigger_paths', []) or []
|
|
130
|
+
auto_run = sat.get('auto_run', False)
|
|
131
|
+
if not repo or not trigger_on_merge:
|
|
132
|
+
continue
|
|
133
|
+
result.append({
|
|
134
|
+
'repo': repo,
|
|
135
|
+
'trigger_paths': trigger_paths,
|
|
136
|
+
'auto_run': bool(auto_run),
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
print(json.dumps(result), end='')
|
|
140
|
+
")
|
|
141
|
+
echo "Satellites with trigger_on_merge=true: $SATELLITES_JSON"
|
|
142
|
+
echo "satellites_json=$SATELLITES_JSON" >> "$GITHUB_OUTPUT"
|
|
143
|
+
|
|
144
|
+
- name: Fire satellite triggers
|
|
145
|
+
if: steps.forge_check.outputs.exists == 'true'
|
|
146
|
+
env:
|
|
147
|
+
GH_TOKEN: ${{ env.FORGE_TOKEN }}
|
|
148
|
+
UPSTREAM_REPO: ${{ github.repository }}
|
|
149
|
+
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
150
|
+
MERGED_BRANCH: ${{ github.event.pull_request.head.ref }}
|
|
151
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
152
|
+
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
153
|
+
SATELLITES_JSON: ${{ steps.forge_parse.outputs.satellites_json }}
|
|
154
|
+
CHANGED_FILES_JSON: ${{ steps.forge_parse.outputs.changed_files_json }}
|
|
155
|
+
run: |
|
|
156
|
+
python3 -c "
|
|
157
|
+
import json, os, subprocess, sys, fnmatch
|
|
158
|
+
|
|
159
|
+
upstream_repo = os.environ['UPSTREAM_REPO']
|
|
160
|
+
pr_number = os.environ['PR_NUMBER']
|
|
161
|
+
merged_branch = os.environ['MERGED_BRANCH']
|
|
162
|
+
pr_url = os.environ['PR_URL']
|
|
163
|
+
pr_title = os.environ['PR_TITLE']
|
|
164
|
+
satellites_json = os.environ.get('SATELLITES_JSON', '[]')
|
|
165
|
+
changed_files_json = os.environ.get('CHANGED_FILES_JSON', '[]')
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
satellites = json.loads(satellites_json) if satellites_json else []
|
|
169
|
+
except json.JSONDecodeError:
|
|
170
|
+
satellites = []
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
changed_files = json.loads(changed_files_json) if changed_files_json else []
|
|
174
|
+
except json.JSONDecodeError:
|
|
175
|
+
changed_files = []
|
|
176
|
+
|
|
177
|
+
if not satellites:
|
|
178
|
+
print('No satellites with trigger_on_merge: true found in forge.yaml')
|
|
179
|
+
sys.exit(0)
|
|
180
|
+
|
|
181
|
+
if not changed_files:
|
|
182
|
+
print('No changed files found in merged PR — nothing to match against trigger_paths')
|
|
183
|
+
sys.exit(0)
|
|
184
|
+
|
|
185
|
+
def matches_glob(path, pattern):
|
|
186
|
+
if fnmatch.fnmatch(path, pattern):
|
|
187
|
+
return True
|
|
188
|
+
if '**' not in pattern:
|
|
189
|
+
return False
|
|
190
|
+
parts = pattern.split('**')
|
|
191
|
+
prefix = parts[0].rstrip('/')
|
|
192
|
+
suffix = parts[-1].lstrip('/')
|
|
193
|
+
if prefix and not path.startswith(prefix):
|
|
194
|
+
return False
|
|
195
|
+
if suffix and not (fnmatch.fnmatch(path, suffix) or '/' + suffix in path):
|
|
196
|
+
return False
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
issues_created = 0
|
|
200
|
+
MAX_ISSUES = 10
|
|
201
|
+
NL = chr(10)
|
|
202
|
+
|
|
203
|
+
for sat in satellites:
|
|
204
|
+
if issues_created >= MAX_ISSUES:
|
|
205
|
+
print(f'Rate limit reached: {MAX_ISSUES} satellite issues created. Stopping.')
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
sat_repo = sat.get('repo', '')
|
|
209
|
+
trigger_paths = sat.get('trigger_paths', [])
|
|
210
|
+
auto_run = sat.get('auto_run', False)
|
|
211
|
+
|
|
212
|
+
if not sat_repo:
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
# Circular dependency guard
|
|
216
|
+
if sat_repo == upstream_repo:
|
|
217
|
+
print(f'Skipping {sat_repo}: circular dependency guard (satellite == upstream repo)')
|
|
218
|
+
continue
|
|
219
|
+
|
|
220
|
+
# Glob matching against trigger_paths
|
|
221
|
+
if trigger_paths:
|
|
222
|
+
matched_files = [
|
|
223
|
+
f for f in changed_files
|
|
224
|
+
if any(matches_glob(f, p) for p in trigger_paths)
|
|
225
|
+
]
|
|
226
|
+
if not matched_files:
|
|
227
|
+
print(f'Skipping {sat_repo}: no changed files match trigger_paths {trigger_paths}')
|
|
228
|
+
continue
|
|
229
|
+
else:
|
|
230
|
+
matched_files = list(changed_files)
|
|
231
|
+
print(f'{sat_repo}: no trigger_paths set — triggering on all {len(matched_files)} changed file(s)')
|
|
232
|
+
|
|
233
|
+
print(f'Creating issue in {sat_repo}: {len(matched_files)} matched file(s)')
|
|
234
|
+
|
|
235
|
+
matched_list = NL.join(f'- {f}' for f in matched_files)
|
|
236
|
+
auto_run_note = (
|
|
237
|
+
NL + '> **auto_run** is set for this satellite. '
|
|
238
|
+
'Run /work-on on this issue number to begin automated investigation.' + NL
|
|
239
|
+
if auto_run else ''
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
issue_body = (
|
|
243
|
+
'<!-- FORGE:CROSS_REPO_TRIGGER -->' + NL +
|
|
244
|
+
'## Cross-Repo Trigger' + NL + NL +
|
|
245
|
+
f'A PR merged in **{upstream_repo}** touches paths relevant to this satellite repo.' + NL + NL +
|
|
246
|
+
'### Upstream Event' + NL +
|
|
247
|
+
f'- Upstream repo: {upstream_repo}' + NL +
|
|
248
|
+
f'- Merged PR: #{pr_number} ({pr_url})' + NL +
|
|
249
|
+
f'- PR title: {pr_title}' + NL +
|
|
250
|
+
f'- Merged branch: {merged_branch}' + NL + NL +
|
|
251
|
+
'### Matched Files' + NL +
|
|
252
|
+
f'These changed files matched this satellites trigger_paths:' + NL + NL +
|
|
253
|
+
matched_list + NL + NL +
|
|
254
|
+
'### Recommended Action' + NL +
|
|
255
|
+
'Investigate whether these changes affect this satellite repos contracts, types, or integrations.' + NL + NL +
|
|
256
|
+
f'Review the diff at {pr_url} or run /work-on on this issue to begin automated investigation.' + NL +
|
|
257
|
+
auto_run_note + NL +
|
|
258
|
+
'### Context' + NL +
|
|
259
|
+
'Created automatically by the ForgeDock cross-repo trigger workflow.' + NL +
|
|
260
|
+
'See https://github.com/RapierCraftStudios/ForgeDock for documentation.' + NL
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
issue_title = f'feat(cross-repo): upstream merge in {upstream_repo} (PR #{pr_number}) — investigate impact'
|
|
264
|
+
|
|
265
|
+
result = subprocess.run(
|
|
266
|
+
['gh', 'issue', 'create',
|
|
267
|
+
'-R', sat_repo,
|
|
268
|
+
'--title', issue_title,
|
|
269
|
+
'--body', issue_body],
|
|
270
|
+
capture_output=True, text=True
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if result.returncode == 0:
|
|
274
|
+
issue_url = result.stdout.strip()
|
|
275
|
+
print(f'Created issue in {sat_repo}: {issue_url}')
|
|
276
|
+
issues_created += 1
|
|
277
|
+
else:
|
|
278
|
+
print(f'ERROR creating issue in {sat_repo}: {result.stderr.strip()}')
|
|
279
|
+
print('Hint: Ensure FORGE_PAT has issues:write access to this satellite repo.')
|
|
280
|
+
|
|
281
|
+
print(f'Done: {issues_created} satellite issue(s) created.')
|
|
282
|
+
"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: ForgeDock pipeline adapter for Cursor Agent mode
|
|
3
|
+
alwaysApply: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ForgeDock Pipeline — Cursor Adapter
|
|
7
|
+
|
|
8
|
+
This file is part of the [ForgeDock](https://github.com/RapierCraftStudios/ForgeDock) autonomous AI development pipeline. It adapts the ForgeDock pipeline for execution within Cursor's Agent mode.
|
|
9
|
+
|
|
10
|
+
## What ForgeDock Is
|
|
11
|
+
|
|
12
|
+
ForgeDock is a structured development pipeline that uses GitHub as a knowledge graph. Each pipeline phase (investigate → build → review → merge) writes machine-readable annotations (FORGE: HTML comments) to GitHub issue and PR comments. Downstream phases read those annotations to reconstruct context — without relying on conversation history.
|
|
13
|
+
|
|
14
|
+
**Key principle**: GitHub issues/PRs are the persistent context layer. All state lives there, not in this conversation.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
Before running any pipeline phase, verify these tools are available in the terminal:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
gh auth status # GitHub CLI — authenticated
|
|
22
|
+
git --version # Git — available
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
ForgeDock command specs live in the repo at `commands/` (if this repo uses ForgeDock) or are available globally after `npx forgedock` install.
|
|
26
|
+
|
|
27
|
+
## Running the Pipeline
|
|
28
|
+
|
|
29
|
+
### Start a Pipeline Run
|
|
30
|
+
|
|
31
|
+
To work on a GitHub issue, invoke the `work-on` command spec directly:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Read the full work-on spec first
|
|
35
|
+
cat commands/work-on.md
|
|
36
|
+
|
|
37
|
+
# Then execute the pipeline for issue #NUMBER following the spec exactly
|
|
38
|
+
# Phase sequence: Phase 0 (load) → Phase 1 (investigate) → Phase 3 (build) → Phase 4 (PR) → Phase 5 (review) → Phase 6 (close)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Critical**: After reading `commands/work-on.md`, follow the Universal Phase Dispatcher section exactly. Execute all phases in sequence — do not stop between phases unless a terminal label is set (`workflow:merged`, `workflow:invalid`, `needs-human`).
|
|
42
|
+
|
|
43
|
+
### Load Issue Context
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Load issue state (always do this first — GitHub is the source of truth)
|
|
47
|
+
gh issue view NUMBER -R OWNER/REPO --json number,title,body,labels,state,comments,milestone
|
|
48
|
+
|
|
49
|
+
# Read existing pipeline annotations
|
|
50
|
+
gh api repos/OWNER/REPO/issues/NUMBER/comments \
|
|
51
|
+
--jq '.[] | {id: .id, author: .user.login, body: .body}'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Check Terminal State
|
|
55
|
+
|
|
56
|
+
After every phase, check whether a terminal label is set before continuing:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
gh issue view NUMBER -R OWNER/REPO --json labels --jq '[.labels[].name]'
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Terminal labels** (stop if any are present):
|
|
63
|
+
- `workflow:merged`
|
|
64
|
+
- `workflow:invalid`
|
|
65
|
+
- `needs-human`
|
|
66
|
+
- `workflow:decomposed`
|
|
67
|
+
|
|
68
|
+
### Write FORGE Annotations
|
|
69
|
+
|
|
70
|
+
Each pipeline phase writes a structured annotation to GitHub. Use exact format from `docs/FORGE-PROTOCOL.md`:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Post investigation report (Phase 1C)
|
|
74
|
+
gh issue comment NUMBER -R OWNER/REPO --body "<!-- FORGE:INVESTIGATOR -->
|
|
75
|
+
## Investigation Report
|
|
76
|
+
...
|
|
77
|
+
<!-- INVESTIGATION:COMPLETE -->"
|
|
78
|
+
|
|
79
|
+
# Post builder report (Phase 3M)
|
|
80
|
+
gh issue comment NUMBER -R OWNER/REPO --body "<!-- FORGE:BUILDER -->
|
|
81
|
+
## Implementation Complete
|
|
82
|
+
...
|
|
83
|
+
<!-- FORGE:BUILDER:COMPLETE -->"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Git Workflow
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# Create a worktree for the branch (Phase 3E)
|
|
90
|
+
git fetch origin
|
|
91
|
+
git worktree add .claude/worktrees/BRANCH_NAME -b BRANCH_NAME origin/STAGING_OR_MILESTONE_BRANCH
|
|
92
|
+
|
|
93
|
+
# Commit changes (Phase 3K)
|
|
94
|
+
cd .claude/worktrees/BRANCH_NAME
|
|
95
|
+
git add -u
|
|
96
|
+
git commit -m "feat(scope): description (#NUMBER)"
|
|
97
|
+
|
|
98
|
+
# Push and create PR (Phase 4B-4D)
|
|
99
|
+
git push -u origin BRANCH_NAME
|
|
100
|
+
gh pr create -R OWNER/REPO --base PR_BASE --head BRANCH_NAME \
|
|
101
|
+
--title "Feat: description" --body "..."
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Labels
|
|
105
|
+
|
|
106
|
+
ForgeDock uses `workflow:*` labels to track pipeline state:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Transition labels as you progress through phases
|
|
110
|
+
gh issue edit NUMBER -R OWNER/REPO --add-label "workflow:investigating"
|
|
111
|
+
gh issue edit NUMBER -R OWNER/REPO --add-label "workflow:ready-to-build" --remove-label "workflow:investigating"
|
|
112
|
+
gh issue edit NUMBER -R OWNER/REPO --add-label "workflow:building" --remove-label "workflow:ready-to-build"
|
|
113
|
+
gh issue edit NUMBER -R OWNER/REPO --add-label "workflow:in-review" --remove-label "workflow:building"
|
|
114
|
+
gh issue edit NUMBER -R OWNER/REPO --add-label "workflow:merged" --remove-label "workflow:in-review"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Limitations vs. Claude Code
|
|
118
|
+
|
|
119
|
+
| Capability | Claude Code | Cursor (this adapter) |
|
|
120
|
+
|------------|-------------|----------------------|
|
|
121
|
+
| Skill loading | Dynamic (`~/.claude/commands/`) | Static (this rules file) |
|
|
122
|
+
| Multi-phase pipeline | Automatic (single session) | Manual (re-invoke agent per phase) |
|
|
123
|
+
| Sub-skill invocation | `Skill("review-pr")` | Read `commands/review-pr.md` directly |
|
|
124
|
+
| Session continuity | Native compaction resilience | Re-read GitHub state each invocation |
|
|
125
|
+
|
|
126
|
+
**Multi-phase pipeline**: Cursor Agent mode does not persist between invocations. After each phase completes, you must manually re-invoke the agent for the next phase. The pipeline's compaction resilience (reading GitHub state at the start of each phase) handles this correctly — each new agent invocation re-reads the issue and picks up from the current label state.
|
|
127
|
+
|
|
128
|
+
**Sub-skill invocation**: When the pipeline spec calls `Skill("review-pr")` or similar, read the referenced command spec file directly:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
cat commands/review-pr.md # Then follow the spec
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Configuration
|
|
135
|
+
|
|
136
|
+
Your project's ForgeDock configuration is in `forge.yaml`. The pipeline reads `project.github.repo`, `project.github.owner`, and `branches.staging` from this file.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
cat forge.yaml # Review project configuration
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Reference
|
|
143
|
+
|
|
144
|
+
- `commands/work-on.md` — Full pipeline spec (investigate → build → review → merge)
|
|
145
|
+
- `commands/review-pr.md` — PR review spec
|
|
146
|
+
- `commands/quality-gate.md` — Pre-commit quality checks
|
|
147
|
+
- `docs/FORGE-PROTOCOL.md` — FORGE annotation protocol reference
|
|
148
|
+
- `docs/CURSOR.md` — Cursor-specific installation and usage guide
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# ForgeDock Autopilot — Scheduled Cron Workflow Template
|
|
2
|
+
#
|
|
3
|
+
# Copy this file into your repo at:
|
|
4
|
+
# .github/workflows/forgedock-autopilot.yml
|
|
5
|
+
#
|
|
6
|
+
# Required secrets (set in your repo's Settings → Secrets → Actions):
|
|
7
|
+
# ANTHROPIC_API_KEY — Your Anthropic API key (https://console.anthropic.com)
|
|
8
|
+
#
|
|
9
|
+
# GITHUB_TOKEN is provided automatically by GitHub Actions.
|
|
10
|
+
#
|
|
11
|
+
# See docs/AUTOPILOT-CRON.md for full setup guide.
|
|
12
|
+
|
|
13
|
+
name: ForgeDock Autopilot
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
schedule:
|
|
17
|
+
# Every Monday at 9 AM UTC — adjust to your preferred cadence
|
|
18
|
+
- cron: '0 9 * * 1'
|
|
19
|
+
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
# Manual trigger — allows you to run autopilot on demand with custom options
|
|
22
|
+
inputs:
|
|
23
|
+
mode:
|
|
24
|
+
description: 'Run mode: recon (safe, read-only) or fix (creates branches + PRs)'
|
|
25
|
+
required: false
|
|
26
|
+
default: 'recon'
|
|
27
|
+
type: choice
|
|
28
|
+
options:
|
|
29
|
+
- recon
|
|
30
|
+
- fix
|
|
31
|
+
limit:
|
|
32
|
+
description: 'Max issues to fix per run (fix mode only)'
|
|
33
|
+
required: false
|
|
34
|
+
default: '3'
|
|
35
|
+
type: string
|
|
36
|
+
|
|
37
|
+
jobs:
|
|
38
|
+
autopilot:
|
|
39
|
+
name: Run /autopilot
|
|
40
|
+
runs-on: ubuntu-latest
|
|
41
|
+
|
|
42
|
+
permissions:
|
|
43
|
+
contents: write # Required: create branches, push commits (fix mode)
|
|
44
|
+
issues: write # Required: create and label issues
|
|
45
|
+
pull-requests: write # Required: create PRs (fix mode)
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- name: Checkout repository
|
|
49
|
+
uses: actions/checkout@v4
|
|
50
|
+
with:
|
|
51
|
+
# Fetch full history so git log commands in /autopilot work correctly
|
|
52
|
+
fetch-depth: 0
|
|
53
|
+
|
|
54
|
+
- name: Set up Node.js
|
|
55
|
+
uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: 24
|
|
58
|
+
|
|
59
|
+
- name: Install ForgeDock
|
|
60
|
+
run: npx --yes forgedock
|
|
61
|
+
|
|
62
|
+
- name: Install Claude Code CLI
|
|
63
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
64
|
+
|
|
65
|
+
- name: Resolve autopilot arguments
|
|
66
|
+
id: args
|
|
67
|
+
env:
|
|
68
|
+
# Pass workflow_dispatch inputs through env: indirection rather than
|
|
69
|
+
# interpolating ${{ inputs.* }} directly into the run: script text.
|
|
70
|
+
# inputs.limit is free-text (type: string) — splicing it straight into
|
|
71
|
+
# the shell would let a crafted value break out of the assignment and
|
|
72
|
+
# inject arbitrary commands. inputs.mode is a `type: choice` enum and
|
|
73
|
+
# isn't exploitable the same way, but is routed the same way for
|
|
74
|
+
# consistency/defense-in-depth.
|
|
75
|
+
MODE_INPUT: ${{ inputs.mode }}
|
|
76
|
+
LIMIT_INPUT: ${{ inputs.limit }}
|
|
77
|
+
run: |
|
|
78
|
+
# On scheduled runs: always recon mode (safe default)
|
|
79
|
+
# On workflow_dispatch: use the selected mode input
|
|
80
|
+
if [ "${{ github.event_name }}" = "schedule" ]; then
|
|
81
|
+
MODE="recon"
|
|
82
|
+
LIMIT="3"
|
|
83
|
+
else
|
|
84
|
+
MODE="$MODE_INPUT"
|
|
85
|
+
LIMIT="$LIMIT_INPUT"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ "$MODE" = "fix" ]; then
|
|
89
|
+
ARGS="--fix --limit $LIMIT"
|
|
90
|
+
else
|
|
91
|
+
ARGS="--recon-only"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo "mode=$MODE" >> "$GITHUB_OUTPUT"
|
|
95
|
+
echo "args=$ARGS" >> "$GITHUB_OUTPUT"
|
|
96
|
+
echo "Autopilot mode: $MODE (args: $ARGS)"
|
|
97
|
+
|
|
98
|
+
- name: Run /autopilot
|
|
99
|
+
env:
|
|
100
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
101
|
+
GH_TOKEN: ${{ github.token }}
|
|
102
|
+
# CI=true tells /autopilot Phase 4B to skip the interactive confirmation
|
|
103
|
+
# gate. The workflow_dispatch mode=fix input serves as the human opt-in.
|
|
104
|
+
CI: "true"
|
|
105
|
+
# AUTOPILOT_ARGS is derived (one step upstream) from the free-text
|
|
106
|
+
# inputs.limit workflow_dispatch input. Route it through env:
|
|
107
|
+
# indirection here too — splicing it directly via ${{ }} into this
|
|
108
|
+
# run: block would reopen the same script-injection vector one hop
|
|
109
|
+
# later, in a step that also carries ANTHROPIC_API_KEY/GH_TOKEN.
|
|
110
|
+
AUTOPILOT_ARGS: ${{ steps.args.outputs.args }}
|
|
111
|
+
run: |
|
|
112
|
+
claude --dangerously-skip-permissions \
|
|
113
|
+
-p "/autopilot $AUTOPILOT_ARGS"
|