forgedock 1.0.0 → 1.0.2

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 (42) hide show
  1. package/README.md +19 -2
  2. package/bin/forgedock.mjs +286 -10
  3. package/commands/analytics.md +129 -52
  4. package/commands/audit-agents.md +29 -5
  5. package/commands/audit.md +55 -17
  6. package/commands/autopilot.md +11 -6
  7. package/commands/cleanup.md +44 -24
  8. package/commands/deploy-info.md +7 -3
  9. package/commands/forge-stats.md +13 -4
  10. package/commands/forgedock-init.md +534 -0
  11. package/commands/geo-audit.md +114 -26
  12. package/commands/incident-response.md +4 -1
  13. package/commands/issue.md +26 -25
  14. package/commands/milestone.md +80 -59
  15. package/commands/orchestrate.md +38 -21
  16. package/commands/pipeline-health.md +26 -15
  17. package/commands/qa-sweep.md +68 -5
  18. package/commands/quality-gate.md +18 -0
  19. package/commands/review-pr-agents.md +58 -90
  20. package/commands/review-pr.md +58 -16
  21. package/commands/rollback.md +8 -5
  22. package/commands/security-audit.md +68 -12
  23. package/commands/sync-ecosystem.md +196 -156
  24. package/commands/validate.md +1 -1
  25. package/commands/work-on/build/architect.md +2 -2
  26. package/commands/work-on/build/context.md +2 -2
  27. package/commands/work-on/build/implement.md +3 -3
  28. package/commands/work-on/build/validate.md +3 -3
  29. package/commands/work-on/build.md +6 -6
  30. package/commands/work-on/close.md +59 -30
  31. package/commands/work-on/decompose.md +6 -6
  32. package/commands/work-on/investigate.md +16 -20
  33. package/commands/work-on/review.md +4 -4
  34. package/commands/work-on-monolithic.md +15 -16
  35. package/commands/work-on.md +45 -33
  36. package/docs/CODEX.md +1 -1
  37. package/docs/CONFIG.md +398 -0
  38. package/docs/WORKFLOW.md +115 -72
  39. package/package.json +2 -2
  40. package/scripts/verify-host-headers.sh +14 -9
  41. package/scripts/verify-route-registration.sh +10 -10
  42. package/commands/failure-recon.md +0 -375
@@ -14,15 +14,91 @@ You are the analytics orchestrator. Pull data from ALL available analytics platf
14
14
 
15
15
  ---
16
16
 
17
+ ## Config Preamble (REQUIRED — run before anything else)
18
+
19
+ **Before any data collection, credentials access, or phase execution**, read `forge.yaml` from the project root and resolve all constants. If the config is missing or incomplete, stop and tell the user what to add.
20
+
21
+ ```bash
22
+ # Locate forge.yaml (project root = directory containing the forge.yaml file)
23
+ FORGE_YAML="${FORGE_CONFIG:-$(git rev-parse --show-toplevel 2>/dev/null)/forge.yaml}"
24
+
25
+ if [ ! -f "$FORGE_YAML" ]; then
26
+ echo "ERROR: forge.yaml not found at $FORGE_YAML"
27
+ echo ""
28
+ echo "The /analytics command requires forge.yaml to be configured."
29
+ echo "Run: cp forge.yaml.example forge.yaml"
30
+ echo "Then fill in the required sections (project, paths, branches) and the"
31
+ echo "optional services.analytics section for this command."
32
+ exit 1
33
+ fi
34
+
35
+ # Read config with Python (YAML parser — values are shell-quoted via shlex.quote)
36
+ ANALYTICS_CONFIG=$(python3 -c "
37
+ import yaml, sys, shlex
38
+ cfg = yaml.safe_load(open(sys.argv[1]))
39
+ svc = cfg.get('services', {})
40
+ analytics = svc.get('analytics', None)
41
+ if analytics is None:
42
+ print('MISSING_ANALYTICS_CONFIG')
43
+ sys.exit(0)
44
+ paths = cfg.get('paths', {})
45
+ creds = paths.get('credentials', {})
46
+ project = cfg.get('project', {})
47
+ board = cfg.get('project_board', {})
48
+ print('CREDENTIALS_FILE=' + shlex.quote(str(creds.get('file', ''))))
49
+ print('DOMAIN=' + shlex.quote(str(svc.get('domain', ''))))
50
+ print('SITE_URL=' + shlex.quote(str(svc.get('gsc_property', ''))))
51
+ print('HISTORY_FILE=' + shlex.quote(str(analytics.get('history_file', ''))))
52
+ print('UMAMI_URL=' + shlex.quote(str(analytics.get('umami', {}).get('url', ''))))
53
+ print('UMAMI_WEBSITE_ID=' + shlex.quote(str(analytics.get('umami', {}).get('website_id', ''))))
54
+ print('GA4_PROPERTY_ID=' + shlex.quote(str(analytics.get('ga4', {}).get('property_id', ''))))
55
+ print('GA4_SERVICE_ACCOUNT_KEY=' + shlex.quote(str(analytics.get('ga4', {}).get('service_account_key', ''))))
56
+ print('REPO_PATH=' + shlex.quote(str(cfg.get('paths', {}).get('root', ''))))
57
+ print('GH_REPO=' + shlex.quote(str(project.get('owner', '')) + '/' + str(project.get('repo', ''))))
58
+ print('PROJECT_BOARD_OWNER=' + shlex.quote(str(board.get('owner', project.get('owner', '')))))
59
+ print('PROJECT_NUMBER=' + shlex.quote(str(board.get('project_number', ''))))
60
+ print('PROJECT_ID=' + shlex.quote(str(board.get('project_id', ''))))
61
+ " "$FORGE_YAML")
62
+
63
+ if echo "$ANALYTICS_CONFIG" | grep -q "MISSING_ANALYTICS_CONFIG"; then
64
+ echo "ERROR: forge.yaml is missing the 'services.analytics' section."
65
+ echo ""
66
+ echo "Add the following to your forge.yaml to use /analytics:"
67
+ echo ""
68
+ echo " services:"
69
+ echo " domain: \"your-domain.com\""
70
+ echo " gsc_property: \"https://your-domain.com\""
71
+ echo " analytics:"
72
+ echo " history_file: \"/path/to/analytics-history.yaml\""
73
+ echo " umami:"
74
+ echo " url: \"https://umami.your-domain.com\""
75
+ echo " website_id: \"your-umami-website-id\""
76
+ echo " ga4:"
77
+ echo " property_id: \"your-ga4-property-id\""
78
+ echo " service_account_key: \"/path/to/ga4-service-account.json\""
79
+ echo ""
80
+ echo "See docs/CONFIG.md for the full services.analytics schema."
81
+ exit 1
82
+ fi
83
+
84
+ # Export resolved constants — all downstream phases use these variables
85
+ # Values are shell-quoted by the Python block above; eval is safe against metacharacters in forge.yaml values
86
+ eval "$ANALYTICS_CONFIG"
87
+ ```
88
+
89
+ All downstream phases use these resolved constants: `{CREDENTIALS_FILE}`, `{DOMAIN}`, `{SITE_URL}`, `{HISTORY_FILE}`, `{UMAMI_URL}`, `{UMAMI_WEBSITE_ID}`, `{GA4_PROPERTY_ID}`, `{GA4_SERVICE_ACCOUNT_KEY}`, `{REPO_PATH}`, `{GH_REPO}`.
90
+
91
+ ---
92
+
17
93
  ## Credentials
18
94
 
19
- **ALL credentials live in one file**: `/home/mrdubey/projects/ScraperAPI/credentials.yaml`
95
+ **ALL credentials live in one file** at the path resolved from `forge.yaml → paths.credentials.file` (`{CREDENTIALS_FILE}`).
20
96
 
21
97
  Agents MUST read this file for API keys, tokens, and login creds. Do NOT grep SOPS, do NOT hardcode tokens, do NOT look in `.mcp.json`.
22
98
 
23
99
  ```bash
24
100
  # Read any credential:
25
- python3 -c "import yaml; creds=yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml')); print(creds['section']['key'])"
101
+ python3 -c "import yaml; creds=yaml.safe_load(open('{CREDENTIALS_FILE}')); print(creds['section']['key'])"
26
102
  ```
27
103
 
28
104
  ---
@@ -30,16 +106,17 @@ python3 -c "import yaml; creds=yaml.safe_load(open('/home/mrdubey/projects/Scrap
30
106
  ## Constants
31
107
 
32
108
  ```
33
- CREDENTIALS_FILE = "/home/mrdubey/projects/ScraperAPI/credentials.yaml"
34
- SITE_URL = "sc-domain:alterlab.io"
35
- DOMAIN = "alterlab.io"
109
+ CREDENTIALS_FILE = {forge.yaml → paths.credentials.file}
110
+ SITE_URL = {forge.yaml → services.gsc_property}
111
+ DOMAIN = {forge.yaml → services.domain}
112
+ HISTORY_FILE = {forge.yaml → services.analytics.history_file}
36
113
  TODAY = (current date in YYYY-MM-DD)
37
114
  SEVEN_DAYS_AGO = (TODAY minus 7 days)
38
115
  TWENTY_EIGHT_DAYS_AGO = (TODAY minus 28 days)
39
116
  SEO_RECENT_CHANGE_DAYS = 30
40
117
  ```
41
118
 
42
- All platform-specific constants (project IDs, zone IDs, API keys, base URLs) are in `credentials.yaml` — read them from there.
119
+ All platform-specific constants (project IDs, zone IDs, API keys, base URLs) are in `{CREDENTIALS_FILE}` — read them from there.
43
120
 
44
121
  ---
45
122
 
@@ -48,18 +125,18 @@ All platform-specific constants (project IDs, zone IDs, API keys, base URLs) are
48
125
  **Before any data collection**, read the persistent audit history file. This gives you a multi-run baseline for trend analysis and tells you what issues were created in past audits so you can measure their impact and avoid duplicates.
49
126
 
50
127
  ```bash
51
- HISTORY_FILE="/home/mrdubey/projects/ScraperAPI/analytics-history.yaml"
128
+ HISTORY_FILE="{HISTORY_FILE}"
52
129
 
53
130
  if [ -f "$HISTORY_FILE" ]; then
54
131
  HISTORY_AVAILABLE=true
55
- # Read last 5 audit snapshots (Python for YAML parsing — same pattern as credentials.yaml)
132
+ # Read last 5 audit snapshots (Python for YAML parsing — same pattern as credentials file)
56
133
  python3 -c "
57
- import yaml, json
58
- history = yaml.safe_load(open('$HISTORY_FILE')) or []
134
+ import yaml, json, sys
135
+ history = yaml.safe_load(open(sys.argv[1])) or []
59
136
  # Keep only last 5 entries
60
137
  recent = history[-5:] if len(history) >= 5 else history
61
138
  print(json.dumps(recent, indent=2))
62
- "
139
+ " "$HISTORY_FILE"
63
140
  else
64
141
  HISTORY_AVAILABLE=false
65
142
  echo 'First audit — no historical baseline. History file will be created at end of this run.'
@@ -83,7 +160,7 @@ Launch ALL 7 data collection agents simultaneously using `run_in_background=true
83
160
 
84
161
  Launch as a background Agent (subagent_type: general-purpose, model: sonnet):
85
162
 
86
- **Mission**: Collect Google Search Console data for alterlab.io. Use the MCP GSC tools directly.
163
+ **Mission**: Collect Google Search Console data for `{DOMAIN}`. Use the MCP GSC tools directly.
87
164
 
88
165
  **Queries to run:**
89
166
 
@@ -95,7 +172,7 @@ Launch as a background Agent (subagent_type: general-purpose, model: sonnet):
95
172
  6. **Quick wins** — `mcp__gsc__detect_quick_wins` with minImpressions: 30, positionRangeMin: 4, positionRangeMax: 20, maxCtr: 3
96
173
  7. **Previous 28 days** (for trend comparison) — same query/page queries but for the prior period
97
174
 
98
- All queries use `siteUrl: "sc-domain:alterlab.io"`.
175
+ All queries use `siteUrl: "{SITE_URL}"`.
99
176
 
100
177
  **Return**: Total clicks, impressions, avg CTR, avg position (current + previous + % change). Top 20 queries and pages. Quick wins list. Device/country splits. Rising and declining queries.
101
178
 
@@ -133,14 +210,14 @@ Launch as a background Agent (subagent_type: general-purpose, model: sonnet):
133
210
 
134
211
  **Mission**: Collect Umami analytics via REST API.
135
212
 
136
- **Auth**: Read credentials from `credentials.yaml` (under `umami:`), then authenticate:
213
+ **Auth**: Read credentials from `{CREDENTIALS_FILE}` (under `umami:`), then authenticate:
137
214
  ```bash
138
215
  # Read umami creds
139
- python3 -c "import yaml; c=yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml')); print(c['umami']['username'], c['umami']['password'])"
140
- # Login: POST https://umami.alterlab.io/api/auth/login with {"username": "...", "password": "..."}
216
+ python3 -c "import yaml; c=yaml.safe_load(open('{CREDENTIALS_FILE}')); print(c['umami']['username'], c['umami']['password'])"
217
+ # Login: POST {UMAMI_URL}/api/auth/login with {"username": "...", "password": "..."}
141
218
  ```
142
219
 
143
- **Queries** (all use website ID `ccfb1cdd-5a05-4b41-8e61-48695b4ff6c0`, last 28 days):
220
+ **Queries** (all use website ID `{UMAMI_WEBSITE_ID}`, last 28 days):
144
221
  - `/api/websites/{id}/stats` — overall stats
145
222
  - `/api/websites/{id}/pageviews?unit=day` — daily trend
146
223
  - `/api/websites/{id}/metrics?type=url` — top pages
@@ -160,13 +237,13 @@ Launch as a background Agent (subagent_type: general-purpose, model: sonnet):
160
237
 
161
238
  **Mission**: Collect Cloudflare traffic/performance data via GraphQL API.
162
239
 
163
- **Auth**: Read from `credentials.yaml`:
240
+ **Auth**: Read from `{CREDENTIALS_FILE}`:
164
241
  ```bash
165
- CF_TOKEN=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['cloudflare']['api_token'])")
166
- CF_ZONE=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['cloudflare']['zone_id'])")
242
+ CF_TOKEN=$(python3 -c "import yaml; print(yaml.safe_load(open('{CREDENTIALS_FILE}'))['cloudflare']['api_token'])")
243
+ CF_ZONE=$(python3 -c "import yaml; print(yaml.safe_load(open('{CREDENTIALS_FILE}'))['cloudflare']['zone_id'])")
167
244
  ```
168
245
 
169
- **GraphQL queries** (zone from `credentials.yaml → cloudflare.zone_id`, last 7 days):
246
+ **GraphQL queries** (zone from `{CREDENTIALS_FILE} → cloudflare.zone_id`, last 7 days):
170
247
  1. HTTP requests daily — requests, pageViews, bytes, cachedBytes, threats, countryMap, uniques
171
248
  2. Status code breakdown — responseStatusMap
172
249
  3. Threats/bot traffic — threatPathingMap
@@ -181,17 +258,17 @@ CF_ZONE=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/proj
181
258
 
182
259
  Launch as a background Agent (subagent_type: general-purpose, model: sonnet):
183
260
 
184
- **Mission**: Collect Bing Webmaster Tools data for alterlab.io via REST API. Bing powers ChatGPT search, so Bing indexing/ranking data = AI discoverability signal.
261
+ **Mission**: Collect Bing Webmaster Tools data for `{DOMAIN}` via REST API. Bing powers ChatGPT search, so Bing indexing/ranking data = AI discoverability signal.
185
262
 
186
- **Auth**: Read from `credentials.yaml`:
263
+ **Auth**: Read from `{CREDENTIALS_FILE}`:
187
264
  ```bash
188
- BING_KEY=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['bing_webmaster']['api_key'])")
189
- BING_SITE=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['bing_webmaster']['site_url'])")
190
- BING_BASE=$(python3 -c "import yaml; print(yaml.safe_load(open('/home/mrdubey/projects/ScraperAPI/credentials.yaml'))['bing_webmaster']['api_base'])")
265
+ BING_KEY=$(python3 -c "import yaml; print(yaml.safe_load(open('{CREDENTIALS_FILE}'))['bing_webmaster']['api_key'])")
266
+ BING_SITE=$(python3 -c "import yaml; print(yaml.safe_load(open('{CREDENTIALS_FILE}'))['bing_webmaster']['site_url'])")
267
+ BING_BASE=$(python3 -c "import yaml; print(yaml.safe_load(open('{CREDENTIALS_FILE}'))['bing_webmaster']['api_base'])")
191
268
  ```
192
269
 
193
270
  **Auth method**: Query param `?apikey={BING_KEY}`
194
- **Site URL**: URL-encode site_url as `https%3A%2F%2Falterlab.io` in siteUrl param
271
+ **Site URL**: URL-encode the site_url value from `{CREDENTIALS_FILE} → bing_webmaster.site_url` in siteUrl param
195
272
 
196
273
  **Queries to run:**
197
274
 
@@ -233,20 +310,20 @@ Launch as a background Agent (subagent_type: general-purpose, model: sonnet):
233
310
 
234
311
  **Auth**: JWT-based service account authentication:
235
312
  ```bash
236
- # Service account key at:
237
- SA_KEY="/home/mrdubey/projects/ScraperAPI/credentials/ga4-service-account.json"
238
- GA4_PROPERTY_ID="522967190"
313
+ # Service account key and property ID resolved from forge.yaml:
314
+ SA_KEY="{GA4_SERVICE_ACCOUNT_KEY}"
315
+ GA4_PROPERTY_ID="{GA4_PROPERTY_ID}"
239
316
 
240
317
  # Generate JWT and exchange for access token:
241
318
  python3 -c "
242
- import json, time, jwt, requests
243
- sa = json.load(open('$SA_KEY'))
319
+ import json, sys, time, jwt, requests
320
+ sa = json.load(open(sys.argv[1]))
244
321
  now = int(time.time())
245
322
  payload = {'iss': sa['client_email'], 'scope': 'https://www.googleapis.com/auth/analytics.readonly', 'aud': sa['token_uri'], 'iat': now, 'exp': now + 3600}
246
323
  signed = jwt.encode(payload, sa['private_key'], algorithm='RS256')
247
324
  token = requests.post(sa['token_uri'], data={'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer', 'assertion': signed}).json()['access_token']
248
325
  print(token)
249
- "
326
+ " "$SA_KEY"
250
327
  ```
251
328
 
252
329
  If `jwt` (PyJWT) is not installed, use: `pip install PyJWT requests`
@@ -365,17 +442,17 @@ After tracking integrity is validated, synthesize findings. Write down key data
365
442
 
366
443
  **Quick wins (with git history check — hard gate)**: GSC queries at position 4-20 with decent impressions but low CTR = title/meta optimization. **BUT FIRST**: For every page you'd propose a title/meta rewrite, run the git history check using `SEO_RECENT_CHANGE_DAYS`:
367
444
  ```bash
368
- git -C /home/mrdubey/projects/ScraperAPI/alterlab log --since="${SEO_RECENT_CHANGE_DAYS} days ago" --oneline -- web/src/app/<page>/page.tsx
445
+ git -C "{REPO_PATH}" log --since="${SEO_RECENT_CHANGE_DAYS} days ago" --oneline -- web/src/app/<page>/page.tsx
369
446
  ```
370
447
  For blog posts stored in DB, check migration files:
371
448
  ```bash
372
- grep -rl "<slug>" /home/mrdubey/projects/ScraperAPI/alterlab/infra/migrations/ 2>/dev/null
449
+ grep -rl "<slug>" "{REPO_PATH}/infra/migrations/" 2>/dev/null
373
450
  ```
374
451
  **If metadata changed within `SEO_RECENT_CHANGE_DAYS` days**: **Do NOT add to Proposed Actions.** Instead, add the page to the **"Recently Updated — Monitor"** section of the report (see Phase 3 template) with the note: "Updated {date} — allow 1 SEO cycle before re-evaluating. Current GSC data may reflect pre-change state." Only pages with stable metadata (unchanged for more than `SEO_RECENT_CHANGE_DAYS` days) may appear in Proposed Actions for SEO rewrites.
375
452
 
376
453
  Quantify the opportunity (e.g., "500 impressions/month at 1.2% CTR → industry avg 5% at position 6 → ~19 extra clicks/month").
377
454
 
378
- **Performance**: Clarity CWV ratings cross-referenced with GSC mobile data — poor mobile CWV hurts mobile rankings. Cloudflare cache hit ratio — **interpret in context**: AlterLab is API-first, so dynamic HTML+JSON responses should NOT be cached. Only flag cache issues if static assets (JS, CSS, images, fonts) have low cache rates. Check content type breakdown from Cloudflare data before claiming "low cache rate."
455
+ **Performance**: Clarity CWV ratings cross-referenced with GSC mobile data — poor mobile CWV hurts mobile rankings. Cloudflare cache hit ratio — **interpret in context**: API-first projects have low cache rates by design, so dynamic HTML+JSON responses should NOT be cached. Only flag cache issues if static assets (JS, CSS, images, fonts) have low cache rates. Check content type breakdown from Cloudflare data before claiming "low cache rate."
379
456
 
380
457
  **Bing vs Google divergence**: Compare Bing Webmaster keyword rankings with GSC. Keywords ranking well on Bing but not Google (or vice versa) reveal optimization gaps. Bing rankings matter for ChatGPT search discoverability. **But**: only flag as actionable if the query has >50 impressions on the platform where ranking is weak. Below 50, position data is too noisy.
381
458
 
@@ -400,7 +477,7 @@ Before treating any event count as meaningful:
400
477
  - Cloudflare threat count > 50% of total requests → subtract bot traffic from ALL per-request metrics
401
478
 
402
479
  **Under-counting awareness** (developer audience):
403
- - AlterLab targets developers. Developers run ad blockers at 3-5x the general population rate.
480
+ - Developer-focused projects see ad blockers at 3-5x the general population rate.
404
481
  - GA4 event counts likely undercount true user actions by 20-50%.
405
482
  - Never create a "low conversion rate" issue if Stripe successful charges roughly match or exceed analytics purchase events — the funnel is healthy, tracking is lossy.
406
483
  - Self-hosted Umami is more reliable than GA4 for this audience — prefer Umami event counts when they diverge.
@@ -450,7 +527,7 @@ Every candidate finding must pass this gate. If any check fails, downgrade to "O
450
527
  - [ ] **Not already tracked**: Before adding a finding to Proposed Actions, check if an open issue in history's `issues_created` list already covers the same finding:
451
528
  ```bash
452
529
  # For each issue number from history's issues_created:
453
- gh issue view {PAST_ISSUE_NUMBER} -R RapierCraft/AlterLab --json state,title --jq '{state, title}'
530
+ gh issue view {PAST_ISSUE_NUMBER} -R {GH_REPO} --json state,title --jq '{state, title}'
454
531
  # If state=OPEN and the title/category matches the current finding → skip creation, note "Already tracked in #{PAST_ISSUE_NUMBER}"
455
532
  ```
456
533
  If the past issue is closed (fixed), the finding may recur — create a new issue and note the recurrence.
@@ -654,16 +731,16 @@ If an open milestone fits, offer to assign issues to it.
654
731
 
655
732
  ### Add analytics issues to Project board
656
733
 
657
- For each created issue, add it to the GitHub Project. Reference `~/projects/forge/docs/WORKFLOW.md` → "Project Board Integration" for field IDs.
734
+ For each created issue, add it to the GitHub Project. Field IDs are resolved from `forge.yaml → project_board.field_ids` (see the ForgeDock docs for the full project board configuration schema).
658
735
 
659
736
  ```bash
660
- ISSUE_URL="https://github.com/RapierCraft/AlterLab/issues/${ISSUE_NUM}"
661
- ITEM_ID=$(gh project item-add 1 --owner RapierCraft --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
737
+ ISSUE_URL="https://github.com/{GH_REPO}/issues/${ISSUE_NUM}"
738
+ ITEM_ID=$(gh project item-add {PROJECT_NUMBER} --owner {PROJECT_BOARD_OWNER} --url "$ISSUE_URL" --format json --jq '.id' 2>/dev/null)
662
739
  if [ -n "$ITEM_ID" ]; then
663
- 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
664
- 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
665
- 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
666
- 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 issue)
740
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {project_board.field_ids.status} --single-select-option-id {project_board.option_ids.status.todo} 2>/dev/null || true # Status=Todo
741
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {project_board.field_ids.lane} --single-select-option-id {project_board.option_ids.lane.fast} 2>/dev/null || true # Lane=Fast
742
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {project_board.field_ids.component} --single-select-option-id {project_board.option_ids.component} 2>/dev/null || true # Component
743
+ gh project item-edit --project-id {PROJECT_ID} --id "$ITEM_ID" --field-id {project_board.field_ids.priority} --single-select-option-id {PRIORITY_OPTION_ID} 2>/dev/null || true # Priority (from issue)
667
744
  fi
668
745
  ```
669
746
 
@@ -696,10 +773,10 @@ fi
696
773
  After displaying the summary, write the current audit snapshot to the history file. This enables Phase 0 of future audits to load it.
697
774
 
698
775
  ```bash
699
- HISTORY_FILE="/home/mrdubey/projects/ScraperAPI/analytics-history.yaml"
776
+ HISTORY_FILE="{HISTORY_FILE}"
700
777
 
701
778
  python3 -c "
702
- import yaml, os, json
779
+ import yaml, os, sys, json
703
780
  from datetime import date
704
781
 
705
782
  # Build snapshot from current audit data
@@ -742,9 +819,9 @@ snapshot = {
742
819
 
743
820
  # Read existing history (or start fresh)
744
821
  history = []
745
- if os.path.exists('$HISTORY_FILE'):
822
+ if os.path.exists(sys.argv[1]):
746
823
  try:
747
- history = yaml.safe_load(open('$HISTORY_FILE')) or []
824
+ history = yaml.safe_load(open(sys.argv[1])) or []
748
825
  except Exception:
749
826
  history = [] # corrupt file — start fresh, do not fail
750
827
 
@@ -753,11 +830,11 @@ history.append(snapshot)
753
830
  history = history[-12:]
754
831
 
755
832
  # Write back
756
- with open('$HISTORY_FILE', 'w') as f:
833
+ with open(sys.argv[1], 'w') as f:
757
834
  yaml.dump(history, f, default_flow_style=False, allow_unicode=True, sort_keys=False)
758
835
 
759
- print(f'History updated: {len(history)} entries in $HISTORY_FILE')
760
- "
836
+ print(f'History updated: {len(history)} entries in {sys.argv[1]}')
837
+ " "$HISTORY_FILE"
761
838
  ```
762
839
 
763
840
  **Placeholder substitution**: Replace `{GSC_CLICKS}`, `{UMAMI_VISITORS}`, etc. with the actual numeric values from the data collection agents. For any platform that was unavailable this run, use `null` (Python None). For `{ISSUES_CREATED_LIST}`, use a list of dicts matching the schema: `[{'number': 123, 'title': '...', 'priority': 'P1', 'category': 'infra'}]`. For `{KEY_FINDINGS_LIST}`, use a list of the top 3-5 finding strings from the Executive Summary. For `{GA4_STATUS}` etc., use one of: `trusted`, `partially_trusted`, `unverified`, `unavailable`.
@@ -18,6 +18,30 @@ Parse agent JSONL output files from orchestration runs to produce actionable dia
18
18
 
19
19
  ---
20
20
 
21
+ ## Config Preamble
22
+
23
+ Before executing any phase, read `forge.yaml` to resolve project references:
24
+
25
+ ```bash
26
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
27
+ if [ -f "$CONFIG_FILE" ]; then
28
+ GH_OWNER=$(yq '.project.owner' "$CONFIG_FILE")
29
+ GH_REPO_NAME=$(yq '.project.repo' "$CONFIG_FILE")
30
+ GH_REPO="${GH_OWNER}/${GH_REPO_NAME}"
31
+ # FORGE_REPO: the self-pipeline repo where orchestration metrics are tracked.
32
+ # Set project.forge_repo in forge.yaml if your pipeline repo differs from GH_REPO.
33
+ FORGE_REPO=$(yq '.project.forge_repo // ""' "$CONFIG_FILE")
34
+ [ -z "$FORGE_REPO" ] && FORGE_REPO="$GH_REPO"
35
+ else
36
+ echo "WARNING: forge.yaml not found — commands will use placeholder values"
37
+ echo "Run: cp forge.yaml.example forge.yaml and fill in your project details"
38
+ GH_REPO="your-org/your-repo"
39
+ FORGE_REPO="$GH_REPO"
40
+ fi
41
+ ```
42
+
43
+ ---
44
+
21
45
  ## Phase 1: Locate Agent Outputs
22
46
 
23
47
  ### Step 1A: Find the session
@@ -42,7 +66,7 @@ done | sort -rn | head -5
42
66
  - `latest` or no argument → most recent session with agent outputs
43
67
  - A session UUID → that specific session
44
68
  - An agent ID (starts with `a`, 17+ hex chars) → find the session containing that agent
45
- - A project path fragment (e.g., `ScraperAPI`) → filter to sessions for that project
69
+ - A project path fragment (e.g., `my-project`) → filter to sessions for that project
46
70
 
47
71
  ### Step 1B: Collect agent JSONL files
48
72
 
@@ -320,17 +344,17 @@ This step posts a structured `<!-- FORGE:AUDIT-AGENTS -->` summary comment to th
320
344
 
321
345
  ```bash
322
346
  # Ensure the label exists before using it (gh issue create fails with GraphQL error if label is absent)
323
- gh label create "orchestration-metrics" -R RapierCraftStudios/forge \
347
+ gh label create "orchestration-metrics" -R {FORGE_REPO} \
324
348
  --color "5319E7" --description "Running log of persisted audit-agents efficiency summaries" \
325
349
  --force 2>/dev/null || true
326
350
 
327
- TRACKING_ISSUE=$(gh issue list -R RapierCraftStudios/forge \
351
+ TRACKING_ISSUE=$(gh issue list -R {FORGE_REPO} \
328
352
  --state open --label "orchestration-metrics" --limit 1 \
329
353
  --json number --jq '.[0].number' 2>/dev/null)
330
354
 
331
355
  if [ -z "$TRACKING_ISSUE" ]; then
332
356
  # Create the tracking issue on first use
333
- TRACKING_ISSUE=$(gh issue create -R RapierCraftStudios/forge \
357
+ TRACKING_ISSUE=$(gh issue create -R {FORGE_REPO} \
334
358
  --title "Orchestration Metrics — Running Log" \
335
359
  --label "orchestration-metrics" \
336
360
  --body "This issue is a running log of persisted \`/audit-agents\` summaries. Each comment contains one session's efficiency metrics. Do not close this issue — \`/pipeline-health\` Phase 2K queries it to aggregate orchestration efficiency trends." \
@@ -363,7 +387,7 @@ STALL_BOUNDARIES=$(echo "${AGENT_DATA[@]}" | jq -r '
363
387
  ```bash
364
388
  SESSION_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
365
389
 
366
- gh issue comment $TRACKING_ISSUE -R RapierCraftStudios/forge --body "<!-- FORGE:AUDIT-AGENTS -->
390
+ gh issue comment $TRACKING_ISSUE -R {FORGE_REPO} --body "<!-- FORGE:AUDIT-AGENTS -->
367
391
  ## Audit-Agents Summary — $SESSION_DATE
368
392
 
369
393
  **Session**: \`$SESSION_ID\`
package/commands/audit.md CHANGED
@@ -9,7 +9,7 @@ argument-hint: <description of what went wrong> [--issue N] [--pr N] [--repo pre
9
9
 
10
10
  You are Forge's post-mortem auditor. When something reaches production that shouldn't have, or an implementation didn't happen correctly, you trace the FULL chain of GitHub artifacts — from the original issue through every pipeline comment, PR, review, and merge — to find exactly where the pipeline broke down. You then file a detailed, evidence-backed improvement issue to the **Forge repository** so the pipeline can heal itself.
11
11
 
12
- **This command ALWAYS files issues to `RapierCraftStudios/forge`.** The target project is read-only — you only read its GitHub artifacts as evidence.
12
+ **This command ALWAYS files issues to `{FORGE_REPO}`.** The target project is read-only — you only read its GitHub artifacts as evidence.
13
13
 
14
14
  **Agent model policy**: Default `model: "sonnet"`. If Sonnet is rate-limited, fall back to `model: "opus"`.
15
15
 
@@ -17,26 +17,64 @@ You are Forge's post-mortem auditor. When something reaches production that shou
17
17
 
18
18
  ---
19
19
 
20
+ ## Config Preamble
21
+
22
+ Before executing any phase, read `forge.yaml` to resolve project references:
23
+
24
+ ```bash
25
+ CONFIG_FILE="${FORGE_CONFIG:-forge.yaml}"
26
+ if [ -f "$CONFIG_FILE" ]; then
27
+ GH_OWNER=$(yq '.project.owner' "$CONFIG_FILE")
28
+ GH_REPO_NAME=$(yq '.project.repo' "$CONFIG_FILE")
29
+ GH_REPO="${GH_OWNER}/${GH_REPO_NAME}"
30
+ GH_FLAG="-R $GH_REPO"
31
+ REPO_PATH=$(yq '.paths.root' "$CONFIG_FILE")
32
+ # FORGE_REPO: the self-pipeline repo where audit improvement issues are filed.
33
+ # Set project.forge_repo in forge.yaml if your pipeline repo differs from GH_REPO.
34
+ # Example: project:
35
+ # forge_repo: "my-org/my-forge"
36
+ FORGE_REPO=$(yq '.project.forge_repo // ""' "$CONFIG_FILE")
37
+ [ -z "$FORGE_REPO" ] && FORGE_REPO="$GH_REPO"
38
+ else
39
+ echo "WARNING: forge.yaml not found — commands will use placeholder values"
40
+ echo "Run: cp forge.yaml.example forge.yaml and fill in your project details"
41
+ GH_REPO="your-org/your-repo"
42
+ GH_FLAG="-R $GH_REPO"
43
+ REPO_PATH="./"
44
+ FORGE_REPO="$GH_REPO"
45
+ fi
46
+ ```
47
+
48
+ ---
49
+
20
50
  ## Multi-Repo Support
21
51
 
22
- | Input Pattern | Target Repo |
23
- |---------------|-------------|
24
- | `--repo alterlab` or default | `RapierCraftStudios/AlterLab` |
25
- | `--repo mcp` | `RapierCraftStudios/alterlab-mcp-server` |
26
- | `--repo n8n` | `RapierCraftStudios/n8n-nodes-alterlab` |
52
+ If your project spans multiple repositories, define them in the `repos.satellites` section of `forge.yaml`. The `--repo <prefix>` argument selects a satellite by its `prefix` field.
27
53
 
28
54
  Parse `$ARGUMENTS` for:
29
55
  - **Problem description**: free-text explanation of what went wrong
30
56
  - **`--issue N`**: specific issue number(s) to trace (comma-separated)
31
57
  - **`--pr N`**: specific PR number(s) to trace (comma-separated)
32
- - **`--repo prefix`**: target repo (default: AlterLab)
58
+ - **`--repo prefix`**: target repo prefix (default: primary repo from `project.owner/project.repo`)
33
59
 
34
- Set context variables:
60
+ Set context variables from config:
35
61
 
36
- | Variable | AlterLab | MCP | n8n |
37
- |----------|----------|-----|-----|
38
- | `GH_REPO` | `RapierCraftStudios/AlterLab` | `RapierCraftStudios/alterlab-mcp-server` | `RapierCraftStudios/n8n-nodes-alterlab` |
39
- | `GH_FLAG` | _(empty)_ | `-R RapierCraftStudios/alterlab-mcp-server` | `-R RapierCraftStudios/n8n-nodes-alterlab` |
62
+ ```bash
63
+ REPO_PREFIX=$(echo "$ARGUMENTS" | grep -oP '(?<=--repo )\S+' || true)
64
+
65
+ if [ -n "$REPO_PREFIX" ]; then
66
+ # Look up satellite repo by prefix in forge.yaml
67
+ SATELLITE_REPO=$(yq ".repos.satellites[] | select(.prefix == \"$REPO_PREFIX\") | .repo" "$CONFIG_FILE" 2>/dev/null)
68
+ if [ -n "$SATELLITE_REPO" ] && [ "$SATELLITE_REPO" != "null" ]; then
69
+ GH_REPO="$SATELLITE_REPO"
70
+ GH_FLAG="-R $GH_REPO"
71
+ REPO_PATH=$(yq ".repos.satellites[] | select(.prefix == \"$REPO_PREFIX\") | .local_path" "$CONFIG_FILE" 2>/dev/null || echo "$REPO_PATH")
72
+ else
73
+ echo "WARNING: --repo prefix '$REPO_PREFIX' not found in forge.yaml repos.satellites — using default repo"
74
+ fi
75
+ fi
76
+ # GH_REPO, GH_FLAG, REPO_PATH are now set for the target repo
77
+ ```
40
78
 
41
79
  ---
42
80
 
@@ -247,7 +285,7 @@ Create a detailed, actionable issue in the **Forge repository**.
247
285
  ### 4B: Create the issue
248
286
 
249
287
  ```bash
250
- gh issue create -R RapierCraftStudios/forge \
288
+ gh issue create -R {FORGE_REPO} \
251
289
  --title "fix(pipeline): {one-line description of what the pipeline missed}" \
252
290
  --label "{LABELS}" \
253
291
  --body "$(cat <<'EOF'
@@ -332,7 +370,7 @@ Files that need changes:
332
370
 
333
371
  ```bash
334
372
  # Search forge for related issues
335
- gh issue list -R RapierCraftStudios/forge --state all --limit 30 --json number,title \
373
+ gh issue list -R {FORGE_REPO} --state all --limit 30 --json number,title \
336
374
  --jq '[.[] | select(.title | test("{KEYWORDS}"; "i"))]'
337
375
  ```
338
376
 
@@ -351,11 +389,11 @@ Check if this same failure pattern has occurred before:
351
389
 
352
390
  ```bash
353
391
  # Similar audit findings in forge
354
- gh issue list -R RapierCraftStudios/forge --state all --label "audit-finding" --limit 50 \
392
+ gh issue list -R {FORGE_REPO} --state all --label "audit-finding" --limit 50 \
355
393
  --json number,title,state --jq '.[] | "\(.number) | \(.title) | \(.state)"'
356
394
 
357
395
  # Same failure point in existing issues
358
- gh issue list -R RapierCraftStudios/forge --state all --limit 100 --json number,title,body \
396
+ gh issue list -R {FORGE_REPO} --state all --limit 100 --json number,title,body \
359
397
  --jq "[.[] | select(.body | contains(\"{FAILURE_POINT}\"))] | .[] | \"\(.number) | \(.title)\""
360
398
  ```
361
399
 
@@ -375,7 +413,7 @@ gh issue comment {NUMBER} {GH_FLAG} --body "$(cat <<'EOF'
375
413
  ## Pipeline Audit
376
414
 
377
415
  This issue was audited by Forge. A pipeline improvement issue has been filed:
378
- - **Forge issue**: RapierCraftStudios/forge#{FORGE_ISSUE_NUMBER}
416
+ - **Forge issue**: {FORGE_REPO}#{FORGE_ISSUE_NUMBER}
379
417
  - **Failure point**: {FAILURE_POINT}
380
418
  - **Root cause**: {1-line summary}
381
419
 
@@ -7,7 +7,12 @@ argument-hint: [--fix | --recon-only | --fix --limit 5 | --dry-run]
7
7
 
8
8
  **Input**: $ARGUMENTS (default: recon + triage + report, no fixing)
9
9
 
10
- You are AlterLab's autonomous improvement engine. Your job is to **find what's wrong, create trackable issues, and optionally fix the highest-impact ones** — all in a single cycle. Every cycle leaves the platform measurably better than before.
10
+ **Config variables used by this command** (set in `forge.yaml`):
11
+ - `{CREDENTIALS_FILE}` ← `paths.credentials.file` (optional) — path to credentials YAML for analytics APIs
12
+ - `{SERVER_SSH}` ← `services.server_ssh` (optional) — SSH target for production server health checks (e.g., `ubuntu@1.2.3.4`)
13
+ - `{EMEMO_PATH}` ← `services.ememo_path` (optional) — path on production server to open eMemo files
14
+
15
+ You are an autonomous improvement engine for this project. Your job is to **find what's wrong, create trackable issues, and optionally fix the highest-impact ones** — all in a single cycle. Every cycle leaves the platform measurably better than before.
11
16
 
12
17
  **This is designed to run repeatedly.** Each cycle builds on the last — issues created in cycle N get fixed in cycle N+1. The platform compounds improvements over time.
13
18
 
@@ -94,7 +99,7 @@ Spawn these as **background agents** (all `run_in_background=true`, all `model="
94
99
  **Agent: Production Health**
95
100
  ```
96
101
  Check production system health:
97
- 1. SSH to check for open eMemos: ssh ubuntu@51.195.40.172 "ls /opt/alterlab/app/infra/ememos/*-open-* 2>/dev/null"
102
+ 1. SSH to check for open eMemos: if {SERVER_SSH} is configured, run: ssh {SERVER_SSH} "ls {EMEMO_PATH}/*-open-* 2>/dev/null" — skip this step if SERVER_SSH or EMEMO_PATH is not set in forge.yaml
98
103
  2. If any exist, cat each one and summarize
99
104
  3. Check container health via MCP: get_production_status, run_production_health_check
100
105
  4. Check recent error logs: get_production_logs for api and worker (last 100 lines), grep for ERROR/CRITICAL
@@ -133,7 +138,7 @@ Analyze GitHub issue backlog:
133
138
  **Agent: Analytics Snapshot** (lightweight — not the full /analytics audit)
134
139
  ```
135
140
  Quick analytics pulse — just the key metrics, not a full audit:
136
- 1. Read credentials from /home/mrdubey/projects/ScraperAPI/credentials.yaml
141
+ 1. Read credentials from {CREDENTIALS_FILE} (set via paths.credentials.file in forge.yaml)
137
142
  2. GSC: mcp__gsc__search_analytics for last 7 days — total clicks, impressions, avg position
138
143
  3. Stripe: mcp__stripe__retrieve_balance — current balance
139
144
  4. Return: clicks trend (up/down), any revenue, notable changes
@@ -409,9 +414,9 @@ When ready to deploy, merge `staging` → `main` via GitHub.
409
414
 
410
415
  Each cycle feeds the next:
411
416
  ```
412
- Cycle N: /failure-recon finds tier 3 headers broken → creates issue #X
413
- Cycle N+1: autopilot picks up #X → /work-on fixes it → merged to staging
414
- Cycle N+2: /failure-recon shows tier 3 pass rate improved 30% → no new issue needed
417
+ Cycle N: CI recon detects recurring lint failures across 3 files → autopilot triage creates issue #X
418
+ Cycle N+1: autopilot picks up #X → /work-on adds pre-commit hook → merged to staging
419
+ Cycle N+2: CI recon shows lint failures resolved — no new issue needed
415
420
  ```
416
421
 
417
422
  ```