forgedock 1.0.22 → 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 +255 -211
- package/bin/batch-runner.mjs +339 -0
- package/bin/cinema.mjs +320 -0
- package/bin/demo.mjs +92 -7
- package/bin/engine/phases.mjs +168 -0
- package/bin/engine/projector.mjs +32 -0
- package/bin/engine/reconcile.mjs +11 -0
- package/bin/engine/runlog.mjs +79 -0
- package/bin/engine/state.mjs +32 -0
- package/bin/engine-cli.mjs +222 -0
- package/bin/engine.mjs +138 -0
- package/bin/forgedock.mjs +896 -1540
- 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 +841 -0
- package/bin/registry.mjs +139 -0
- package/bin/report.mjs +447 -0
- package/bin/runner.mjs +136 -20
- package/bin/settings-hook.mjs +346 -0
- package/bin/tui.mjs +6 -3
- 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 +84 -12
- 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 +63 -4
- package/commands/design-bench.md +6 -1
- package/commands/design-render-critique-loop.md +2 -1
- package/commands/design.md +1 -0
- package/commands/diagnose.md +2 -1
- package/commands/explain.md +2 -1
- package/commands/forge-stats.md +122 -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 +24 -3
- 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 -2027
- 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 +326 -42
- 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 +612 -56
- 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 +19 -5
- package/commands/work-on/build/validate.md +62 -6
- package/commands/work-on/build.md +187 -30
- package/commands/work-on/close.md +132 -5
- package/commands/work-on/decompose.md +19 -1
- package/commands/work-on/investigate.md +117 -2
- package/commands/work-on/review.md +48 -4
- package/commands/work-on-monolithic.md +8 -2
- package/commands/work-on.md +247 -7
- package/docs/BRAND.md +16 -5
- package/docs/CI.md +2 -2
- package/docs/CODEX.md +36 -1
- package/docs/CONFIG.md +44 -42
- 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 +58 -12
- package/docs/site/github-is-the-memory.md +113 -0
- package/docs/site/how-it-works.md +3 -13
- package/docs/site/index.md +10 -2
- 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/plans/2026-07-03-cinematic-onboarding-tui.md +2339 -0
- package/docs/superpowers/plans/2026-07-03-durable-execution-engine.md +1223 -0
- package/docs/superpowers/specs/2026-07-03-cinematic-onboarding-tui-design.md +157 -0
- package/docs/superpowers/specs/2026-07-03-durable-execution-engine-design.md +194 -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 +12 -4
- 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/verify-env-vars.sh +18 -2
- package/scripts/verify-host-headers.sh +4 -4
- package/scripts/verify-route-registration.sh +2 -2
- package/scripts/worktree-lifecycle.sh +202 -0
- package/templates/devdocs/agent/using-forgedock.md +14 -0
package/bin/runner.mjs
CHANGED
|
@@ -46,9 +46,9 @@ import {
|
|
|
46
46
|
import { join, dirname, basename, relative, isAbsolute } from "path";
|
|
47
47
|
import { execSync, spawnSync } from "child_process";
|
|
48
48
|
|
|
49
|
-
const DEFAULT_MODEL = "claude-sonnet-
|
|
49
|
+
const DEFAULT_MODEL = "claude-sonnet-5";
|
|
50
50
|
const DEFAULT_MAX_ITERATIONS = 50;
|
|
51
|
-
const DEFAULT_MAX_TOKENS =
|
|
51
|
+
const DEFAULT_MAX_TOKENS = 16384;
|
|
52
52
|
// Default wall-clock limit for a single run_bash command. Chosen to be
|
|
53
53
|
// generous enough for CI steps (git clones, test suites) while bounding
|
|
54
54
|
// the worst-case hang to 5 minutes. Override via FORGEDOCK_BASH_TIMEOUT (ms).
|
|
@@ -338,6 +338,31 @@ function resolveConfinedPath(cwd, p) {
|
|
|
338
338
|
);
|
|
339
339
|
}
|
|
340
340
|
|
|
341
|
+
// Residual TOCTOU assumption: we return the lexical `resolved` path rather
|
|
342
|
+
// than `realResolved` so that callers (readFileSync / writeFileSync) work
|
|
343
|
+
// correctly with not-yet-existing paths (where realResolved === resolved
|
|
344
|
+
// anyway, since there is nothing to dereference). The check above verified
|
|
345
|
+
// confinement on the realpath of the nearest *existing* ancestor, but a
|
|
346
|
+
// concurrent actor that mutates the filesystem between this check and the
|
|
347
|
+
// caller's open() call could in theory introduce a symlink that evades the
|
|
348
|
+
// guard — a classic time-of-check / time-of-use (TOCTOU) window.
|
|
349
|
+
//
|
|
350
|
+
// This is acceptable under the current threat model because:
|
|
351
|
+
// 1. Tool calls are executed in a strictly sequential, synchronous
|
|
352
|
+
// `for...of` loop (see the runLive tool-use loop). There is no async
|
|
353
|
+
// gap and no concurrent tool execution within the runner itself.
|
|
354
|
+
// 2. A prompt-injected model therefore cannot create this race — it would
|
|
355
|
+
// need to schedule two tool calls simultaneously, which the loop
|
|
356
|
+
// prevents.
|
|
357
|
+
// 3. Only an independent, external OS process running concurrently could
|
|
358
|
+
// exploit the window, which is outside the stated single-shot
|
|
359
|
+
// prompt-injection threat model.
|
|
360
|
+
//
|
|
361
|
+
// If the runner is ever refactored to execute tool calls concurrently or
|
|
362
|
+
// asynchronously, this assumption MUST be revisited. Mitigations to
|
|
363
|
+
// consider at that point include: operating on `realResolved` directly
|
|
364
|
+
// (eliminating the lexical/realpath split), or opening the file with an
|
|
365
|
+
// O_NOFOLLOW-equivalent flag so the kernel rejects a symlink at open time.
|
|
341
366
|
return resolved;
|
|
342
367
|
}
|
|
343
368
|
|
|
@@ -539,31 +564,73 @@ export function getToolHandlers(cwd) {
|
|
|
539
564
|
// available. `shell: shell || true` preserves execSync's implicit
|
|
540
565
|
// "always run via a shell" behavior for compound commands (&&, pipes,
|
|
541
566
|
// heredocs) when resolveBashShell() returns undefined.
|
|
567
|
+
// maxBuffer defaults to 50 MB. FORGEDOCK_MAX_BUFFER_BYTES overrides it so
|
|
568
|
+
// tests can trigger ENOBUFS with a small value without generating 50 MB of
|
|
569
|
+
// output. Non-positive or non-finite values fall back to the default.
|
|
570
|
+
const rawMaxBuffer = parseInt(process.env.FORGEDOCK_MAX_BUFFER_BYTES, 10);
|
|
571
|
+
const maxBuffer =
|
|
572
|
+
Number.isFinite(rawMaxBuffer) && rawMaxBuffer > 0
|
|
573
|
+
? rawMaxBuffer
|
|
574
|
+
: 50 * 1024 * 1024;
|
|
542
575
|
const result = spawnSync(command, {
|
|
543
576
|
cwd,
|
|
544
577
|
encoding: "utf-8",
|
|
545
|
-
maxBuffer
|
|
578
|
+
maxBuffer,
|
|
546
579
|
timeout: timeoutMs,
|
|
547
580
|
env: childEnv,
|
|
548
581
|
shell: shell || true,
|
|
549
582
|
});
|
|
550
583
|
const stdout = result.stdout ? String(result.stdout) : "";
|
|
551
584
|
const stderr = result.stderr ? String(result.stderr) : "";
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
//
|
|
557
|
-
//
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
585
|
+
// Check ENOBUFS BEFORE the timedOut fallback. When a command both exceeds
|
|
586
|
+
// maxBuffer AND runs past timeoutMs, both result.error.code === "ENOBUFS"
|
|
587
|
+
// and elapsedMs >= timeoutMs are simultaneously true. The elapsedMs
|
|
588
|
+
// fallback (check 2 below) is a heuristic catch-all that must not
|
|
589
|
+
// override a specific, authoritative error code. ENOBUFS is always the
|
|
590
|
+
// correct diagnosis when present — surface it first. (issue #1364)
|
|
591
|
+
if (result.error?.code === "ENOBUFS") {
|
|
592
|
+
// The process ran but produced more output than maxBuffer allows.
|
|
593
|
+
// spawnSync populates result.stdout/stderr up to the limit before
|
|
594
|
+
// setting result.error — surface what was captured so the agent has
|
|
595
|
+
// actionable context rather than a misleading "failed to start" error.
|
|
596
|
+
const partial = (stdout + stderr).trim();
|
|
597
|
+
const bufDisplay =
|
|
598
|
+
maxBuffer < 1024
|
|
599
|
+
? `${maxBuffer} bytes`
|
|
600
|
+
: maxBuffer < 1024 * 1024
|
|
601
|
+
? `${Math.round(maxBuffer / 1024)}KB`
|
|
602
|
+
: `${Math.round(maxBuffer / (1024 * 1024))}MB`;
|
|
603
|
+
throw new Error(
|
|
604
|
+
`Command output exceeded ${bufDisplay} buffer limit (output truncated).` +
|
|
605
|
+
(partial ? `\nPartial output:\n${partial}` : ""),
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
// Detect timeout. Two reliable indicators:
|
|
609
|
+
// 1. result.error.code === "ETIMEDOUT" — Node sets this on the error
|
|
610
|
+
// object specifically when spawnSync's own `timeout` option fires
|
|
611
|
+
// and it kills the child. This is the authoritative Node-initiated
|
|
612
|
+
// kill signal.
|
|
613
|
+
// 2. elapsedMs >= timeoutMs — elapsed-wall-time fallback for platforms
|
|
614
|
+
// (primarily Windows) where the ETIMEDOUT error code is unreliable.
|
|
615
|
+
// If we spent at least as long as the timeout, the timer must have
|
|
616
|
+
// fired, since a voluntarily-exiting process would have returned
|
|
617
|
+
// before then.
|
|
618
|
+
//
|
|
619
|
+
// NOTE: `result.signal === "SIGTERM"` is intentionally NOT included.
|
|
620
|
+
// spawnSync sets result.signal for ANY signal that terminated the child —
|
|
621
|
+
// including SIGTERM from external sources (supervisors, orchestrators,
|
|
622
|
+
// `timeout(1)` wrappers, or the child self-signaling) that are entirely
|
|
623
|
+
// unrelated to this runner's timeout. Including it caused any externally-
|
|
624
|
+
// sent SIGTERM to be misreported as a ForgeDock timeout (issue #1240).
|
|
625
|
+
//
|
|
626
|
+
// Gate all of this on `result.status === null` so a legitimately-
|
|
627
|
+
// completed process near the timeout boundary is never misreported.
|
|
628
|
+
// ENOBUFS is excluded above — it is checked first so a command that both
|
|
629
|
+
// overflows the buffer AND exceeds the timeout gets the correct message.
|
|
561
630
|
const elapsedMs = Date.now() - startMs;
|
|
562
631
|
const timedOut =
|
|
563
632
|
result.status === null &&
|
|
564
|
-
(result.error?.code === "ETIMEDOUT" ||
|
|
565
|
-
result.signal === "SIGTERM" ||
|
|
566
|
-
elapsedMs >= timeoutMs);
|
|
633
|
+
(result.error?.code === "ETIMEDOUT" || elapsedMs >= timeoutMs);
|
|
567
634
|
if (timedOut) {
|
|
568
635
|
const timeoutSecs = Math.round(timeoutMs / 1000);
|
|
569
636
|
const partial = (stdout + stderr).trim();
|
|
@@ -622,20 +689,36 @@ export function renderDryRun(ctx) {
|
|
|
622
689
|
|
|
623
690
|
/**
|
|
624
691
|
* Render the pipeline summary card emitted on completion.
|
|
625
|
-
* @param {{command: string, args: string[], iterations: number, stopReason: string}} ctx
|
|
692
|
+
* @param {{command: string, args: string[], iterations: number, stopReason: string, usage?: object|null}} ctx
|
|
626
693
|
* @returns {string}
|
|
627
694
|
*/
|
|
628
695
|
export function renderSummaryCard(ctx) {
|
|
629
|
-
const { command, args, iterations, stopReason } = ctx;
|
|
696
|
+
const { command, args, iterations, stopReason, usage = null } = ctx;
|
|
630
697
|
const argStr = Array.isArray(args) ? args.join(" ") : String(args ?? "");
|
|
631
|
-
|
|
698
|
+
const lines = [
|
|
632
699
|
``,
|
|
633
700
|
`┌─ ForgeDock pipeline summary ────────────────────────────`,
|
|
634
701
|
`│ command: /${command} ${argStr}`.trimEnd(),
|
|
635
702
|
`│ iterations: ${iterations}`,
|
|
636
703
|
`│ stop: ${stopReason}`,
|
|
704
|
+
];
|
|
705
|
+
if (usage) {
|
|
706
|
+
lines.push(
|
|
707
|
+
`│ tokens: ${usage.input_tokens} in / ${usage.output_tokens} out`,
|
|
708
|
+
`│ cache: ${usage.cache_read_input_tokens} read / ${usage.cache_creation_input_tokens} write`,
|
|
709
|
+
);
|
|
710
|
+
} else {
|
|
711
|
+
lines.push(`│ tokens: N/A`);
|
|
712
|
+
}
|
|
713
|
+
lines.push(
|
|
637
714
|
`└─────────────────────────────────────────────────────────`,
|
|
638
|
-
|
|
715
|
+
);
|
|
716
|
+
if (usage) {
|
|
717
|
+
lines.push(
|
|
718
|
+
`FORGE:USAGE_JSON:${JSON.stringify(usage)}`,
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
return lines.join("\n");
|
|
639
722
|
}
|
|
640
723
|
|
|
641
724
|
// ---------------------------------------------------------------------------
|
|
@@ -701,9 +784,31 @@ export async function runCommand(opts = {}) {
|
|
|
701
784
|
}
|
|
702
785
|
|
|
703
786
|
const client = new Anthropic({ apiKey });
|
|
787
|
+
// NOTE on /proc/$PPID/environ (Linux): deleting from process.env calls
|
|
788
|
+
// unsetenv() at the C level, which does NOT update the kernel's
|
|
789
|
+
// /proc/<pid>/environ snapshot (that is fixed at execve() time). A
|
|
790
|
+
// run_bash payload can still read /proc/$PPID/environ and recover the
|
|
791
|
+
// key if it was set in the shell environment before the node process
|
|
792
|
+
// started. The only effective JavaScript-layer mitigation is the
|
|
793
|
+
// child-env scrub in run_bash: `childEnv = {...process.env}; delete
|
|
794
|
+
// childEnv.ANTHROPIC_API_KEY` — this prevents the child from inheriting
|
|
795
|
+
// the key in its own process.env, which is sufficient for
|
|
796
|
+
// `process.env.ANTHROPIC_API_KEY` access. The /proc/$PPID/environ vector
|
|
797
|
+
// is a known Linux limitation at the OS level; closing it would require
|
|
798
|
+
// a native module (e.g. prctl PR_SET_DUMPABLE) or process isolation.
|
|
799
|
+
// See: issue #1370 for tracking this known limitation.
|
|
704
800
|
const handlers = getToolHandlers(cwd);
|
|
705
801
|
const messages = [{ role: "user", content: userMessage }];
|
|
706
802
|
|
|
803
|
+
// Accumulate token usage across all messages.create() calls in this run.
|
|
804
|
+
// Field names match the Anthropic SDK's response.usage object exactly.
|
|
805
|
+
const usage = {
|
|
806
|
+
input_tokens: 0,
|
|
807
|
+
output_tokens: 0,
|
|
808
|
+
cache_creation_input_tokens: 0,
|
|
809
|
+
cache_read_input_tokens: 0,
|
|
810
|
+
};
|
|
811
|
+
|
|
707
812
|
let iterations = 0;
|
|
708
813
|
while (iterations < maxIterations) {
|
|
709
814
|
iterations++;
|
|
@@ -716,6 +821,14 @@ export async function runCommand(opts = {}) {
|
|
|
716
821
|
messages,
|
|
717
822
|
});
|
|
718
823
|
|
|
824
|
+
// Accumulate usage — guard each field for null/undefined (SDK omits
|
|
825
|
+
// cache fields when prompt caching is not active).
|
|
826
|
+
const ru = response.usage ?? {};
|
|
827
|
+
usage.input_tokens += ru.input_tokens ?? 0;
|
|
828
|
+
usage.output_tokens += ru.output_tokens ?? 0;
|
|
829
|
+
usage.cache_creation_input_tokens += ru.cache_creation_input_tokens ?? 0;
|
|
830
|
+
usage.cache_read_input_tokens += ru.cache_read_input_tokens ?? 0;
|
|
831
|
+
|
|
719
832
|
messages.push({ role: "assistant", content: response.content });
|
|
720
833
|
|
|
721
834
|
for (const block of response.content) {
|
|
@@ -735,6 +848,7 @@ export async function runCommand(opts = {}) {
|
|
|
735
848
|
args,
|
|
736
849
|
iterations,
|
|
737
850
|
stopReason: response.stop_reason,
|
|
851
|
+
usage,
|
|
738
852
|
}),
|
|
739
853
|
);
|
|
740
854
|
return {
|
|
@@ -742,6 +856,7 @@ export async function runCommand(opts = {}) {
|
|
|
742
856
|
command: spec.name,
|
|
743
857
|
iterations,
|
|
744
858
|
stopReason: response.stop_reason,
|
|
859
|
+
usage,
|
|
745
860
|
};
|
|
746
861
|
}
|
|
747
862
|
|
|
@@ -774,7 +889,8 @@ export async function runCommand(opts = {}) {
|
|
|
774
889
|
args,
|
|
775
890
|
iterations,
|
|
776
891
|
stopReason: "max_iterations",
|
|
892
|
+
usage,
|
|
777
893
|
}),
|
|
778
894
|
);
|
|
779
|
-
return { status: "max-iterations", command: spec.name, iterations };
|
|
895
|
+
return { status: "max-iterations", command: spec.name, iterations, usage };
|
|
780
896
|
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/settings-hook.mjs — Idempotent SessionStart hook registration in
|
|
3
|
+
* ~/.claude/settings.json.
|
|
4
|
+
*
|
|
5
|
+
* Contract (spec: "Hook wiring"):
|
|
6
|
+
* - Read-modify-write; unrelated keys and hooks preserved verbatim
|
|
7
|
+
* - Our entry identified by HOOK_MARKER in its command string
|
|
8
|
+
* - Malformed JSON → skip and report, NEVER overwrite the file
|
|
9
|
+
* - Absolute hook script path baked in at install time
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "fs";
|
|
13
|
+
import { dirname } from "path";
|
|
14
|
+
|
|
15
|
+
/** Substring identifying ForgeDock's own SessionStart hook entry. */
|
|
16
|
+
export const HOOK_MARKER = "session-start.mjs";
|
|
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
|
+
|
|
27
|
+
function isOurs(entry) {
|
|
28
|
+
return JSON.stringify(entry).includes(HOOK_MARKER);
|
|
29
|
+
}
|
|
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
|
+
|
|
43
|
+
function readSettings(settingsPath) {
|
|
44
|
+
if (!existsSync(settingsPath)) return { settings: {}, fresh: true };
|
|
45
|
+
try {
|
|
46
|
+
const parsed = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
47
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
48
|
+
return { settings: parsed, fresh: false };
|
|
49
|
+
} catch {
|
|
50
|
+
return null; // malformed — caller must not write
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function writeSettings(settingsPath, settings) {
|
|
55
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
56
|
+
const tmp = settingsPath + ".tmp";
|
|
57
|
+
writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
58
|
+
renameSync(tmp, settingsPath);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Merge ForgeDock's SessionStart hook into settings.json.
|
|
63
|
+
* @param {string} settingsPath - Absolute path to ~/.claude/settings.json
|
|
64
|
+
* @param {string} hookScriptPath - Absolute path to bin/hooks/session-start.mjs
|
|
65
|
+
* @returns {{ status: 'installed'|'already'|'skipped-malformed' }}
|
|
66
|
+
*/
|
|
67
|
+
export function installSessionStartHook(settingsPath, hookScriptPath) {
|
|
68
|
+
const read = readSettings(settingsPath);
|
|
69
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
70
|
+
const { settings } = read;
|
|
71
|
+
|
|
72
|
+
// Validate hooks shape: must be a plain object (not array, string, etc.)
|
|
73
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
74
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
75
|
+
return { status: "skipped-malformed" };
|
|
76
|
+
}
|
|
77
|
+
// Validate SessionStart if present: must be an array
|
|
78
|
+
if (settings.hooks.SessionStart !== undefined && !Array.isArray(settings.hooks.SessionStart)) {
|
|
79
|
+
return { status: "skipped-malformed" };
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// hooks missing or null → initialize to fresh object
|
|
83
|
+
settings.hooks = {};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const list = Array.isArray(settings.hooks.SessionStart) ? settings.hooks.SessionStart : [];
|
|
87
|
+
if (list.some(isOurs)) return { status: "already" };
|
|
88
|
+
|
|
89
|
+
list.push({
|
|
90
|
+
hooks: [
|
|
91
|
+
{
|
|
92
|
+
type: "command",
|
|
93
|
+
command: `node "${hookScriptPath}"`,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
});
|
|
97
|
+
settings.hooks.SessionStart = list;
|
|
98
|
+
writeSettings(settingsPath, settings);
|
|
99
|
+
return { status: "installed" };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Remove ForgeDock's SessionStart hook entry; leaves everything else alone.
|
|
104
|
+
* @returns {{ status: 'removed'|'absent'|'skipped-malformed' }}
|
|
105
|
+
*/
|
|
106
|
+
export function removeSessionStartHook(settingsPath) {
|
|
107
|
+
const read = readSettings(settingsPath);
|
|
108
|
+
if (read === null) return { status: "skipped-malformed" };
|
|
109
|
+
const { settings, fresh } = read;
|
|
110
|
+
if (fresh) return { status: "absent" };
|
|
111
|
+
|
|
112
|
+
// Validate hooks shape: must be a plain object (not array, string, etc.)
|
|
113
|
+
if (settings.hooks !== undefined && settings.hooks !== null) {
|
|
114
|
+
if (Array.isArray(settings.hooks) || typeof settings.hooks !== "object") {
|
|
115
|
+
return { status: "skipped-malformed" };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate SessionStart shape if present: must be an array
|
|
120
|
+
if (settings.hooks && settings.hooks.SessionStart !== undefined && !Array.isArray(settings.hooks.SessionStart)) {
|
|
121
|
+
return { status: "skipped-malformed" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const list = settings.hooks && Array.isArray(settings.hooks.SessionStart)
|
|
125
|
+
? settings.hooks.SessionStart
|
|
126
|
+
: [];
|
|
127
|
+
const kept = list.filter((e) => !isOurs(e));
|
|
128
|
+
if (kept.length === list.length) return { status: "absent" };
|
|
129
|
+
|
|
130
|
+
settings.hooks.SessionStart = kept;
|
|
131
|
+
writeSettings(settingsPath, settings);
|
|
132
|
+
return { status: "removed" };
|
|
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/bin/tui.mjs
CHANGED
|
@@ -837,7 +837,7 @@ const REVIEW_FIELDS = [
|
|
|
837
837
|
*/
|
|
838
838
|
export async function annotatedReviewScreen(
|
|
839
839
|
draft,
|
|
840
|
-
{ hasExistingConfig = false, existingContent = "", showSources = false } = {},
|
|
840
|
+
{ hasExistingConfig = false, existingContent = "", showSources = false, extraFields = {} } = {},
|
|
841
841
|
) {
|
|
842
842
|
// Helper — pull a field from the draft by path, or return a low-confidence placeholder.
|
|
843
843
|
function getField(draftPath) {
|
|
@@ -862,7 +862,10 @@ export async function annotatedReviewScreen(
|
|
|
862
862
|
const whys = {};
|
|
863
863
|
|
|
864
864
|
for (const fd of REVIEW_FIELDS) {
|
|
865
|
-
const field =
|
|
865
|
+
const field =
|
|
866
|
+
fd.draftPath === null && extraFields[fd.key]
|
|
867
|
+
? extraFields[fd.key]
|
|
868
|
+
: getField(fd.draftPath);
|
|
866
869
|
values[fd.key] = field.value;
|
|
867
870
|
confidences[fd.key] = field.confidence;
|
|
868
871
|
sources[fd.key] = field.source;
|
|
@@ -987,7 +990,7 @@ export async function annotatedReviewScreen(
|
|
|
987
990
|
}
|
|
988
991
|
|
|
989
992
|
process.stdout.write(
|
|
990
|
-
` ${dim("Press")} ${bold("Enter")} ${dim("to
|
|
993
|
+
` ${dim("Press")} ${bold("Enter")} ${dim("to forge, or a number to edit a field")}\n\n`,
|
|
991
994
|
);
|
|
992
995
|
}
|
|
993
996
|
|
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
|
|