forgedock 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +244 -0
  2. package/README.md +251 -0
  3. package/bin/forgedock.mjs +232 -0
  4. package/commands/analytics.md +777 -0
  5. package/commands/audit-agents.md +410 -0
  6. package/commands/audit.md +410 -0
  7. package/commands/autopilot.md +432 -0
  8. package/commands/cleanup.md +264 -0
  9. package/commands/deploy-info.md +292 -0
  10. package/commands/failure-recon.md +375 -0
  11. package/commands/forge-stats.md +183 -0
  12. package/commands/geo-audit.md +343 -0
  13. package/commands/incident-response.md +384 -0
  14. package/commands/issue.md +506 -0
  15. package/commands/milestone.md +599 -0
  16. package/commands/orchestrate.md +1044 -0
  17. package/commands/pipeline-health.md +2343 -0
  18. package/commands/qa-sweep.md +237 -0
  19. package/commands/quality-gate.md +777 -0
  20. package/commands/review-pr-agents.md +1711 -0
  21. package/commands/review-pr-staging.md +325 -0
  22. package/commands/review-pr.md +888 -0
  23. package/commands/rollback.md +253 -0
  24. package/commands/security-audit.md +529 -0
  25. package/commands/sync-ecosystem.md +527 -0
  26. package/commands/validate.md +131 -0
  27. package/commands/work-on/build/architect.md +291 -0
  28. package/commands/work-on/build/context.md +349 -0
  29. package/commands/work-on/build/implement.md +280 -0
  30. package/commands/work-on/build/validate.md +292 -0
  31. package/commands/work-on/build.md +262 -0
  32. package/commands/work-on/close.md +350 -0
  33. package/commands/work-on/decompose.md +254 -0
  34. package/commands/work-on/investigate.md +437 -0
  35. package/commands/work-on/review.md +245 -0
  36. package/commands/work-on-monolithic.md +298 -0
  37. package/commands/work-on.md +1089 -0
  38. package/docs/CODEX.md +92 -0
  39. package/docs/WORKFLOW.md +104 -0
  40. package/package.json +45 -0
  41. package/scripts/verify-env-vars.sh +201 -0
  42. package/scripts/verify-host-headers.sh +117 -0
  43. package/scripts/verify-route-registration.sh +118 -0
  44. package/scripts/verify-sops-chain.sh +163 -0
@@ -0,0 +1,410 @@
1
+ ---
2
+ description: Audit agent outputs from an orchestration run — timeline analysis, stall detection, active vs idle time breakdown
3
+ argument-hint: [session-id | latest | <agent-id>]
4
+ ---
5
+
6
+ # /audit-agents — Agent Output Auditor
7
+
8
+ **Input**: $ARGUMENTS
9
+
10
+ ## Purpose
11
+
12
+ Parse agent JSONL output files from orchestration runs to produce actionable diagnostics:
13
+ - Per-agent phase timeline with durations
14
+ - Stall detection (gaps between phases caused by agent `end_turn` stops)
15
+ - Active vs idle time breakdown
16
+ - Resume cycle counting
17
+ - Wave-level efficiency metrics
18
+
19
+ ---
20
+
21
+ ## Phase 1: Locate Agent Outputs
22
+
23
+ ### Step 1A: Find the session
24
+
25
+ Agent outputs live in `/tmp/claude-1000/` as symlinks to JSONL files in `~/.claude/projects/*/subagents/`.
26
+
27
+ ```bash
28
+ # List recent sessions
29
+ ls -lt /tmp/claude-1000/ 2>/dev/null | head -10
30
+
31
+ # For each session dir, list agent output files
32
+ for DIR in /tmp/claude-1000/*/; do
33
+ TASKS_DIR="${DIR}*/tasks/"
34
+ AGENT_COUNT=$(find $TASKS_DIR -name "a*.output" -type l 2>/dev/null | wc -l)
35
+ if [ "$AGENT_COUNT" -gt 0 ]; then
36
+ echo "$(stat -c %Y "$DIR" 2>/dev/null) $DIR ($AGENT_COUNT agents)"
37
+ fi
38
+ done | sort -rn | head -5
39
+ ```
40
+
41
+ **Input resolution:**
42
+ - `latest` or no argument → most recent session with agent outputs
43
+ - A session UUID → that specific session
44
+ - 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
46
+
47
+ ### Step 1B: Collect agent JSONL files
48
+
49
+ ```bash
50
+ SESSION_DIR="/tmp/claude-1000/{PROJECT_PATH}/{SESSION_ID}"
51
+ TASKS_DIR="${SESSION_DIR}/tasks"
52
+
53
+ # Agent files are symlinks starting with 'a' pointing to .jsonl files
54
+ for LINK in ${TASKS_DIR}/a*.output; do
55
+ AGENT_ID=$(basename "$LINK" .output)
56
+ TARGET=$(readlink -f "$LINK")
57
+ LINES=$(wc -l < "$TARGET" 2>/dev/null || echo 0)
58
+ echo "$AGENT_ID $LINES $TARGET"
59
+ done
60
+ ```
61
+
62
+ Only process files with `> 10` lines (smaller files are helper/polling agents, not work-on agents).
63
+
64
+ ---
65
+
66
+ ## Phase 2: Parse Each Agent
67
+
68
+ For each agent JSONL file, use a Python script to extract the timeline.
69
+
70
+ **IMPORTANT**: Run this as a SINGLE Python script, not per-agent bash loops. The JSONL files can be large (500+ lines, 100KB+).
71
+
72
+ ```python
73
+ import json, sys, os
74
+ from datetime import datetime
75
+ from collections import defaultdict
76
+
77
+ def parse_agent(filepath, agent_id):
78
+ """Parse a single agent JSONL file and return structured timeline data."""
79
+ with open(filepath) as f:
80
+ lines = f.readlines()
81
+
82
+ # Extract all events with timestamps
83
+ events = []
84
+ skill_invocations = [] # unique (ts, skill) pairs
85
+ skill_set = set()
86
+ tool_counts = defaultdict(int)
87
+ resume_count = 0
88
+ first_ts = last_ts = None
89
+ end_turn_points = []
90
+
91
+ for line in lines:
92
+ data = json.loads(line)
93
+ ts_str = data.get('timestamp', '')
94
+ if not ts_str:
95
+ continue
96
+
97
+ ts = datetime.fromisoformat(ts_str.replace('Z', '+00:00'))
98
+ if first_ts is None:
99
+ first_ts = ts
100
+ last_ts = ts
101
+
102
+ msg = data.get('message', {})
103
+ stop_reason = msg.get('stop_reason', '')
104
+ content = msg.get('content', [])
105
+
106
+ if stop_reason == 'end_turn':
107
+ # Find the text content of this end_turn message
108
+ text = ''
109
+ if isinstance(content, list):
110
+ for c in content:
111
+ if c.get('type') == 'text':
112
+ text = c.get('text', '')[:100]
113
+ break
114
+ end_turn_points.append({'ts': ts, 'text': text})
115
+
116
+ if isinstance(content, list):
117
+ for c in content:
118
+ if c.get('type') == 'tool_use':
119
+ tool_name = c.get('name', '?')
120
+ tool_counts[tool_name] += 1
121
+
122
+ if tool_name == 'Skill':
123
+ inp = c.get('input', {})
124
+ skill_name = inp.get('skill', '?')
125
+ key = (ts_str, skill_name)
126
+ if key not in skill_set:
127
+ skill_set.add(key)
128
+ skill_invocations.append({
129
+ 'ts': ts,
130
+ 'skill': skill_name,
131
+ 'args': inp.get('args', '')[:60]
132
+ })
133
+ else:
134
+ # Duplicate = resume replay
135
+ pass
136
+
137
+ # Detect resume points: user messages that contain resume instructions
138
+ if data.get('type') == 'user':
139
+ if isinstance(content, str) and any(kw in content.lower() for kw in ['continue', 'resume', 'you need to']):
140
+ resume_count += 1
141
+ elif isinstance(content, list):
142
+ for c in content:
143
+ if c.get('type') == 'text':
144
+ t = c.get('text', '').lower()
145
+ if any(kw in t for kw in ['continue the', 'resume', 'you need to continue']):
146
+ resume_count += 1
147
+
148
+ # Count duplicate skill timestamps to detect resume replays
149
+ all_skill_timestamps = []
150
+ for line in lines:
151
+ data = json.loads(line)
152
+ msg = data.get('message', {})
153
+ content = msg.get('content', [])
154
+ if isinstance(content, list):
155
+ for c in content:
156
+ if c.get('type') == 'tool_use' and c.get('name') == 'Skill':
157
+ all_skill_timestamps.append(data.get('timestamp', ''))
158
+
159
+ # Duplicated timestamps indicate resume replays
160
+ ts_counts = defaultdict(int)
161
+ for t in all_skill_timestamps:
162
+ ts_counts[t] += 1
163
+ max_replays = max(ts_counts.values()) if ts_counts else 1
164
+ resume_cycles = max_replays - 1 # first occurrence is original, rest are replays
165
+
166
+ # Build phase timeline from unique skill invocations
167
+ phases = []
168
+ prev_ts = first_ts
169
+ for si in skill_invocations:
170
+ gap_sec = (si['ts'] - prev_ts).total_seconds()
171
+ phases.append({
172
+ 'ts': si['ts'],
173
+ 'skill': si['skill'],
174
+ 'args': si['args'],
175
+ 'gap_from_prev_sec': gap_sec,
176
+ 'is_stall': gap_sec > 120 # > 2 min = stall
177
+ })
178
+ prev_ts = si['ts']
179
+
180
+ total_sec = (last_ts - first_ts).total_seconds() if first_ts and last_ts else 0
181
+ stall_sec = sum(p['gap_from_prev_sec'] for p in phases if p['is_stall'])
182
+ active_sec = total_sec - stall_sec
183
+
184
+ return {
185
+ 'agent_id': agent_id,
186
+ 'filepath': filepath,
187
+ 'jsonl_lines': len(lines),
188
+ 'first_ts': first_ts,
189
+ 'last_ts': last_ts,
190
+ 'total_sec': total_sec,
191
+ 'active_sec': active_sec,
192
+ 'stall_sec': stall_sec,
193
+ 'idle_pct': (stall_sec / total_sec * 100) if total_sec > 0 else 0,
194
+ 'tool_counts': dict(tool_counts),
195
+ 'phases': phases,
196
+ 'end_turn_points': end_turn_points,
197
+ 'resume_cycles': resume_cycles,
198
+ 'skill_count': len(skill_invocations),
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## Phase 3: Identify Issues
205
+
206
+ For each agent, try to determine which GitHub issue it was working on:
207
+
208
+ ```bash
209
+ # Read the first user message in the JSONL — it contains the agent prompt with issue number
210
+ python3 -c "
211
+ import json
212
+ with open('$FILEPATH') as f:
213
+ first = json.loads(f.readline())
214
+ msg = first.get('message', {}).get('content', '')
215
+ if isinstance(msg, list):
216
+ for c in msg:
217
+ if c.get('type') == 'text':
218
+ msg = c['text']
219
+ break
220
+ # Extract issue number
221
+ import re
222
+ m = re.search(r'#(\d+)', str(msg))
223
+ print(m.group(1) if m else 'unknown')
224
+ "
225
+ ```
226
+
227
+ Also extract the issue title from the prompt.
228
+
229
+ ---
230
+
231
+ ## Phase 4: Generate Report
232
+
233
+ ### Step 4A: Per-agent timeline
234
+
235
+ For each agent, display:
236
+
237
+ ```
238
+ ## Agent: #{ISSUE_NUMBER} — {ISSUE_TITLE}
239
+ **Duration**: {total_min} min (active: {active_min} min, idle: {stall_min} min — {idle_pct}% idle)
240
+ **Resume cycles**: {resume_cycles} (agent stopped and was resumed {resume_cycles} times)
241
+ **JSONL lines**: {lines} | **Tool calls**: Bash:{N} Read:{N} Edit:{N} Skill:{N}
242
+
243
+ ### Phase Timeline
244
+ | Time | Phase | Duration | Gap | Status |
245
+ |------|-------|----------|-----|--------|
246
+ | 12:09:11 | work-on | — | — | start |
247
+ | 12:09:24 | investigate | 1m 3s | 13s | ok |
248
+ | 12:17:46 | build | — | **7m 22s** | STALL |
249
+ | 12:18:28 | build:context | 42s | 42s | ok |
250
+ | 12:42:13 | build:architect | 25s | **23m 45s** | STALL |
251
+ | 12:42:38 | build:implement | 39s | 25s | ok |
252
+ | 12:43:17 | build:validate | 4s | 39s | ok |
253
+ | 12:43:21 | quality-gate | 1m 18s | 4s | ok |
254
+ | 12:44:39 | review | 44s | 1m 18s | ok |
255
+ | 12:45:23 | review-pr | 2m 13s | 44s | ok |
256
+ | 12:47:36 | close | 1m 16s | 2m 13s | ok |
257
+
258
+ ### end_turn Stops (caused stalls)
259
+ | Time | Last message before stop |
260
+ |------|--------------------------|
261
+ | 12:10:27 | `INVESTIGATE_RESULT: verdict: CONFIRMED...` |
262
+ | 12:19:07 | `Context phase complete. Returning to B4.` |
263
+ ```
264
+
265
+ ### Step 4B: Wave summary
266
+
267
+ ```
268
+ ## Wave Summary
269
+
270
+ | Agent | Issue | Total | Active | Idle | Idle% | Resumes | Stall Points |
271
+ |-------|-------|-------|--------|------|-------|---------|--------------|
272
+ | afbc… | #14513 | 40m | 8m | 31m | 80% | 2 | investigate→build, context→architect |
273
+ | a3b5… | #14508 | 23m | 23m | 0m | 0% | 0 | — |
274
+ | adf5… | #14514 | 55m | 12m | 43m | 78% | 3 | investigate→build, context→architect, implement→validate |
275
+
276
+ **Wave efficiency**: {avg_idle_pct}% idle time across all agents
277
+ **Longest stall**: {max_stall_min} min ({agent_id} between {phase_a} → {phase_b})
278
+ **Clean agents**: {N} of {total} ran without stalls
279
+ ```
280
+
281
+ ### Step 4C: Stall pattern analysis
282
+
283
+ Identify recurring stall patterns across agents:
284
+
285
+ ```
286
+ ## Stall Pattern Analysis
287
+
288
+ ### Common stall boundaries
289
+ | Boundary | Agents affected | Avg gap |
290
+ |----------|----------------|---------|
291
+ | investigate → build | 4/5 | 7.5 min |
292
+ | context → architect | 4/5 | 23.8 min |
293
+ | implement → validate | 3/5 | 20.3 min |
294
+
295
+ ### Root cause indicators
296
+ - **Synchronized stall times**: 4 agents stalled at 12:17, 12:42, 13:03
297
+ → Orchestrator polling intervals, not agent-side issues
298
+ - **end_turn at phase boundaries**: Agent outputs result text then stops
299
+ → LLM routing loop exits instead of continuing to next phase
300
+ - **Resume replays**: Each resume re-sends full conversation history
301
+ → Context grows with each cycle, compounding the problem
302
+ ```
303
+
304
+ ### Step 4D: Recommendations
305
+
306
+ Based on the data, output specific recommendations:
307
+
308
+ - If idle% > 50% across wave → "Orchestrator polling too slow — agents spend more time waiting than working"
309
+ - If resume_cycles > 0 for most agents → "Routing loop in work-on.md not continuing past phase boundaries"
310
+ - If specific boundary stalls repeatedly → "Phase {X} returns text with end_turn instead of continuing loop — check work-on.md routing instructions"
311
+ - If one agent ran clean but others didn't → "Compare clean agent (#XXXX) vs stalled agents — what differs?"
312
+
313
+ ### Step 4E: Persist summary (triggered by `--persist` flag)
314
+
315
+ **Trigger**: Run this step ONLY when `$ARGUMENTS` contains `--persist`. Default mode (no flag) skips this step and only prints to the conversation.
316
+
317
+ This step posts a structured `<!-- FORGE:AUDIT-AGENTS -->` summary comment to the Forge orchestration-metrics tracking issue so that `/pipeline-health` can query historical efficiency data.
318
+
319
+ **Step 4E.1 — Locate or create the tracking issue**:
320
+
321
+ ```bash
322
+ # 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 \
324
+ --color "5319E7" --description "Running log of persisted audit-agents efficiency summaries" \
325
+ --force 2>/dev/null || true
326
+
327
+ TRACKING_ISSUE=$(gh issue list -R RapierCraftStudios/forge \
328
+ --state open --label "orchestration-metrics" --limit 1 \
329
+ --json number --jq '.[0].number' 2>/dev/null)
330
+
331
+ if [ -z "$TRACKING_ISSUE" ]; then
332
+ # Create the tracking issue on first use
333
+ TRACKING_ISSUE=$(gh issue create -R RapierCraftStudios/forge \
334
+ --title "Orchestration Metrics — Running Log" \
335
+ --label "orchestration-metrics" \
336
+ --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." \
337
+ --json number --jq '.number')
338
+ echo "Created orchestration-metrics tracking issue #$TRACKING_ISSUE"
339
+ fi
340
+ ```
341
+
342
+ **Step 4E.2 — Compute wave-level aggregate metrics** (from the data already parsed in Phase 2):
343
+
344
+ ```bash
345
+ # From the per-agent data computed in Phase 2, derive wave-level aggregates
346
+ TOTAL_AGENTS=$(echo "${AGENT_DATA[@]}" | jq 'length')
347
+ AVG_IDLE=$(echo "${AGENT_DATA[@]}" | jq '[.[].idle_pct] | add / length | . * 10 | round / 10')
348
+ AVG_RESUMES=$(echo "${AGENT_DATA[@]}" | jq '[.[].resume_cycles] | add / length | . * 100 | round / 100')
349
+ CLEAN_N=$(echo "${AGENT_DATA[@]}" | jq '[.[] | select(.idle_pct == 0 and .resume_cycles == 0)] | length')
350
+
351
+ # Top stall boundaries: aggregate gap_from_prev_sec > 120 entries by skill transition label
352
+ # Format: "investigate→build(4), context→architect(3), implement→validate(2)"
353
+ STALL_BOUNDARIES=$(echo "${AGENT_DATA[@]}" | jq -r '
354
+ [.[].phases[] | select(.is_stall) | .skill] |
355
+ group_by(.) | map({boundary: .[0], count: length}) |
356
+ sort_by(-.count) | .[:5] |
357
+ map("\(.boundary)(\(.count))") | join(", ")
358
+ ')
359
+ ```
360
+
361
+ **Step 4E.3 — Post the structured summary comment**:
362
+
363
+ ```bash
364
+ SESSION_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
365
+
366
+ gh issue comment $TRACKING_ISSUE -R RapierCraftStudios/forge --body "<!-- FORGE:AUDIT-AGENTS -->
367
+ ## Audit-Agents Summary — $SESSION_DATE
368
+
369
+ **Session**: \`$SESSION_ID\`
370
+ **Date**: $SESSION_DATE
371
+ **Agents**: $TOTAL_AGENTS
372
+ **Avg idle%**: $AVG_IDLE
373
+ **Avg resumes**: $AVG_RESUMES
374
+ **Clean agents**: $CLEAN_N/$TOTAL_AGENTS
375
+ **Stall boundaries**: $STALL_BOUNDARIES
376
+
377
+ _Posted by \`audit-agents --persist\`. Queried by \`/pipeline-health\` Phase 2K._
378
+
379
+ <!-- FORGE:AUDIT-AGENTS:COMPLETE -->"
380
+
381
+ echo "Persisted audit summary to tracking issue #$TRACKING_ISSUE"
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Phase 5: Comparison Mode (optional)
387
+
388
+ If `$ARGUMENTS` contains `--compare` or two session IDs, run the analysis on both sessions and produce a diff:
389
+
390
+ ```
391
+ ## Session Comparison
392
+
393
+ | Metric | Session A | Session B | Delta |
394
+ |--------|-----------|-----------|-------|
395
+ | Avg agent duration | 38m | 24m | -37% |
396
+ | Avg idle% | 62% | 15% | -47pp |
397
+ | Avg resume cycles | 2.5 | 0.3 | -88% |
398
+ | Clean agents | 1/5 | 4/5 | +60pp |
399
+ ```
400
+
401
+ This enables tracking whether orchestrator/prompt changes actually improved throughput.
402
+
403
+ ---
404
+
405
+ ## Notes
406
+
407
+ - **File format**: Agent outputs are JSONL (one JSON object per line). Each line has `type` (user/assistant), `timestamp`, `message` (with `content` array and optional `stop_reason`).
408
+ - **Symlinks**: Files in `/tmp/claude-1000/*/tasks/a*.output` are symlinks to `~/.claude/projects/*/subagents/agent-*.jsonl`.
409
+ - **Size filtering**: Only analyze files > 10 lines. Small files are helper/polling agents spawned by the orchestrator for status checks.
410
+ - **Resume detection**: When an agent is resumed, the full conversation is replayed. Duplicate `(timestamp, skill_name)` pairs indicate replay cycles. `max(duplicates) - 1 = resume_cycles`.