forgedock 1.1.1 → 1.1.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 +53 -8
- package/bin/batch-runner.mjs +339 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +20 -0
- package/bin/engine/state.mjs +8 -3
- package/bin/engine-cli.mjs +177 -5
- package/bin/forgedock.mjs +409 -26
- package/bin/hooks/interactive-engine.mjs +509 -0
- package/bin/hooks/pre-tool-use.mjs +498 -0
- package/bin/hooks/subagent-stop-enforce.mjs +197 -0
- package/bin/journey.mjs +72 -14
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +68 -19
- package/bin/settings-hook.mjs +235 -1
- package/commands/adopt.md +4 -1
- package/commands/analytics.md +4 -1
- package/commands/audit-agents.md +110 -27
- package/commands/audit.md +20 -1
- package/commands/autopilot.md +80 -8
- package/commands/changelog.md +2 -1
- package/commands/ci-audit.md +2 -1
- package/commands/cleanup.md +161 -1
- package/commands/compat-audit.md +2 -1
- package/commands/deploy-info.md +58 -1
- package/commands/design-bench.md +5 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +121 -2
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +3 -0
- package/commands/incident-response.md +2 -1
- package/commands/issue.md +23 -2
- package/commands/milestone.md +77 -12
- package/commands/optimize.md +2 -1
- package/commands/orchestrate/config.md +67 -0
- package/commands/orchestrate/phase-1-resolve.md +159 -0
- package/commands/orchestrate/phase-2-triage.md +126 -0
- package/commands/orchestrate/phase-2.5-synthesis.md +156 -0
- package/commands/orchestrate/phase-3-dependency.md +591 -0
- package/commands/orchestrate/phase-4-execution.md +962 -0
- package/commands/orchestrate/phase-5-cleanup.md +47 -0
- package/commands/orchestrate/phase-6-report.md +198 -0
- package/commands/orchestrate/safety.md +62 -0
- package/commands/orchestrate.md +25 -2045
- package/commands/pipeline-health/config.md +34 -0
- package/commands/pipeline-health/phase-1-context.md +180 -0
- package/commands/pipeline-health/phase-2-metrics.md +1729 -0
- package/commands/pipeline-health/phase-3-analyze.md +204 -0
- package/commands/pipeline-health/phase-4-proposals.md +117 -0
- package/commands/pipeline-health/phase-5-report.md +284 -0
- package/commands/pipeline-health/phase-6-summary.md +52 -0
- package/commands/pipeline-health.md +25 -2401
- package/commands/pipeline-resume.md +24 -1
- package/commands/pipeline-status.md +65 -0
- package/commands/qa-sweep.md +4 -1
- package/commands/quality-gate.md +248 -10
- package/commands/replay.md +2 -1
- package/commands/review-pr-agents/api.md +135 -0
- package/commands/review-pr-agents/auth.md +137 -0
- package/commands/review-pr-agents/billing.md +101 -0
- package/commands/review-pr-agents/concurrency.md +145 -0
- package/commands/review-pr-agents/database.md +103 -0
- package/commands/review-pr-agents/frontend.md +89 -0
- package/commands/review-pr-agents/infra.md +385 -0
- package/commands/review-pr-agents/protocols.md +185 -0
- package/commands/review-pr-agents/scraper.md +185 -0
- package/commands/review-pr-agents/security.md +406 -0
- package/commands/review-pr-agents.md +45 -1722
- package/commands/review-pr-staging.md +196 -40
- package/commands/review-pr.md +591 -52
- package/commands/rollback.md +2 -1
- package/commands/scope.md +2 -1
- package/commands/security-audit.md +2 -1
- package/commands/signal-planner.md +623 -0
- package/commands/sync-ecosystem.md +1 -0
- package/commands/test-gate.md +17 -1
- package/commands/validate.md +2 -1
- package/commands/work-on/build/architect.md +28 -0
- package/commands/work-on/build/context.md +2 -0
- package/commands/work-on/build/implement.md +3 -1
- package/commands/work-on/build/validate.md +26 -7
- package/commands/work-on/build.md +163 -24
- package/commands/work-on/close.md +130 -3
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +24 -4
- package/commands/work-on-monolithic.md +7 -2
- package/commands/work-on.md +202 -4
- package/docs/BRAND.md +14 -7
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +5 -8
- package/docs/FORGE-PROTOCOL.md +15 -1064
- package/docs/anthropic-devrel-outreach.md +2 -2
- package/docs/articles/command-decomposition-token-savings.md +214 -0
- package/docs/articles/model-release-playbook.md +131 -0
- package/docs/articles/per-repo-adaptive-scripts-token-savings.md +27 -25
- package/docs/claude-breakpoints.json +75 -5
- package/docs/design/abc-benchmark.md +1 -1
- package/docs/design/design-spec-schema.md +1 -1
- package/docs/design/divergent-generation.md +1 -1
- package/docs/design/eval-gate-runbook.md +178 -0
- package/docs/design/show-hn-draft.md +26 -1
- package/docs/design/token-cost-baseline-1298.md +59 -0
- package/docs/eval/README.md +117 -0
- package/docs/eval/corpus/seed-v1.json +6 -0
- package/docs/eval/published/claude-sonnet-4-6-2026-07-04.json +33 -0
- package/docs/site/annotations-explained.md +24 -0
- package/docs/site/command-learning-path.md +51 -14
- package/docs/site/command-reference.md +339 -113
- package/docs/site/for-companies.md +95 -0
- package/docs/site/forge-annotation-protocol.md +12 -256
- package/docs/site/getting-started.md +43 -4
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +9 -1
- package/docs/spec/eval-run-result.md +147 -0
- package/docs/spec/forge-protocol-v1.md +441 -18
- package/docs/spec/label-state-machine.md +55 -0
- package/docs/spec/review-protocol.md +158 -0
- package/docs/spec-graph-schema.md +28 -0
- package/docs/superpowers/specs/2026-07-04-economic-self-governance-design.md +181 -0
- package/docs/superpowers/specs/2026-07-04-provenance-trail-design.md +189 -0
- package/examples/forgedock-demo/README.md +41 -22
- package/examples/forgedock-demo/issues/06-bug-tags-type-validation.md +43 -0
- package/examples/forgedock-demo/issues/07-bug-negative-offset-pagination.md +42 -0
- package/examples/forgedock-demo/issues/08-bug-patch-mass-assignment.md +49 -0
- package/examples/forgedock-demo/issues/09-bug-count-injection.md +47 -0
- package/examples/forgedock-demo/issues/10-feature-sort-param.md +39 -0
- package/examples/forgedock-demo/issues/11-feature-patch-tags.md +37 -0
- package/examples/forgedock-demo/issues/12-feature-bulk-create.md +42 -0
- package/examples/forgedock-demo/issues/13-feature-archive-notes.md +46 -0
- package/examples/forgedock-demo/issues/14-refactor-extract-title-validation.md +41 -0
- package/examples/forgedock-demo/issues/15-refactor-protected-route-flag.md +43 -0
- package/examples/forgedock-demo/issues/16-refactor-extract-store.md +45 -0
- package/examples/forgedock-demo/issues/17-perf-tags-dedup-set.md +35 -0
- package/examples/forgedock-demo/issues/18-perf-remove-redundant-sort.md +37 -0
- package/examples/forgedock-demo/issues/19-docs-api-reference-full.md +53 -0
- package/examples/forgedock-demo/issues/20-docs-intentional-flaw-convention.md +46 -0
- package/examples/forgedock-demo/scripts/smoke.js +18 -1
- package/examples/forgedock-demo/src/db.js +36 -11
- package/examples/forgedock-demo/src/routes/notes.js +106 -9
- package/examples/forgedock-demo/src/server.js +4 -1
- package/package.json +3 -1
- package/packages/protocol/LICENSE +21 -0
- package/packages/protocol/src/cli.js +135 -0
- package/packages/protocol/src/emit.js +146 -0
- package/packages/protocol/src/index.js +43 -0
- package/packages/protocol/src/parse.js +229 -0
- package/packages/protocol/src/types.js +171 -0
- package/packages/protocol/src/validate.js +122 -0
- package/scripts/bench-topology-cost.mjs +372 -0
- package/scripts/check-command-docs-drift.sh +198 -0
- package/scripts/check-protocol-restatements.sh +176 -0
- package/scripts/check-registry/README.md +72 -0
- package/scripts/check-registry/manifest.json +6 -0
- package/scripts/code-index.sh +454 -0
- package/scripts/conformance-check.mjs +168 -0
- package/scripts/doctor-pipeline-state.sh +484 -0
- package/scripts/eval-gate-baseline.json +17 -0
- package/scripts/eval-gate-scorecard.mjs +242 -0
- package/scripts/eval-scorecard-aggregator.mjs +252 -0
- package/scripts/flaky-quarantine.sh +241 -0
- package/scripts/forge-annotation.sh +567 -0
- package/scripts/issue-dedup.sh +236 -0
- package/scripts/validate-annotation-node.mjs +233 -0
- package/scripts/worktree-lifecycle.sh +3 -3
- package/templates/devdocs/agent/using-forgedock.md +14 -0
- package/docs/site/.vitepress/config.mjs +0 -85
package/bin/settings-hook.mjs
CHANGED
|
@@ -12,13 +12,34 @@
|
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "fs";
|
|
13
13
|
import { dirname } from "path";
|
|
14
14
|
|
|
15
|
-
/** Substring identifying ForgeDock's own hook entry. */
|
|
15
|
+
/** Substring identifying ForgeDock's own SessionStart hook entry. */
|
|
16
16
|
export const HOOK_MARKER = "session-start.mjs";
|
|
17
17
|
|
|
18
|
+
/** Substring identifying ForgeDock's own SubagentStop hook entry (interactive engine adapter). */
|
|
19
|
+
export const SUBAGENT_STOP_MARKER = "interactive-engine.mjs";
|
|
20
|
+
|
|
21
|
+
/** Substring identifying ForgeDock's SubagentStop enforcement hook (annotation verification). */
|
|
22
|
+
export const SUBAGENT_STOP_ENFORCE_MARKER = "subagent-stop-enforce.mjs";
|
|
23
|
+
|
|
24
|
+
/** Substring identifying ForgeDock's own PreToolUse hook entry. */
|
|
25
|
+
export const PRE_TOOL_USE_MARKER = "pre-tool-use.mjs";
|
|
26
|
+
|
|
18
27
|
function isOurs(entry) {
|
|
19
28
|
return JSON.stringify(entry).includes(HOOK_MARKER);
|
|
20
29
|
}
|
|
21
30
|
|
|
31
|
+
function isOursSubagentStop(entry) {
|
|
32
|
+
return JSON.stringify(entry).includes(SUBAGENT_STOP_MARKER);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isOursSubagentStopEnforce(entry) {
|
|
36
|
+
return JSON.stringify(entry).includes(SUBAGENT_STOP_ENFORCE_MARKER);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function isOursPreToolUse(entry) {
|
|
40
|
+
return JSON.stringify(entry).includes(PRE_TOOL_USE_MARKER);
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
function readSettings(settingsPath) {
|
|
23
44
|
if (!existsSync(settingsPath)) return { settings: {}, fresh: true };
|
|
24
45
|
try {
|
|
@@ -110,3 +131,216 @@ export function removeSessionStartHook(settingsPath) {
|
|
|
110
131
|
writeSettings(settingsPath, settings);
|
|
111
132
|
return { status: "removed" };
|
|
112
133
|
}
|
|
134
|
+
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
// SubagentStop hook — interactive engine adapter (#1323)
|
|
137
|
+
// ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Merge ForgeDock's SubagentStop hook into settings.json.
|
|
141
|
+
* @param {string} settingsPath - Absolute path to ~/.claude/settings.json
|
|
142
|
+
* @param {string} hookScriptPath - Absolute path to bin/hooks/interactive-engine.mjs
|
|
143
|
+
* @returns {{ status: 'installed'|'already'|'skipped-malformed' }}
|
|
144
|
+
*/
|
|
145
|
+
export function installSubagentStopHook(settingsPath, hookScriptPath) {
|
|
146
|
+
const read = readSettings(settingsPath);
|
|
147
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
148
|
+
const { settings } = read;
|
|
149
|
+
|
|
150
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
151
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
152
|
+
return { status: "skipped-malformed" };
|
|
153
|
+
}
|
|
154
|
+
if (settings.hooks.SubagentStop !== undefined && !Array.isArray(settings.hooks.SubagentStop)) {
|
|
155
|
+
return { status: "skipped-malformed" };
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
settings.hooks = {};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const list = Array.isArray(settings.hooks.SubagentStop) ? settings.hooks.SubagentStop : [];
|
|
162
|
+
if (list.some(isOursSubagentStop)) return { status: "already" };
|
|
163
|
+
|
|
164
|
+
list.push({
|
|
165
|
+
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }],
|
|
166
|
+
});
|
|
167
|
+
settings.hooks.SubagentStop = list;
|
|
168
|
+
writeSettings(settingsPath, settings);
|
|
169
|
+
return { status: "installed" };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Remove ForgeDock's SubagentStop hook entry; leaves everything else alone.
|
|
174
|
+
* @returns {{ status: 'removed'|'absent'|'skipped-malformed' }}
|
|
175
|
+
*/
|
|
176
|
+
export function removeSubagentStopHook(settingsPath) {
|
|
177
|
+
const read = readSettings(settingsPath);
|
|
178
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
179
|
+
const { settings, fresh } = read;
|
|
180
|
+
if (fresh) return { status: "absent" };
|
|
181
|
+
|
|
182
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
183
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
184
|
+
return { status: "skipped-malformed" };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (settings.hooks && settings.hooks.SubagentStop !== undefined && !Array.isArray(settings.hooks.SubagentStop)) {
|
|
188
|
+
return { status: "skipped-malformed" };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const list = settings.hooks && Array.isArray(settings.hooks.SubagentStop)
|
|
192
|
+
? settings.hooks.SubagentStop : [];
|
|
193
|
+
const kept = list.filter((e) => !isOursSubagentStop(e));
|
|
194
|
+
if (kept.length === list.length) return { status: "absent" };
|
|
195
|
+
|
|
196
|
+
settings.hooks.SubagentStop = kept;
|
|
197
|
+
writeSettings(settingsPath, settings);
|
|
198
|
+
return { status: "removed" };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// PreToolUse hook — enforcement (#1250, #1323)
|
|
203
|
+
// ---------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Merge ForgeDock's PreToolUse hook into settings.json.
|
|
207
|
+
*
|
|
208
|
+
* The installed entry carries `matcher: "Bash"` so Claude Code's harness
|
|
209
|
+
* only invokes this hook for Bash tool calls — without it, Node spawns for
|
|
210
|
+
* every tool call (Read/Edit/Grep/Glob/...) just to exit 0 immediately
|
|
211
|
+
* (issue #1591).
|
|
212
|
+
*
|
|
213
|
+
* @param {string} settingsPath - Absolute path to ~/.claude/settings.json
|
|
214
|
+
* @param {string} hookScriptPath - Absolute path to bin/hooks/pre-tool-use.mjs
|
|
215
|
+
* @returns {{ status: 'installed'|'already'|'skipped-malformed' }}
|
|
216
|
+
*/
|
|
217
|
+
export function installPreToolUseHook(settingsPath, hookScriptPath) {
|
|
218
|
+
const read = readSettings(settingsPath);
|
|
219
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
220
|
+
const { settings } = read;
|
|
221
|
+
|
|
222
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
223
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
224
|
+
return { status: "skipped-malformed" };
|
|
225
|
+
}
|
|
226
|
+
if (settings.hooks.PreToolUse !== undefined && !Array.isArray(settings.hooks.PreToolUse)) {
|
|
227
|
+
return { status: "skipped-malformed" };
|
|
228
|
+
}
|
|
229
|
+
} else {
|
|
230
|
+
settings.hooks = {};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const list = Array.isArray(settings.hooks.PreToolUse) ? settings.hooks.PreToolUse : [];
|
|
234
|
+
if (list.some(isOursPreToolUse)) return { status: "already" };
|
|
235
|
+
|
|
236
|
+
list.push({
|
|
237
|
+
matcher: "Bash",
|
|
238
|
+
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }],
|
|
239
|
+
});
|
|
240
|
+
settings.hooks.PreToolUse = list;
|
|
241
|
+
writeSettings(settingsPath, settings);
|
|
242
|
+
return { status: "installed" };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Remove ForgeDock's PreToolUse hook entry; leaves everything else alone.
|
|
247
|
+
* @returns {{ status: 'removed'|'absent'|'skipped-malformed' }}
|
|
248
|
+
*/
|
|
249
|
+
export function removePreToolUseHook(settingsPath) {
|
|
250
|
+
const read = readSettings(settingsPath);
|
|
251
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
252
|
+
const { settings, fresh } = read;
|
|
253
|
+
if (fresh) return { status: "absent" };
|
|
254
|
+
|
|
255
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
256
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
257
|
+
return { status: "skipped-malformed" };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (settings.hooks && settings.hooks.PreToolUse !== undefined && !Array.isArray(settings.hooks.PreToolUse)) {
|
|
261
|
+
return { status: "skipped-malformed" };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const list = settings.hooks && Array.isArray(settings.hooks.PreToolUse)
|
|
265
|
+
? settings.hooks.PreToolUse : [];
|
|
266
|
+
const kept = list.filter((e) => !isOursPreToolUse(e));
|
|
267
|
+
if (kept.length === list.length) return { status: "absent" };
|
|
268
|
+
|
|
269
|
+
settings.hooks.PreToolUse = kept;
|
|
270
|
+
writeSettings(settingsPath, settings);
|
|
271
|
+
return { status: "removed" };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
// SubagentStop enforcement hook — annotation verification (#1250)
|
|
276
|
+
// ---------------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Merge ForgeDock's SubagentStop enforcement hook into settings.json.
|
|
280
|
+
*
|
|
281
|
+
* This hook (subagent-stop-enforce.mjs) is separate from the interactive
|
|
282
|
+
* engine adapter (interactive-engine.mjs): it verifies that each pipeline
|
|
283
|
+
* phase posted its FORGE: annotation before completion, and blocks (exit 2)
|
|
284
|
+
* if the annotation is missing.
|
|
285
|
+
*
|
|
286
|
+
* Version-gated: requires Claude Code v2.1.163+ (additionalContext injection).
|
|
287
|
+
*
|
|
288
|
+
* @param {string} settingsPath - Absolute path to ~/.claude/settings.json
|
|
289
|
+
* @param {string} hookScriptPath - Absolute path to bin/hooks/subagent-stop-enforce.mjs
|
|
290
|
+
* @returns {{ status: 'installed'|'already'|'skipped-malformed' }}
|
|
291
|
+
*/
|
|
292
|
+
export function installSubagentStopEnforceHook(settingsPath, hookScriptPath) {
|
|
293
|
+
const read = readSettings(settingsPath);
|
|
294
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
295
|
+
const { settings } = read;
|
|
296
|
+
|
|
297
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
298
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
299
|
+
return { status: "skipped-malformed" };
|
|
300
|
+
}
|
|
301
|
+
if (settings.hooks.SubagentStop !== undefined && !Array.isArray(settings.hooks.SubagentStop)) {
|
|
302
|
+
return { status: "skipped-malformed" };
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
settings.hooks = {};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const list = Array.isArray(settings.hooks.SubagentStop) ? settings.hooks.SubagentStop : [];
|
|
309
|
+
if (list.some(isOursSubagentStopEnforce)) return { status: "already" };
|
|
310
|
+
|
|
311
|
+
list.push({
|
|
312
|
+
hooks: [{ type: "command", command: `node "${hookScriptPath}"` }],
|
|
313
|
+
});
|
|
314
|
+
settings.hooks.SubagentStop = list;
|
|
315
|
+
writeSettings(settingsPath, settings);
|
|
316
|
+
return { status: "installed" };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Remove ForgeDock's SubagentStop enforcement hook entry; leaves everything else alone.
|
|
321
|
+
* @returns {{ status: 'removed'|'absent'|'skipped-malformed' }}
|
|
322
|
+
*/
|
|
323
|
+
export function removeSubagentStopEnforceHook(settingsPath) {
|
|
324
|
+
const read = readSettings(settingsPath);
|
|
325
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
326
|
+
const { settings, fresh } = read;
|
|
327
|
+
if (fresh) return { status: "absent" };
|
|
328
|
+
|
|
329
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
330
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
331
|
+
return { status: "skipped-malformed" };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (settings.hooks && settings.hooks.SubagentStop !== undefined && !Array.isArray(settings.hooks.SubagentStop)) {
|
|
335
|
+
return { status: "skipped-malformed" };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const list = settings.hooks && Array.isArray(settings.hooks.SubagentStop)
|
|
339
|
+
? settings.hooks.SubagentStop : [];
|
|
340
|
+
const kept = list.filter((e) => !isOursSubagentStopEnforce(e));
|
|
341
|
+
if (kept.length === list.length) return { status: "absent" };
|
|
342
|
+
|
|
343
|
+
settings.hooks.SubagentStop = kept;
|
|
344
|
+
writeSettings(settingsPath, settings);
|
|
345
|
+
return { status: "removed" };
|
|
346
|
+
}
|
package/commands/adopt.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Bootstrap an existing repo for ForgeDock — triage open issues, apply labels, suggest a starter milestone, and identify first /work-on candidates
|
|
3
3
|
argument-hint: [--dry-run | --limit <N> | --milestone | --no-milestone]
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
|
|
6
7
|
# /adopt — Repo Bootstrap & Backlog Triage
|
|
@@ -11,9 +12,11 @@ You onboard an existing repo into the ForgeDock pipeline. Unlike `/forgedock-ini
|
|
|
11
12
|
|
|
12
13
|
Run `/adopt` once after `npx forgedock init` + `/forgedock-init` to make a legacy backlog pipeline-ready without touching the issues themselves.
|
|
13
14
|
|
|
14
|
-
**Agent model policy**:
|
|
15
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
15
16
|
**NEVER use plan mode (EnterPlanMode).**
|
|
16
17
|
|
|
18
|
+
<!-- FORGE:SPEC_LOADED — adopt.md loaded and active. Agent is bound by this spec. -->
|
|
19
|
+
|
|
17
20
|
---
|
|
18
21
|
|
|
19
22
|
## Argument Parsing
|
package/commands/analytics.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Pull production analytics from GSC, Bing Webmaster, Clarity, Umami, Cloudflare, Stripe, and GA4 — generate insights and create actionable GitHub issues. Trigger when user says things like "check analytics", "look at prod analytics", "make issues from analytics", "what's happening on the site", "audit the site", "check revenue", etc.
|
|
3
|
+
install: extras
|
|
3
4
|
---
|
|
4
5
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
5
6
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -12,7 +13,9 @@ You are the analytics orchestrator. Pull data from ALL available analytics platf
|
|
|
12
13
|
|
|
13
14
|
**NEVER use plan mode (EnterPlanMode)** — it breaks execution context.
|
|
14
15
|
|
|
15
|
-
**Agent model policy**:
|
|
16
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. User can override with `--model <name>`. Pass model explicitly in every `Agent` tool call. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
17
|
+
|
|
18
|
+
<!-- FORGE:SPEC_LOADED — analytics.md loaded and active. Agent is bound by this spec. -->
|
|
16
19
|
|
|
17
20
|
---
|
|
18
21
|
|
package/commands/audit-agents.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Audit agent outputs from an orchestration run — timeline analysis, stall detection, active vs idle time breakdown
|
|
3
3
|
argument-hint: [session-id | latest | <agent-id>]
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
7
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -48,20 +49,49 @@ fi
|
|
|
48
49
|
|
|
49
50
|
### Step 1A: Find the session
|
|
50
51
|
|
|
51
|
-
Agent outputs live in
|
|
52
|
+
Agent outputs live in `~/.claude/projects/{munged-project-path}/subagents/agent-{agentId}.jsonl`.
|
|
53
|
+
The project path munging rule: both `/` and `.` are replaced with `-`
|
|
54
|
+
(e.g. `/home/user/projects/myproject` → `-home-user-projects-myproject`).
|
|
52
55
|
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
```python
|
|
57
|
+
import os, sys
|
|
58
|
+
from pathlib import Path
|
|
59
|
+
|
|
60
|
+
def _session_project_dir(project_path):
|
|
61
|
+
"""Convert a Claude Code project path to its ~/.claude/projects/ directory name.
|
|
62
|
+
Rule: both '/' and '.' are replaced with '-'.
|
|
63
|
+
Matches pipeline-health.md _session_project_dir() exactly.
|
|
64
|
+
"""
|
|
65
|
+
import re
|
|
66
|
+
return re.sub(r'[/.]', '-', project_path)
|
|
67
|
+
|
|
68
|
+
CLAUDE_PROJECTS_DIR = Path.home() / ".claude" / "projects"
|
|
69
|
+
|
|
70
|
+
# Discover all subagent JSONL files under ~/.claude/projects/
|
|
71
|
+
# Each subagent file: ~/.claude/projects/{project}/{sessionId}/subagents/agent-{agentId}.jsonl
|
|
72
|
+
subagent_files = []
|
|
73
|
+
if CLAUDE_PROJECTS_DIR.exists():
|
|
74
|
+
for jsonl_path in sorted(CLAUDE_PROJECTS_DIR.rglob("subagents/agent-*.jsonl"),
|
|
75
|
+
key=lambda p: p.stat().st_mtime, reverse=True):
|
|
76
|
+
# Only process files with actual content (> 10 lines / non-empty)
|
|
77
|
+
try:
|
|
78
|
+
size = jsonl_path.stat().st_size
|
|
79
|
+
if size < 200: # 200 bytes ≈ fewer than 10 minimal JSONL lines
|
|
80
|
+
continue
|
|
81
|
+
# Count lines portably without wc -l
|
|
82
|
+
with open(jsonl_path, 'rb') as fh:
|
|
83
|
+
line_count = sum(1 for _ in fh)
|
|
84
|
+
if line_count > 10:
|
|
85
|
+
subagent_files.append(jsonl_path)
|
|
86
|
+
except OSError:
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
if not subagent_files:
|
|
90
|
+
print("SKIPPED — transcripts unreadable on this platform")
|
|
91
|
+
print(" ~/.claude/projects/ not found or contains no subagent JSONL files.")
|
|
92
|
+
sys.exit(0)
|
|
93
|
+
|
|
94
|
+
print(f"Found {len(subagent_files)} subagent JSONL file(s) under {CLAUDE_PROJECTS_DIR}")
|
|
65
95
|
```
|
|
66
96
|
|
|
67
97
|
**Input resolution:**
|
|
@@ -72,20 +102,47 @@ done | sort -rn | head -5
|
|
|
72
102
|
|
|
73
103
|
### Step 1B: Collect agent JSONL files
|
|
74
104
|
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
105
|
+
```python
|
|
106
|
+
import os
|
|
107
|
+
from pathlib import Path
|
|
108
|
+
|
|
109
|
+
# Filter subagent_files by $ARGUMENTS (session UUID, agent ID, project fragment, or "latest")
|
|
110
|
+
ARGUMENT = "$ARGUMENTS".strip()
|
|
111
|
+
|
|
112
|
+
if not ARGUMENT or ARGUMENT.lower() == "latest":
|
|
113
|
+
# Use the most recent session — take the most recently modified subagent directory
|
|
114
|
+
# Group by parent session dir (grandparent of the subagent file)
|
|
115
|
+
by_session = {}
|
|
116
|
+
for p in subagent_files:
|
|
117
|
+
session_dir = p.parent.parent # .../projects/{project}/{sessionId}/
|
|
118
|
+
mtime = session_dir.stat().st_mtime if session_dir.exists() else 0
|
|
119
|
+
if session_dir not in by_session or mtime > by_session[session_dir][0]:
|
|
120
|
+
by_session[session_dir] = (mtime, [])
|
|
121
|
+
by_session[session_dir][1].append(p)
|
|
122
|
+
if by_session:
|
|
123
|
+
latest_session_dir = max(by_session, key=lambda d: by_session[d][0])
|
|
124
|
+
agent_files = by_session[latest_session_dir][1]
|
|
125
|
+
else:
|
|
126
|
+
agent_files = []
|
|
127
|
+
else:
|
|
128
|
+
# Filter by session UUID, agent ID, or project path fragment
|
|
129
|
+
agent_files = [
|
|
130
|
+
p for p in subagent_files
|
|
131
|
+
if ARGUMENT in str(p)
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
if not agent_files:
|
|
135
|
+
print(f"SKIPPED — no matching agent JSONL files found for argument: {ARGUMENT!r}")
|
|
136
|
+
import sys; sys.exit(0)
|
|
137
|
+
|
|
138
|
+
print(f"Processing {len(agent_files)} agent file(s)")
|
|
139
|
+
for p in agent_files:
|
|
140
|
+
agent_id = p.stem.replace("agent-", "", 1)
|
|
141
|
+
print(f" {agent_id} {p}")
|
|
86
142
|
```
|
|
87
143
|
|
|
88
144
|
Only process files with `> 10` lines (smaller files are helper/polling agents, not work-on agents).
|
|
145
|
+
Line count is computed in Python from the actual JSONL bytes — not from a symlink stub.
|
|
89
146
|
|
|
90
147
|
---
|
|
91
148
|
|
|
@@ -189,17 +246,43 @@ def parse_agent(filepath, agent_id):
|
|
|
189
246
|
max_replays = max(ts_counts.values()) if ts_counts else 1
|
|
190
247
|
resume_cycles = max_replays - 1 # first occurrence is original, rest are replays
|
|
191
248
|
|
|
192
|
-
# Build phase timeline from unique skill invocations
|
|
249
|
+
# Build phase timeline from unique skill invocations.
|
|
250
|
+
# Stall detection: a gap is a TRUE STALL only when it is >120s AND contains zero
|
|
251
|
+
# tool_use events (i.e. ends in end_turn with no tool calls in the window).
|
|
252
|
+
# Gaps that contain any tool_use block (Bash, Edit, Read, Grep, …) are ACTIVE work —
|
|
253
|
+
# the agent was building; only Skill() calls were sparse.
|
|
254
|
+
#
|
|
255
|
+
# To detect tool activity between consecutive Skill() calls we collect all
|
|
256
|
+
# tool_use timestamps from the raw lines and check whether any fall in the gap.
|
|
257
|
+
all_tool_use_ts = []
|
|
258
|
+
for raw_line in lines:
|
|
259
|
+
raw = json.loads(raw_line)
|
|
260
|
+
raw_msg = raw.get('message', {})
|
|
261
|
+
raw_content = raw_msg.get('content', [])
|
|
262
|
+
raw_ts_str = raw.get('timestamp', '')
|
|
263
|
+
if raw_ts_str and isinstance(raw_content, list):
|
|
264
|
+
for c in raw_content:
|
|
265
|
+
if c.get('type') == 'tool_use':
|
|
266
|
+
try:
|
|
267
|
+
all_tool_use_ts.append(
|
|
268
|
+
datetime.fromisoformat(raw_ts_str.replace('Z', '+00:00'))
|
|
269
|
+
)
|
|
270
|
+
except ValueError:
|
|
271
|
+
pass
|
|
272
|
+
|
|
193
273
|
phases = []
|
|
194
274
|
prev_ts = first_ts
|
|
195
275
|
for si in skill_invocations:
|
|
196
276
|
gap_sec = (si['ts'] - prev_ts).total_seconds()
|
|
277
|
+
# Any tool_use event inside the gap window means the agent was active
|
|
278
|
+
has_tool_use_in_gap = any(prev_ts < t <= si['ts'] for t in all_tool_use_ts)
|
|
279
|
+
is_stall = gap_sec > 120 and not has_tool_use_in_gap
|
|
197
280
|
phases.append({
|
|
198
281
|
'ts': si['ts'],
|
|
199
282
|
'skill': si['skill'],
|
|
200
283
|
'args': si['args'],
|
|
201
284
|
'gap_from_prev_sec': gap_sec,
|
|
202
|
-
'is_stall':
|
|
285
|
+
'is_stall': is_stall, # True only when >2 min AND zero tool calls in gap
|
|
203
286
|
})
|
|
204
287
|
prev_ts = si['ts']
|
|
205
288
|
|
|
@@ -433,6 +516,6 @@ This enables tracking whether orchestrator/prompt changes actually improved thro
|
|
|
433
516
|
## Notes
|
|
434
517
|
|
|
435
518
|
- **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`).
|
|
436
|
-
- **
|
|
519
|
+
- **Subagent files**: Located at `~/.claude/projects/{munged-project-path}/{sessionId}/subagents/agent-{agentId}.jsonl`. The munging rule: both `/` and `.` in the project path are replaced with `-` (e.g. `/home/user/projects/myapp` → `-home-user-projects-myapp`). There are no platform-specific temp paths or symlinks involved.
|
|
437
520
|
- **Size filtering**: Only analyze files > 10 lines. Small files are helper/polling agents spawned by the orchestrator for status checks.
|
|
438
521
|
- **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`.
|
package/commands/audit.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Trace a production issue or pipeline failure end-to-end through GitHub artifacts, then file a detailed improvement issue to the Forge repo
|
|
3
3
|
argument-hint: <description of what went wrong> [--issue N] [--pr N] [--repo prefix]
|
|
4
|
+
install: extras
|
|
4
5
|
---
|
|
5
6
|
<!-- SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios -->
|
|
6
7
|
<!-- SPDX-License-Identifier: AGPL-3.0-or-later -->
|
|
@@ -13,7 +14,7 @@ You are Forge's post-mortem auditor. When something reaches production that shou
|
|
|
13
14
|
|
|
14
15
|
**This command ALWAYS files issues to `{FORGE_REPO}`.** The target project is read-only — you only read its GitHub artifacts as evidence.
|
|
15
16
|
|
|
16
|
-
**Agent model policy**:
|
|
17
|
+
**Agent model policy**: `model: "sonnet"` (standard tier). Fallback: `model: "opus"` if rate-limited. Feature gate: pass `effort` in Task/Skill spawns only on Claude Code >= 2.1.154.
|
|
17
18
|
|
|
18
19
|
**NEVER use plan mode (EnterPlanMode).**
|
|
19
20
|
|
|
@@ -286,6 +287,24 @@ Create a detailed, actionable issue in the **Forge repository**.
|
|
|
286
287
|
|
|
287
288
|
### 4B: Create the issue
|
|
288
289
|
|
|
290
|
+
**Before creating, run the deterministic dedup check:** <!-- Added: forge#1335 -->
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
AUDIT_TITLE="fix(pipeline): {one-line description of what the pipeline missed}"
|
|
294
|
+
DEDUP_RESULT=$(scripts/issue-dedup.sh "$AUDIT_TITLE" -R {FORGE_REPO} 2>&1)
|
|
295
|
+
DEDUP_EXIT=$?
|
|
296
|
+
if [ "$DEDUP_EXIT" -eq 1 ]; then
|
|
297
|
+
echo "DEDUP: Audit finding near-duplicate — $DEDUP_RESULT"
|
|
298
|
+
echo "Skipping creation. Comment on the existing issue with new evidence instead."
|
|
299
|
+
# Do NOT call gh issue create for this audit finding
|
|
300
|
+
elif [ "$DEDUP_EXIT" -eq 2 ]; then
|
|
301
|
+
echo "DEDUP: Usage error — $DEDUP_RESULT"
|
|
302
|
+
# Do NOT call gh issue create for this audit finding — fix the invocation and retry
|
|
303
|
+
fi
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Only call `gh issue create` when the dedup script exits 0:
|
|
307
|
+
|
|
289
308
|
```bash
|
|
290
309
|
gh issue create -R {FORGE_REPO} \
|
|
291
310
|
--title "fix(pipeline): {one-line description of what the pipeline missed}" \
|