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.
- package/README.md +19 -2
- package/bin/forgedock.mjs +286 -10
- package/commands/analytics.md +129 -52
- package/commands/audit-agents.md +29 -5
- package/commands/audit.md +55 -17
- package/commands/autopilot.md +11 -6
- package/commands/cleanup.md +44 -24
- package/commands/deploy-info.md +7 -3
- package/commands/forge-stats.md +13 -4
- package/commands/forgedock-init.md +534 -0
- package/commands/geo-audit.md +114 -26
- package/commands/incident-response.md +4 -1
- package/commands/issue.md +26 -25
- package/commands/milestone.md +80 -59
- package/commands/orchestrate.md +38 -21
- package/commands/pipeline-health.md +26 -15
- package/commands/qa-sweep.md +68 -5
- package/commands/quality-gate.md +18 -0
- package/commands/review-pr-agents.md +58 -90
- package/commands/review-pr.md +58 -16
- package/commands/rollback.md +8 -5
- package/commands/security-audit.md +68 -12
- package/commands/sync-ecosystem.md +196 -156
- package/commands/validate.md +1 -1
- package/commands/work-on/build/architect.md +2 -2
- package/commands/work-on/build/context.md +2 -2
- package/commands/work-on/build/implement.md +3 -3
- package/commands/work-on/build/validate.md +3 -3
- package/commands/work-on/build.md +6 -6
- package/commands/work-on/close.md +59 -30
- package/commands/work-on/decompose.md +6 -6
- package/commands/work-on/investigate.md +16 -20
- package/commands/work-on/review.md +4 -4
- package/commands/work-on-monolithic.md +15 -16
- package/commands/work-on.md +45 -33
- package/docs/CODEX.md +1 -1
- package/docs/CONFIG.md +398 -0
- package/docs/WORKFLOW.md +115 -72
- package/package.json +2 -2
- package/scripts/verify-host-headers.sh +14 -9
- package/scripts/verify-route-registration.sh +10 -10
- package/commands/failure-recon.md +0 -375
package/commands/analytics.md
CHANGED
|
@@ -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
|
|
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('
|
|
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 =
|
|
34
|
-
SITE_URL
|
|
35
|
-
DOMAIN
|
|
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 `
|
|
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="
|
|
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
|
|
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(
|
|
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
|
|
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: "
|
|
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 `
|
|
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('
|
|
140
|
-
# Login: POST
|
|
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 `
|
|
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 `
|
|
240
|
+
**Auth**: Read from `{CREDENTIALS_FILE}`:
|
|
164
241
|
```bash
|
|
165
|
-
CF_TOKEN=$(python3 -c "import yaml; print(yaml.safe_load(open('
|
|
166
|
-
CF_ZONE=$(python3 -c "import yaml; print(yaml.safe_load(open('
|
|
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 `
|
|
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
|
|
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 `
|
|
263
|
+
**Auth**: Read from `{CREDENTIALS_FILE}`:
|
|
187
264
|
```bash
|
|
188
|
-
BING_KEY=$(python3 -c "import yaml; print(yaml.safe_load(open('
|
|
189
|
-
BING_SITE=$(python3 -c "import yaml; print(yaml.safe_load(open('
|
|
190
|
-
BING_BASE=$(python3 -c "import yaml; print(yaml.safe_load(open('
|
|
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
|
|
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
|
|
237
|
-
SA_KEY="
|
|
238
|
-
GA4_PROPERTY_ID="
|
|
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(
|
|
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
|
|
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>" /
|
|
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**:
|
|
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
|
-
-
|
|
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
|
|
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.
|
|
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/
|
|
661
|
-
ITEM_ID=$(gh project item-add
|
|
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
|
|
664
|
-
gh project item-edit --project-id
|
|
665
|
-
gh project item-edit --project-id
|
|
666
|
-
gh project item-edit --project-id
|
|
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="
|
|
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(
|
|
822
|
+
if os.path.exists(sys.argv[1]):
|
|
746
823
|
try:
|
|
747
|
-
history = yaml.safe_load(open(
|
|
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(
|
|
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
|
|
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`.
|
package/commands/audit-agents.md
CHANGED
|
@@ -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., `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 `
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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**:
|
|
416
|
+
- **Forge issue**: {FORGE_REPO}#{FORGE_ISSUE_NUMBER}
|
|
379
417
|
- **Failure point**: {FAILURE_POINT}
|
|
380
418
|
- **Root cause**: {1-line summary}
|
|
381
419
|
|
package/commands/autopilot.md
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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:
|
|
413
|
-
Cycle N+1: autopilot picks up #X → /work-on
|
|
414
|
-
Cycle N+2:
|
|
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
|
```
|