forgedock 1.0.1 → 1.0.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 (43) hide show
  1. package/README.md +19 -2
  2. package/bin/forgedock.mjs +3084 -80
  3. package/bin/github-app-manifest.json +24 -0
  4. package/bin/tui.mjs +702 -0
  5. package/commands/analytics.md +129 -52
  6. package/commands/audit-agents.md +29 -5
  7. package/commands/audit.md +55 -17
  8. package/commands/autopilot.md +11 -6
  9. package/commands/cleanup.md +44 -24
  10. package/commands/deploy-info.md +7 -3
  11. package/commands/forge-stats.md +13 -4
  12. package/commands/forgedock-init.md +534 -0
  13. package/commands/geo-audit.md +114 -26
  14. package/commands/incident-response.md +4 -1
  15. package/commands/issue.md +26 -25
  16. package/commands/milestone.md +80 -59
  17. package/commands/orchestrate.md +38 -21
  18. package/commands/pipeline-health.md +26 -15
  19. package/commands/qa-sweep.md +68 -5
  20. package/commands/quality-gate.md +18 -0
  21. package/commands/review-pr-agents.md +58 -90
  22. package/commands/review-pr.md +58 -16
  23. package/commands/rollback.md +8 -5
  24. package/commands/security-audit.md +68 -12
  25. package/commands/sync-ecosystem.md +196 -156
  26. package/commands/work-on/build/architect.md +2 -2
  27. package/commands/work-on/build/context.md +2 -2
  28. package/commands/work-on/build/implement.md +3 -3
  29. package/commands/work-on/build/validate.md +3 -3
  30. package/commands/work-on/build.md +6 -6
  31. package/commands/work-on/close.md +59 -30
  32. package/commands/work-on/decompose.md +6 -6
  33. package/commands/work-on/investigate.md +16 -20
  34. package/commands/work-on/review.md +4 -4
  35. package/commands/work-on-monolithic.md +15 -16
  36. package/commands/work-on.md +45 -33
  37. package/docs/CODEX.md +1 -1
  38. package/docs/CONFIG.md +398 -0
  39. package/docs/WORKFLOW.md +115 -72
  40. package/package.json +1 -1
  41. package/scripts/verify-host-headers.sh +14 -9
  42. package/scripts/verify-route-registration.sh +10 -10
  43. package/commands/failure-recon.md +0 -375
@@ -12,14 +12,81 @@ You are the GEO (Generative Engine Optimization) auditor. Pull AI referral data
12
12
 
13
13
  ---
14
14
 
15
+ ## Phase 0: Load Config
16
+
17
+ Read `forge.yaml` from the current directory (or the path passed as `$ARGUMENTS`). Gate the entire command on the `services.analytics` section existing.
18
+
19
+ ```bash
20
+ # Verify forge.yaml exists and analytics is configured
21
+ GEO_CONFIG=$(python3 - <<'PYEOF'
22
+ import yaml, sys, os
23
+
24
+ config_path = os.environ.get('FORGE_CONFIG', 'forge.yaml')
25
+ try:
26
+ cfg = yaml.safe_load(open(config_path))
27
+ except FileNotFoundError:
28
+ print('ERROR: forge.yaml not found. Run `cp forge.yaml.example forge.yaml` and configure your project.')
29
+ sys.exit(1)
30
+
31
+ svc = cfg.get('services', {})
32
+ analytics = svc.get('analytics', {})
33
+ if not analytics:
34
+ print('ERROR: forge.yaml missing services.analytics section — geo-audit requires analytics to be configured.')
35
+ print('Add services.analytics.umami and/or services.analytics.clarity to forge.yaml.')
36
+ sys.exit(1)
37
+
38
+ paths = cfg.get('paths', {})
39
+ proj = cfg.get('project', {})
40
+ creds = paths.get('credentials', {})
41
+ creds_file = creds.get('file', '') if isinstance(creds, dict) else ''
42
+
43
+ domain = svc.get('domain', '')
44
+ if not domain:
45
+ print('ERROR: forge.yaml missing services.domain — required for page compliance checks.')
46
+ sys.exit(1)
47
+
48
+ umami = analytics.get('umami', {})
49
+ clarity = analytics.get('clarity', {})
50
+ owner = proj.get('owner', '')
51
+ repo = proj.get('repo', '')
52
+
53
+ print(domain)
54
+ print('https://' + domain)
55
+ print(umami.get('website_id', ''))
56
+ print(umami.get('url', ''))
57
+ print(clarity.get('project_id', ''))
58
+ print(creds_file)
59
+ print((owner + '/' + repo) if owner and repo else '')
60
+ PYEOF
61
+ )
62
+
63
+ if echo "$GEO_CONFIG" | grep -q '^ERROR:'; then
64
+ echo "$GEO_CONFIG"
65
+ exit 1
66
+ fi
67
+
68
+ # Assign config variables
69
+ DOMAIN=$(echo "$GEO_CONFIG" | sed -n '1p')
70
+ SITE_URL=$(echo "$GEO_CONFIG" | sed -n '2p')
71
+ UMAMI_WEBSITE_ID=$(echo "$GEO_CONFIG" | sed -n '3p')
72
+ UMAMI_API=$(echo "$GEO_CONFIG" | sed -n '4p')
73
+ CLARITY_PROJECT=$(echo "$GEO_CONFIG" | sed -n '5p')
74
+ CREDENTIALS_FILE=$(echo "$GEO_CONFIG" | sed -n '6p')
75
+ GH_REPO=$(echo "$GEO_CONFIG" | sed -n '7p')
76
+ ```
77
+
78
+ ---
79
+
15
80
  ## Constants
16
81
 
17
82
  ```
18
- DOMAIN = "alterlab.io"
19
- SITE_URL = "https://alterlab.io"
20
- UMAMI_WEBSITE_ID = "ccfb1cdd-5a05-4b41-8e61-48695b4ff6c0"
21
- UMAMI_API = "https://umami.alterlab.io"
22
- CLARITY_PROJECT = "v28wblv737"
83
+ DOMAIN = (from forge.yaml → services.domain)
84
+ SITE_URL = (from forge.yaml → "https://" + services.domain)
85
+ UMAMI_WEBSITE_ID = (from forge.yaml → services.analytics.umami.website_id)
86
+ UMAMI_API = (from forge.yaml → services.analytics.umami.url)
87
+ CLARITY_PROJECT = (from forge.yaml → services.analytics.clarity.project_id)
88
+ CREDENTIALS_FILE = (from forge.yaml → paths.credentials.file)
89
+ GH_REPO = (from forge.yaml → project.owner + "/" + project.repo)
23
90
  TODAY = (current date in YYYY-MM-DD)
24
91
  THIRTY_DAYS_AGO = (TODAY minus 30 days)
25
92
 
@@ -41,11 +108,11 @@ AI_REFERRERS = [
41
108
 
42
109
  ### 1A. Umami — AI Referrer Metrics
43
110
 
44
- Authenticate with Umami (credentials from `credentials.yaml`):
111
+ Authenticate with Umami (credentials from credentials file set in forge.yaml):
45
112
  ```bash
46
- UMAMI_USER=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['umami']['username'])")
47
- UMAMI_PASS=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['umami']['password'])")
48
- UMAMI_TOKEN=$(curl -s -X POST "https://umami.alterlab.io/api/auth/login" \
113
+ UMAMI_USER=$(python3 -c "import yaml; print(yaml.safe_load(open('$CREDENTIALS_FILE'))['umami']['username'])")
114
+ UMAMI_PASS=$(python3 -c "import yaml; print(yaml.safe_load(open('$CREDENTIALS_FILE'))['umami']['password'])")
115
+ UMAMI_TOKEN=$(curl -s -X POST "$UMAMI_API/api/auth/login" \
49
116
  -H "Content-Type: application/json" \
50
117
  -d "{\"username\":\"$UMAMI_USER\",\"password\":\"$UMAMI_PASS\"}" | jq -r '.token')
51
118
  ```
@@ -54,7 +121,7 @@ Then pull referrer metrics for the last 30 days:
54
121
  ```bash
55
122
  START_AT=$(date -d '30 days ago' +%s)000
56
123
  END_AT=$(date +%s)000
57
- curl -s "https://umami.alterlab.io/api/websites/ccfb1cdd-5a05-4b41-8e61-48695b4ff6c0/metrics?startAt=${START_AT}&endAt=${END_AT}&type=referrer" \
124
+ curl -s "$UMAMI_API/api/websites/$UMAMI_WEBSITE_ID/metrics?startAt=${START_AT}&endAt=${END_AT}&type=referrer" \
58
125
  -H "Authorization: Bearer $UMAMI_TOKEN" | jq '.'
59
126
  ```
60
127
 
@@ -62,7 +129,7 @@ From the results, filter for AI_REFERRERS. Record each AI referrer's session cou
62
129
 
63
130
  Also pull landing page metrics to see WHERE AI traffic lands:
64
131
  ```bash
65
- curl -s "https://umami.alterlab.io/api/websites/ccfb1cdd-5a05-4b41-8e61-48695b4ff6c0/metrics?startAt=${START_AT}&endAt=${END_AT}&type=url" \
132
+ curl -s "$UMAMI_API/api/websites/$UMAMI_WEBSITE_ID/metrics?startAt=${START_AT}&endAt=${END_AT}&type=url" \
66
133
  -H "Authorization: Bearer $UMAMI_TOKEN" | jq '.'
67
134
  ```
68
135
 
@@ -70,7 +137,7 @@ For previous period comparison (30-60 days ago):
70
137
  ```bash
71
138
  PREV_START=$(date -d '60 days ago' +%s)000
72
139
  PREV_END=$(date -d '30 days ago' +%s)000
73
- curl -s "https://umami.alterlab.io/api/websites/ccfb1cdd-5a05-4b41-8e61-48695b4ff6c0/metrics?startAt=${PREV_START}&endAt=${PREV_END}&type=referrer" \
140
+ curl -s "$UMAMI_API/api/websites/$UMAMI_WEBSITE_ID/metrics?startAt=${PREV_START}&endAt=${PREV_END}&type=referrer" \
74
141
  -H "Authorization: Bearer $UMAMI_TOKEN" | jq '.'
75
142
  ```
76
143
 
@@ -96,17 +163,17 @@ Cross-reference Clarity referrer data with Umami to validate numbers.
96
163
 
97
164
  Get the sitemap to find all public pages:
98
165
  ```bash
99
- curl -s "https://alterlab.io/sitemap.xml" | grep -oP '<loc>\K[^<]+' | sort
166
+ curl -s "$SITE_URL/sitemap.xml" | grep -oP '<loc>\K[^<]+' | sort
100
167
  ```
101
168
 
102
169
  Also check llms.txt for listed pages:
103
170
  ```bash
104
- curl -s "https://alterlab.io/llms.txt"
171
+ curl -s "$SITE_URL/llms.txt"
105
172
  ```
106
173
 
107
174
  And the full version:
108
175
  ```bash
109
- curl -s "https://alterlab.io/llms-full.txt"
176
+ curl -s "$SITE_URL/llms-full.txt"
110
177
  ```
111
178
 
112
179
  Build the list of all unique public pages from both sources.
@@ -133,7 +200,7 @@ curl -s "PAGE_URL" | grep -oP '<script type="application/ld\+json">\K[^<]+' | jq
133
200
  ```bash
134
201
  curl -s "PAGE_URL" | grep -oP 'property="og:title" content="\K[^"]+'
135
202
  ```
136
- - PASS: og:title exists and is NOT the generic site default (e.g., not just "AlterLab" or empty)
203
+ - PASS: og:title exists and is NOT the generic site default (e.g., not just the project name or empty)
137
204
  - FAIL: missing or generic → flag "Missing/generic OG tags"
138
205
 
139
206
  Also check og:description:
@@ -152,7 +219,7 @@ Check if page URL appears in llms.txt content.
152
219
  ### 2C. Check robots.txt for AI Bots
153
220
 
154
221
  ```bash
155
- curl -s "https://alterlab.io/robots.txt"
222
+ curl -s "$SITE_URL/robots.txt"
156
223
  ```
157
224
 
158
225
  Known AI crawler user-agents to check for:
@@ -297,16 +364,35 @@ gh issue edit {NUMBER} --milestone "geo-ai-discoverability"
297
364
 
298
365
  ### Add GEO issues to Project board
299
366
 
300
- For each created issue, add to the GitHub Project. Reference `~/projects/forge/docs/WORKFLOW.md` → "Project Board Integration" for field IDs.
367
+ Read project board IDs from `forge.yaml → project_board`. If `project_board` is not configured, skip board integration.
301
368
 
302
369
  ```bash
303
- ISSUE_URL="https://github.com/RapierCraft/AlterLab/issues/${ISSUE_NUM}"
304
- ITEM_ID=$(gh project item-add 1 --owner RapierCraft --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
305
- if [ -n "$ITEM_ID" ]; then
306
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF6E --single-select-option-id f75ad846 2>/dev/null || true # Status=Todo
307
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF98 --single-select-option-id 62864af4 2>/dev/null || true # Lane=Fast
308
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF-o --single-select-option-id 214c4d65 2>/dev/null || true # Component=Platform
309
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF8o --single-select-option-id {PRIORITY_OPTION_ID} 2>/dev/null || true # Priority
370
+ # Read project board config from forge.yaml
371
+ BOARD_CONFIG=$(python3 - <<'PYEOF'
372
+ import yaml, sys
373
+ cfg = yaml.safe_load(open('forge.yaml'))
374
+ board = cfg.get('project_board', {})
375
+ if not board:
376
+ print('')
377
+ sys.exit(0)
378
+ print(board.get('owner', cfg.get('project', {}).get('owner', '')))
379
+ print(str(board.get('project_number', '')))
380
+ print(board.get('project_id', ''))
381
+ PYEOF
382
+ )
383
+
384
+ BOARD_OWNER=$(echo "$BOARD_CONFIG" | sed -n '1p')
385
+ BOARD_NUMBER=$(echo "$BOARD_CONFIG" | sed -n '2p')
386
+ BOARD_ID=$(echo "$BOARD_CONFIG" | sed -n '3p')
387
+
388
+ if [ -n "$BOARD_OWNER" ] && [ -n "$BOARD_NUMBER" ]; then
389
+ ISSUE_URL="https://github.com/$GH_REPO/issues/${ISSUE_NUM}"
390
+ ITEM_ID=$(gh project item-add "$BOARD_NUMBER" --owner "$BOARD_OWNER" --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
391
+ if [ -n "$ITEM_ID" ] && [ -n "$BOARD_ID" ]; then
392
+ # Set Status, Lane, Priority using field IDs from forge.yaml → project_board.field_ids
393
+ # See docs/CONFIG.md → project_board section for how to get field IDs
394
+ echo "Issue added to project board. Set Status/Lane/Priority manually or configure field_ids in forge.yaml."
395
+ fi
310
396
  fi
311
397
  ```
312
398
 
@@ -336,8 +422,10 @@ fi
336
422
 
337
423
  ## Error Handling
338
424
 
425
+ - **forge.yaml missing**: Print setup instructions and exit. Do not proceed without config.
426
+ - **services.analytics missing**: Print configuration instructions and exit.
339
427
  - **Umami auth fails**: Try SOPS credentials. If still broken, skip Umami and note in report.
340
428
  - **Clarity MCP fails**: Skip Clarity data, proceed with Umami only.
341
- - **curl to alterlab.io fails**: Report "Site unreachable — cannot run compliance checks" and only show referral data.
429
+ - **curl to site fails**: Report "Site unreachable — cannot run compliance checks" and only show referral data.
342
430
  - **No AI referral data at all**: Report "No AI referral traffic detected in the last 30 days" — this IS an insight (we need to improve discoverability).
343
431
  - **Rate limits or timeouts**: Note which checks were incomplete. Partial audit > no audit.
@@ -7,6 +7,9 @@ argument-hint: [issue number | "active" | "postmortem {issue}"]
7
7
 
8
8
  **Input**: $ARGUMENTS
9
9
 
10
+ **Config variables used by this command** (set in `forge.yaml`):
11
+ - `{REPO_PATH}` ← `paths.root` — project repository root
12
+
10
13
  You are the pipeline's incident response coordinator. When production goes down or a critical bug surfaces, this command orchestrates the response: validates the hotfix before deploy, reconstructs the incident timeline, and produces a post-incident analysis.
11
14
 
12
15
  **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`. User can override with `--model <name>`.
@@ -106,7 +109,7 @@ You are validating a hotfix for a P0 production incident.
106
109
 
107
110
  Issue: #{NUMBER} — {TITLE}
108
111
  Fix branch: {FIX_BRANCH}
109
- Repo: /home/mrdubey/projects/ScraperAPI/alterlab
112
+ Repo: {REPO_PATH}
110
113
 
111
114
  Your job:
112
115
  1. Read the fix diff: git diff origin/main..origin/{FIX_BRANCH}
package/commands/issue.md CHANGED
@@ -35,12 +35,20 @@ The user's input (`$ARGUMENTS`) can be:
35
35
 
36
36
  ### 1A: Resolve target repository
37
37
 
38
+ Read `forge.yaml` to build the routing table dynamically:
39
+
40
+ ```bash
41
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' forge.yaml)
42
+ GH_FLAG="-R $GH_REPO"
43
+ REPO_PATH=$(yq '.paths.root' forge.yaml)
44
+ STAGING_BRANCH=$(yq '.branches.staging' forge.yaml)
45
+ # Satellite repos from forge.yaml → repos.satellites (each has: prefix, repo, staging_branch)
46
+ ```
47
+
38
48
  | Prefix | Repository | GH_REPO | GH_FLAG |
39
49
  |--------|-----------|---------|---------|
40
- | (none) / `alterlab:` | AlterLab | `RapierCraft/AlterLab` | (none) |
41
- | `forge:` | Forge | `RapierCraftStudios/forge` | `-R RapierCraftStudios/forge` |
42
- | `mcp:` | MCP Server | `RapierCraft/alterlab-mcp-server` | `-R RapierCraft/alterlab-mcp-server` |
43
- | `n8n:` | n8n Node | `RapierCraft/n8n-nodes-alterlab` | `-R RapierCraft/n8n-nodes-alterlab` |
50
+ | (none) | Default project | `{GH_REPO}` from `forge.yaml → project.owner/repo` | (none / use `{GH_FLAG}`) |
51
+ | `{satellite_prefix}:` | Satellite repo | `{satellite.repo}` from `forge.yaml → repos.satellites` | `-R {satellite.repo}` |
44
52
 
45
53
  ### 1B: Classify issue type
46
54
 
@@ -88,21 +96,10 @@ Pick ONE primary category label:
88
96
 
89
97
  ### 2A: Identify the domain
90
98
 
91
- For AlterLab issues, map to domains and their key files:
92
-
93
- | Domain | Key Entry Points |
94
- |--------|-----------------|
95
- | BILLING | `routers/billing.py`, `core/pricing.py`, `services/credit_service.py` |
96
- | SCRAPING | `unified_consumer.py`, `queues.py`, `domain_playbooks.json` |
97
- | AUTH | `core/auth.py`, `routers/auth.py`, `dependencies.py` |
98
- | DATABASE | `infra/migrations/`, `models/`, `db/` |
99
- | FRONTEND | `web/src/app/`, `web/src/components/`, `web/src/lib/` |
100
- | CORTEX | `cortex_client.py`, `routers/cortex.py` |
101
- | INFRA | `.github/workflows/`, `docker-compose.yml`, `docker-compose.prod.yml`, `infra/traefik/` |
99
+ Map the issue to its domain and key files. Domain structure comes from the project — check `forge.yaml → review.context` and the project's `CLAUDE.md` for project-specific domain maps.
102
100
 
103
- For Forge issues: `commands/*.md`, `CLAUDE.md`, `install.sh`
104
- For MCP issues: read the repo's `src/` directory
105
- For n8n issues: read the repo's `nodes/` and `credentials/` directories
101
+ For ForgeDock issues (this repo): `commands/*.md`, `CLAUDE.md`, `install.sh`, `bin/`
102
+ For satellite repo issues: read the satellite repo's source directory (from `forge.yaml → repos.satellites`)
106
103
 
107
104
  ### 2B: Read the affected files
108
105
 
@@ -113,12 +110,12 @@ For n8n issues: read the repo's `nodes/` and `credentials/` directories
113
110
  3. **Import chains**: If the issue is about a function, check what imports it and what it imports.
114
111
 
115
112
  ```bash
116
- # For AlterLab — resolve the right branch to read from
117
- # Check staging first (most recent deployable state), then main
118
- git fetch origin staging main 2>/dev/null
113
+ # Resolve the right branch to read from
114
+ # Check the staging branch first (most recent deployable state), then the default branch
115
+ git fetch origin {STAGING_BRANCH} {DEFAULT_BRANCH} 2>/dev/null
119
116
 
120
117
  # Read the files relevant to the issue
121
- # Use git show origin/staging:{filepath} for AlterLab files
118
+ # Use git show origin/{STAGING_BRANCH}:{filepath} for project files
122
119
  ```
123
120
 
124
121
  ### 2C: Identify ALL affected files
@@ -423,11 +420,15 @@ else
423
420
  fi
424
421
  ```
425
422
 
426
- ### 4D: Add to Project board (AlterLab only)
423
+ ### 4D: Add to Project board (if configured)
427
424
 
428
425
  ```bash
429
- # For AlterLab issues, add to the project board
430
- gh project item-add {PROJECT_NUMBER} --owner RapierCraft --url "${ISSUE_URL}" 2>/dev/null || true
426
+ # Add to project board if forge.yaml → project_board is configured
427
+ PROJECT_NUMBER=$(yq '.project_board.project_number // ""' forge.yaml)
428
+ PROJECT_BOARD_OWNER=$(yq '.project_board.owner // .project.owner' forge.yaml)
429
+ if [ -n "$PROJECT_NUMBER" ] && [ "$PROJECT_NUMBER" != "null" ]; then
430
+ gh project item-add "$PROJECT_NUMBER" --owner "$PROJECT_BOARD_OWNER" --url "${ISSUE_URL}" 2>/dev/null || true
431
+ fi
431
432
  ```
432
433
 
433
434
  ### 4E: Report to user
@@ -13,28 +13,51 @@ Milestones are the top-level planning unit. They group related issues into a shi
13
13
 
14
14
  **You have access to ALL tools** — Task tool, Skill tool, sub-agents, everything.
15
15
 
16
+ ## Config Resolution
17
+
18
+ Read `forge.yaml` at the project root to resolve all project-specific variables before running any commands:
19
+
20
+ ```bash
21
+ # Parse forge.yaml for project context
22
+ GH_REPO=$(yq '.project.owner + "/" + .project.repo' forge.yaml)
23
+ GH_FLAG="-R $GH_REPO"
24
+ REPO_PATH=$(yq '.paths.root' forge.yaml)
25
+ PROJECT_NAME=$(yq '.project.name' forge.yaml)
26
+ STAGING_BRANCH=$(yq '.branches.staging' forge.yaml)
27
+ PROJECT_BOARD_OWNER=$(yq '.project_board.owner // .project.owner' forge.yaml)
28
+ PROJECT_NUMBER=$(yq '.project_board.project_number // "1"' forge.yaml)
29
+ PROJECT_ID=$(yq '.project_board.project_id' forge.yaml)
30
+ # Build satellite repo map from repos.satellites list
31
+ # Each satellite: { prefix, repo, staging_branch }
32
+ ```
33
+
34
+ All `{GH_REPO}`, `{GH_FLAG}`, `{REPO_PATH}`, `{PROJECT_NAME}`, `{STAGING_BRANCH}`, `{PROJECT_BOARD_OWNER}`, `{PROJECT_NUMBER}`, and `{PROJECT_ID}` references below are populated from `forge.yaml`.
35
+
36
+ ---
37
+
16
38
  ## Multi-Repo Support
17
39
 
18
- Milestones can span the AlterLab ecosystem. Project context (repo map, paths, conventions) comes from each project's `CLAUDE.md`.
40
+ Milestones can span multiple repositories. Project context (repo map, paths, conventions) comes from `forge.yaml`.
19
41
 
20
42
  ### Cross-Repo Milestones
21
43
 
22
- Some milestones (e.g., "BYOS Authenticated Scraping") require changes across multiple repos — the platform, SDKs, MCP server, etc. For these:
44
+ Some milestones require changes across multiple repos — the platform, SDKs, satellite services, etc. For these:
23
45
 
24
- - **The milestone is created in the primary repo** (usually `RapierCraft/AlterLab`)
25
- - **Issues are created in the repo where the code lives** — e.g., MCP server issues go to `RapierCraft/alterlab-mcp-server`
46
+ - **The milestone is created in the primary repo** (`{GH_REPO}` — the default repo from `forge.yaml`)
47
+ - **Issues are created in the repo where the code lives** — satellite repos come from `forge.yaml → repos.satellites`
26
48
  - **Each repo gets its own milestone branch** (`milestone/{slug}`) if it has issues in the milestone
27
- - **Issues reference the parent milestone** in their body: `Part of cross-repo milestone: **{TITLE}** (primary: RapierCraft/AlterLab)`
49
+ - **Issues reference the parent milestone** in their body: `Part of cross-repo milestone: **{TITLE}** (primary: {GH_REPO})`
28
50
 
29
51
  ### Repo Prefixes (same as `/work-on`)
30
52
 
53
+ Prefixes and repos are defined in `forge.yaml → repos.satellites`. Read the config to build the routing table:
54
+
31
55
  | Prefix | Repo |
32
56
  |--------|------|
33
- | _(none)_ | `RapierCraft/AlterLab` (default) |
34
- | `mcp:` | `RapierCraft/alterlab-mcp-server` |
35
- | `n8n:` | `RapierCraft/n8n-nodes-alterlab` |
57
+ | _(none)_ | `{GH_REPO}` (default) |
58
+ | `{SATELLITE_PREFIX}:` | `{SATELLITE_REPO}` (from `forge.yaml → repos.satellites`) |
36
59
 
37
- When decomposing a milestone, tag each proposed issue with its target repo. Use `-R` flag for non-default repos.
60
+ When decomposing a milestone, tag each proposed issue with its target repo. Use `-R {SATELLITE_REPO}` flag for non-default repos.
38
61
 
39
62
  ---
40
63
 
@@ -76,7 +99,7 @@ Capture the milestone number from the response.
76
99
  ### Step 3: Create the milestone branch
77
100
 
78
101
  ```bash
79
- cd {REPO_PATH} # default: /home/mrdubey/projects/ScraperAPI/alterlab
102
+ cd {REPO_PATH}
80
103
  git fetch origin main
81
104
  git branch milestone/{slug} origin/main
82
105
  git push origin milestone/{slug}
@@ -86,7 +109,7 @@ git push origin milestone/{slug}
86
109
 
87
110
  Use a Task sub-agent to analyze the milestone scope and propose issues:
88
111
 
89
- > You are planning the scope for milestone "{TITLE}" on the AlterLab platform.
112
+ > You are planning the scope for milestone "{TITLE}" on the {PROJECT_NAME} platform.
90
113
  >
91
114
  > **Milestone description**: {DESCRIPTION}
92
115
  >
@@ -95,19 +118,16 @@ Use a Task sub-agent to analyze the milestone scope and propose issues:
95
118
  > - Specific enough for the `/work-on` pipeline to pick up and implement
96
119
  > - Ordered by dependencies (if B needs A's API, A comes first)
97
120
  >
98
- > **AlterLab ecosystem context**:
99
- > - **Platform** (default): FastAPI backend (services/api/, services/worker/), Next.js frontend (web/), Docker microservices, PostgreSQL, Redis
100
- > - **Python SDK** (sdk/python/): Async client, httpx, pydantic
101
- > - **Node SDK** (sdk/node/): TypeScript client, axios
102
- > - **MCP Server** (alterlab-mcp-server/ — SEPARATE REPO): MCP tools for AI assistants, TypeScript, @modelcontextprotocol/sdk
103
- > - **n8n Node** (n8n-nodes-alterlab/ — SEPARATE REPO): n8n community node, TypeScript
104
- > - **Chrome Extension** (tools/chrome-extension/): Cookie capture for BYOS
121
+ > **Project ecosystem context** (from `forge.yaml`):
122
+ > - **Primary repo** (`{GH_REPO}`): The main project codebase at `{REPO_PATH}`
123
+ > - **Tech stack**: {review.tech_stack from forge.yaml, or read CLAUDE.md for project context}
124
+ > - **Satellite repos**: Read from `forge.yaml → repos.satellites` (each has a prefix and repo)
105
125
  >
106
- > Check the existing codebase to understand current architecture before proposing changes.
126
+ > Check the existing codebase at `{REPO_PATH}` to understand current architecture before proposing changes.
107
127
  >
108
128
  > **Output format**: Numbered list of proposed issues, each with:
109
- > - **Title**: Actionable, specific (e.g., "Add per-token billing engine for LLM extraction")
110
- > - **Repo**: Which repo this issue belongs to (`alterlab`, `mcp`, `n8n`, or platform default)
129
+ > - **Title**: Actionable, specific
130
+ > - **Repo**: Which repo this issue belongs to (default or satellite prefix from forge.yaml)
111
131
  > - **Type**: `feature`, `bug`, `refactor`, `infra`
112
132
  > - **Priority**: P1-P3
113
133
  > - **Size**: S (1-2 files), M (3-5 files), L (6+ files)
@@ -126,8 +146,8 @@ Present the proposed issues to the user. Ask:
126
146
  For each approved issue, create it in the **correct repo** based on the scope analysis:
127
147
 
128
148
  ```bash
129
- # For AlterLab issues (default):
130
- gh issue create \
149
+ # For default repo issues:
150
+ gh issue create {GH_FLAG} \
131
151
  --title "{fix|feat|refactor}: {issue_title}" \
132
152
  --label "{type},{priority}" \
133
153
  --milestone "{TITLE}" \
@@ -190,7 +210,7 @@ Files that need changes:
190
210
 
191
211
  ## Context
192
212
 
193
- Part of cross-repo milestone: **{MILESTONE_TITLE}** (primary: RapierCraft/AlterLab)
213
+ Part of cross-repo milestone: **{MILESTONE_TITLE}** (primary: {GH_REPO})
194
214
  **Type**: {feature|bug|refactor|infra}
195
215
 
196
216
  ## Dependencies
@@ -202,17 +222,17 @@ BODY_EOF
202
222
 
203
223
  ### Step 6B: Add issues to Project board
204
224
 
205
- For each created issue, add it to the GitHub Project board with initial fields. Reference `~/projects/forge/docs/WORKFLOW.md` → "Project Board Integration" for field IDs.
225
+ For each created issue, add it to the GitHub Project board with initial fields. Reference `forge.yaml → project_board` for field IDs, or `docs/CONFIG.md` → "Project Board" for lookup commands.
206
226
 
207
227
  ```bash
208
228
  for ISSUE_NUM in {created_issue_numbers}; do
209
229
  ISSUE_URL="https://github.com/{GH_REPO}/issues/${ISSUE_NUM}"
210
- ITEM_ID=$(gh project item-add 1 --owner RapierCraft --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
230
+ ITEM_ID=$(gh project item-add {PROJECT_NUMBER} --owner {PROJECT_BOARD_OWNER} --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
211
231
  if [ -n "$ITEM_ID" ]; then
212
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF6E --single-select-option-id f75ad846 2>/dev/null || true # Status=Todo
213
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF98 --single-select-option-id 4ff6f9e6 2>/dev/null || true # Lane=Feature
214
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF-o --single-select-option-id {COMPONENT_OPTION_ID} 2>/dev/null || true # Component (from repo — see WORKFLOW.md mapping)
215
- gh project item-edit --project-id PVT_kwHOCx3gR84BSK2L --id "$ITEM_ID" --field-id PVTSSF_lAHOCx3gR84BSK2Lzg_yF8o --single-select-option-id {PRIORITY_OPTION_ID} 2>/dev/null || true # Priority (from label)
232
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_STATUS_FIELD_ID} --single-select-option-id {PROJECT_BOARD_STATUS_TODO_ID} 2>/dev/null || true # Status=Todo
233
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_LANE_FIELD_ID} --single-select-option-id {PROJECT_BOARD_LANE_FEATURE_ID} 2>/dev/null || true # Lane=Feature
234
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_COMPONENT_FIELD_ID} --single-select-option-id {COMPONENT_OPTION_ID} 2>/dev/null || true # Component (from forge.yaml → project_board.fields)
235
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {PROJECT_BOARD_PRIORITY_FIELD_ID} --single-select-option-id {PRIORITY_OPTION_ID} 2>/dev/null || true # Priority (from label)
216
236
  fi
217
237
  done
218
238
  ```
@@ -308,13 +328,13 @@ Wait for confirmation.
308
328
  Before shipping, ensure the milestone branch is up to date with `staging` (picks up any fast-lane fixes that accumulated since the milestone branch was created):
309
329
 
310
330
  ```bash
311
- cd {REPO_PATH} # default: /home/mrdubey/projects/ScraperAPI/alterlab
312
- git fetch origin staging milestone/{slug}
331
+ cd {REPO_PATH}
332
+ git fetch origin {STAGING_BRANCH} milestone/{slug}
313
333
  git checkout milestone/{slug}
314
- git merge origin/staging --no-edit
334
+ git merge origin/{STAGING_BRANCH} --no-edit
315
335
  # If conflicts, report to user and STOP — do not auto-resolve
316
336
  git push origin milestone/{slug}
317
- git checkout staging
337
+ git checkout {STAGING_BRANCH}
318
338
  ```
319
339
 
320
340
  ### Step 2.5: Pre-Merge Hunk-Loss Audit (MANDATORY before creating PR)
@@ -325,12 +345,12 @@ This step identifies "at-risk hunks" — staging-only content in files the miles
325
345
 
326
346
  ```bash
327
347
  cd {REPO_PATH}
328
- git fetch origin staging milestone/{slug}
348
+ git fetch origin {STAGING_BRANCH} milestone/{slug}
329
349
 
330
350
  echo "=== Pre-Merge Hunk-Loss Audit ==="
331
351
 
332
- # Files the milestone branch changes vs staging
333
- MILESTONE_FILES=$(git diff --name-only origin/staging...origin/milestone/{slug})
352
+ # Files the milestone branch changes vs the staging branch
353
+ MILESTONE_FILES=$(git diff --name-only origin/{STAGING_BRANCH}...origin/milestone/{slug})
334
354
 
335
355
  if [ -z "$MILESTONE_FILES" ]; then
336
356
  echo "No files changed by milestone vs staging — nothing to audit. Proceeding to PR creation."
@@ -339,8 +359,8 @@ else
339
359
  # NOTE: $MILESTONE_FILES is intentionally unquoted here — word-splitting is required to pass
340
360
  # each filename as a separate path argument to git diff. Quoting would collapse the newline-
341
361
  # separated list into a single argument matching no real path, silently producing an empty diff.
342
- # Filenames with spaces are not present in this codebase (AlterLab/Forge use standard paths).
343
- AT_RISK=$(git diff origin/milestone/{slug}...origin/staging -- $MILESTONE_FILES 2>/dev/null)
362
+ # Filenames with spaces are not expected in standard project paths.
363
+ AT_RISK=$(git diff origin/milestone/{slug}...origin/{STAGING_BRANCH} -- $MILESTONE_FILES 2>/dev/null)
344
364
  AT_RISK_COUNT=$(echo "$AT_RISK" | grep -c '^@@' 2>/dev/null || echo 0)
345
365
 
346
366
  if [ "$AT_RISK_COUNT" -eq 0 ]; then
@@ -350,9 +370,9 @@ else
350
370
  echo "Affected files:"
351
371
  echo "$AT_RISK" | grep '^--- a/' | sed 's|^--- a/||'
352
372
 
353
- echo "Rebasing milestone/{slug} onto origin/staging to absorb at-risk hunks..."
373
+ echo "Rebasing milestone/{slug} onto origin/{STAGING_BRANCH} to absorb at-risk hunks..."
354
374
  git checkout milestone/{slug}
355
- git rebase origin/staging
375
+ git rebase origin/{STAGING_BRANCH}
356
376
 
357
377
  REBASE_EXIT=$?
358
378
  if [ "$REBASE_EXIT" -ne 0 ]; then
@@ -362,21 +382,21 @@ else
362
382
 
363
383
  # Abort the failed rebase and return to a clean state
364
384
  git rebase --abort 2>/dev/null || true
365
- git checkout staging
385
+ git checkout {STAGING_BRANCH}
366
386
 
367
387
  # Truncate AT_RISK to safe size for issue body (avoid shell quoting issues with large diffs)
368
388
  AT_RISK_SNIPPET=$(echo "$AT_RISK" | head -60 | sed "s/'/'\\\\''/g")
369
389
 
370
- gh issue create -R {GH_REPO} \
390
+ gh issue create {GH_FLAG} \
371
391
  --title "fix(milestone): rebase conflict during hunk-loss audit for milestone/{slug}" \
372
392
  --body "$(cat <<ISSUE_EOF
373
393
  ## Problem
374
394
 
375
- The pre-merge hunk-loss audit for milestone \`{slug}\` detected $AT_RISK_COUNT at-risk staging hunks, then attempted to rebase \`milestone/{slug}\` onto \`origin/staging\` to absorb them. The rebase conflicted and was aborted. Manual resolution is required before the milestone can ship.
395
+ The pre-merge hunk-loss audit for milestone \`{slug}\` detected $AT_RISK_COUNT at-risk staging hunks, then attempted to rebase \`milestone/{slug}\` onto \`origin/{STAGING_BRANCH}\` to absorb them. The rebase conflicted and was aborted. Manual resolution is required before the milestone can ship.
376
396
 
377
397
  ## Root Cause
378
398
 
379
- Divergent changes between \`milestone/{slug}\` and \`origin/staging\` cannot be automatically rebased. Conflicting files must be resolved manually.
399
+ Divergent changes between \`milestone/{slug}\` and \`origin/{STAGING_BRANCH}\` cannot be automatically rebased. Conflicting files must be resolved manually.
380
400
 
381
401
  ## Affected Files
382
402
 
@@ -401,7 +421,7 @@ ${AT_RISK_SNIPPET}
401
421
  Resolve the rebase conflict manually:
402
422
  \`\`\`bash
403
423
  git checkout milestone/{slug}
404
- git rebase origin/staging
424
+ git rebase origin/{STAGING_BRANCH}
405
425
  # resolve conflicts in the files listed above
406
426
  git rebase --continue
407
427
  git push origin milestone/{slug} --force-with-lease
@@ -421,7 +441,7 @@ ISSUE_EOF
421
441
  # Rebase succeeded — push the rebased branch
422
442
  git push origin milestone/{slug} --force-with-lease
423
443
  echo "Rebase complete. Milestone branch now includes all $AT_RISK_COUNT at-risk staging hunks."
424
- git checkout staging
444
+ git checkout {STAGING_BRANCH}
425
445
  fi
426
446
  fi
427
447
  ```
@@ -430,11 +450,11 @@ fi
430
450
 
431
451
  ```bash
432
452
  # Get the full diff summary (computed after Step 2.5 rebase, so it reflects the final state)
433
- DIFF_STATS=$(git diff origin/staging...origin/milestone/{slug} --stat)
434
- COMMIT_LOG=$(git log origin/staging..origin/milestone/{slug} --oneline)
453
+ DIFF_STATS=$(git diff origin/{STAGING_BRANCH}...origin/milestone/{slug} --stat)
454
+ COMMIT_LOG=$(git log origin/{STAGING_BRANCH}..origin/milestone/{slug} --oneline)
435
455
 
436
- gh pr create \
437
- --base staging \
456
+ gh pr create {GH_FLAG} \
457
+ --base {STAGING_BRANCH} \
438
458
  --head milestone/{slug} \
439
459
  --title "Ship: {MILESTONE_TITLE}" \
440
460
  --body "$(cat <<'PR_EOF'
@@ -446,8 +466,8 @@ gh pr create \
446
466
  {list of all closed issues in this milestone with PR references}
447
467
 
448
468
  ## Hunk-Loss Audit
449
- {If Step 2.5 rebase ran: "✓ Rebase completed — {AT_RISK_COUNT} at-risk staging hunks absorbed into milestone branch before this PR was created. All staging content is preserved."}
450
- {If Step 2.5 found no at-risk hunks: "✓ Clean — no at-risk staging hunks detected. Safe to merge."}
469
+ {If Step 2.5 rebase ran: "✓ Rebase completed — {AT_RISK_COUNT} at-risk staging-branch hunks absorbed into milestone branch before this PR was created. All staging content is preserved."}
470
+ {If Step 2.5 found no at-risk hunks: "✓ Clean — no at-risk staging-branch hunks detected. Safe to merge."}
451
471
 
452
472
  > ⚠ **IMPORTANT: Merge this PR using a MERGE COMMIT, not squash.** Squash-merging a milestone PR can silently drop staging-only hunks in files the milestone touches. Use the "Create a merge commit" option in the GitHub UI, or `gh pr merge {PR_NUMBER} --merge`.
453
473
 
@@ -544,19 +564,20 @@ To deploy: merge `staging → main` via GitHub web UI when ready.
544
564
 
545
565
  ## Action: Sync Milestone Branch
546
566
 
547
- Syncs the milestone branch with the latest `main` to pick up fast-lane fixes. Run periodically on long-lived milestones.
567
+ Syncs the milestone branch with the latest default branch to pick up fast-lane fixes. Run periodically on long-lived milestones.
548
568
 
549
569
  ```bash
550
- cd {REPO_PATH} # default: /home/mrdubey/projects/ScraperAPI/alterlab
551
- git fetch origin main milestone/{slug}
570
+ cd {REPO_PATH}
571
+ DEFAULT_BRANCH=$(yq '.branches.default' forge.yaml)
572
+ git fetch origin $DEFAULT_BRANCH milestone/{slug}
552
573
  git checkout milestone/{slug}
553
- git merge origin/main --no-edit
574
+ git merge origin/$DEFAULT_BRANCH --no-edit
554
575
  # If conflicts, report to user and STOP
555
576
  git push origin milestone/{slug}
556
- git checkout main
577
+ git checkout $DEFAULT_BRANCH
557
578
  ```
558
579
 
559
- Report: "Synced milestone/{slug} with latest main. {N} new commits from main incorporated."
580
+ Report: "Synced milestone/{slug} with latest `{DEFAULT_BRANCH}`. {N} new commits incorporated."
560
581
 
561
582
  ---
562
583