forgedock 1.1.1 → 1.1.3
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 +24 -2
- 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 +70 -20
- 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 +518 -361
- 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/deploy-pr.md +485 -0
- 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/fix-ci.md +577 -0
- 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/recover-orphans.md +576 -0
- 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 +16 -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 +120 -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 +386 -111
- 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 +254 -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/journey.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* init-detect.mjs, init-enrich-api.mjs, tui.annotatedReviewScreen, registry.mjs
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { existsSync, readFileSync, writeFileSync, renameSync } from "fs";
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, renameSync, unlinkSync } from "fs";
|
|
13
13
|
import { join, basename } from "path";
|
|
14
14
|
|
|
15
15
|
// ---------------------------------------------------------------------------
|
|
@@ -114,7 +114,17 @@ branches:
|
|
|
114
114
|
# health_patterns:
|
|
115
115
|
# - '"status": "ok"'
|
|
116
116
|
`;
|
|
117
|
-
|
|
117
|
+
// Atomic write: write to a temp file first, then rename into place.
|
|
118
|
+
// If the write fails (e.g. ENOSPC), the original file is untouched and
|
|
119
|
+
// any partial .tmp is cleaned up — no corrupt or missing forge.yaml.
|
|
120
|
+
const tmpPath = outputPath + ".tmp";
|
|
121
|
+
try {
|
|
122
|
+
writeFileSync(tmpPath, content, "utf-8");
|
|
123
|
+
renameSync(tmpPath, outputPath);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
try { unlinkSync(tmpPath); } catch { /* best-effort cleanup */ }
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
118
128
|
const todoCount = (content.match(/# TODO\(forgedock:/g) || []).length;
|
|
119
129
|
return { todoCount };
|
|
120
130
|
}
|
|
@@ -349,7 +359,12 @@ export async function preflight(ctx) {
|
|
|
349
359
|
|
|
350
360
|
import { mkdir, symlink, readlink, lstat, readdir, rename, copyFile, readFile, writeFile, unlink } from "fs/promises";
|
|
351
361
|
import { relative, dirname as pathDirname } from "path";
|
|
352
|
-
import {
|
|
362
|
+
import {
|
|
363
|
+
installSessionStartHook,
|
|
364
|
+
installSubagentStopHook,
|
|
365
|
+
installPreToolUseHook,
|
|
366
|
+
removeSubagentStopEnforceHook,
|
|
367
|
+
} from "./settings-hook.mjs";
|
|
353
368
|
|
|
354
369
|
/**
|
|
355
370
|
* Parse the `install:` tier from a command spec's YAML frontmatter block.
|
|
@@ -361,7 +376,7 @@ import { installSessionStartHook } from "./settings-hook.mjs";
|
|
|
361
376
|
*
|
|
362
377
|
* Tier values:
|
|
363
378
|
* 'core' — install for all users (default when key is absent or on any error)
|
|
364
|
-
* 'extras' — opt-in, not
|
|
379
|
+
* 'extras' — opt-in, not installed by default; use `npx forgedock install --extras` (#1257)
|
|
365
380
|
* 'internal' — ForgeDock development only; never installed to user machines
|
|
366
381
|
*
|
|
367
382
|
* Fail-open: any read error, malformed frontmatter, or unrecognised value falls
|
|
@@ -407,22 +422,27 @@ export function parseInstallTier(filePath) {
|
|
|
407
422
|
* Files with `install: internal` in their YAML frontmatter are excluded — they
|
|
408
423
|
* are ForgeDock-development-only specs that must never reach user machines.
|
|
409
424
|
* Files with `install: core` (or no `install:` key) are included. Files with
|
|
410
|
-
* `install: extras` are excluded from the default install surface
|
|
411
|
-
*
|
|
425
|
+
* `install: extras` are excluded from the default install surface; they are
|
|
426
|
+
* available via `npx forgedock install --extras` (implemented in #1257).
|
|
427
|
+
*
|
|
428
|
+
* @param {string} dir - Directory to search recursively.
|
|
429
|
+
* @param {{ includeExtras?: boolean }} [opts] - Options.
|
|
430
|
+
* includeExtras: when true, also include `install: extras` specs (opt-in tier).
|
|
412
431
|
*/
|
|
413
|
-
export async function findMarkdownFiles(dir) {
|
|
432
|
+
export async function findMarkdownFiles(dir, opts = {}) {
|
|
433
|
+
const { includeExtras = false } = opts;
|
|
414
434
|
const results = [];
|
|
415
435
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
416
436
|
for (const entry of entries) {
|
|
417
437
|
const full = join(dir, entry.name);
|
|
418
438
|
if (entry.isDirectory()) {
|
|
419
|
-
results.push(...(await findMarkdownFiles(full)));
|
|
439
|
+
results.push(...(await findMarkdownFiles(full, opts)));
|
|
420
440
|
} else if (entry.name.endsWith(".md")) {
|
|
421
441
|
const tier = parseInstallTier(full);
|
|
422
|
-
if (tier === "core") {
|
|
442
|
+
if (tier === "core" || (includeExtras && tier === "extras")) {
|
|
423
443
|
results.push(full);
|
|
424
444
|
}
|
|
425
|
-
// 'internal'
|
|
445
|
+
// 'internal' is always excluded from the install surface
|
|
426
446
|
}
|
|
427
447
|
}
|
|
428
448
|
return results.sort();
|
|
@@ -473,7 +493,7 @@ export async function forge(ctx) {
|
|
|
473
493
|
const manifest = await loadCopiedManifest(manifestPath);
|
|
474
494
|
let manifestChanged = false;
|
|
475
495
|
|
|
476
|
-
const files = await findMarkdownFiles(commandsDir);
|
|
496
|
+
const files = await findMarkdownFiles(commandsDir, { includeExtras: !!ctx.includeExtras });
|
|
477
497
|
let installed = 0, updated = 0, skipped = 0, copied = 0;
|
|
478
498
|
const barWidth = 24;
|
|
479
499
|
let barShown = false;
|
|
@@ -595,6 +615,23 @@ export async function forge(ctx) {
|
|
|
595
615
|
const settingsPath = join(ctx.home, ".claude", "settings.json");
|
|
596
616
|
const { status: hookStatus } = installSessionStartHook(settingsPath, hookScript);
|
|
597
617
|
|
|
618
|
+
// Install enforcement hooks (#1250): PreToolUse (branch/label validation)
|
|
619
|
+
// and SubagentStop interactive engine adapter. Both are idempotent and
|
|
620
|
+
// always installed (fail-open if settings.json is malformed, same
|
|
621
|
+
// contract as SessionStart hook).
|
|
622
|
+
const preToolUseScript = join(ctx.forgeHome, "bin", "hooks", "pre-tool-use.mjs");
|
|
623
|
+
const subagentStopScript = join(ctx.forgeHome, "bin", "hooks", "interactive-engine.mjs");
|
|
624
|
+
const { status: preToolUseStatus } = installPreToolUseHook(settingsPath, preToolUseScript);
|
|
625
|
+
installSubagentStopHook(settingsPath, subagentStopScript);
|
|
626
|
+
|
|
627
|
+
// SubagentStop annotation-verifier hook (#1250) is NOT installed: its
|
|
628
|
+
// trigger condition (a `FORGE:PHASE_START` marker in the transcript) is
|
|
629
|
+
// never emitted anywhere in the pipeline, so it always exits 0 with zero
|
|
630
|
+
// enforcement effect while still spawning a process + reading the
|
|
631
|
+
// transcript on every SubagentStop (forge#1527). Actively clean up any
|
|
632
|
+
// prior installation instead of installing it.
|
|
633
|
+
const { status: subagentStopEnforceStatus } = removeSubagentStopEnforceHook(settingsPath);
|
|
634
|
+
|
|
598
635
|
// Housekeeping — must never abort the receipt or the hook.
|
|
599
636
|
let manifestSaveFailed = false;
|
|
600
637
|
if (manifestChanged) {
|
|
@@ -624,7 +661,23 @@ export async function forge(ctx) {
|
|
|
624
661
|
w.write(` ${glyph(true)} SessionStart hook ${hookStatus === "already" ? "active" : "registered"} ${dimLine(ctx, settingsPath)}\n`);
|
|
625
662
|
}
|
|
626
663
|
|
|
627
|
-
|
|
664
|
+
// Report enforcement hook status.
|
|
665
|
+
if (preToolUseStatus !== null) {
|
|
666
|
+
w.write(` ${glyph(true)} PreToolUse enforcement hook ${preToolUseStatus === "already" ? "active" : "registered"} ${dimLine(ctx, "(branch/label enforcement)")}\n`);
|
|
667
|
+
} else {
|
|
668
|
+
w.write(" " + dimLine(ctx, "PreToolUse hook skipped — requires Claude Code v2.1.163+") + "\n");
|
|
669
|
+
}
|
|
670
|
+
// "skipped-malformed" and "absent" are intentionally silent here:
|
|
671
|
+
// - skipped-malformed: installSessionStartHook() above reads the same
|
|
672
|
+
// settingsPath and already prints the malformed-JSON fix-card, so a
|
|
673
|
+
// second message would just repeat the same signal.
|
|
674
|
+
// - absent: the expected steady state (nothing to remove) — this
|
|
675
|
+
// function only reports state *changes*, not no-ops.
|
|
676
|
+
if (subagentStopEnforceStatus === "removed") {
|
|
677
|
+
w.write(` ${glyph(true)} SubagentStop enforcement hook removed ${dimLine(ctx, "(non-functional — see forge#1527)")}\n`);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return { installed, updated, skipped, copied, total: files.length, hookStatus, preToolUseStatus, subagentStopEnforceStatus };
|
|
628
681
|
}
|
|
629
682
|
|
|
630
683
|
// ---------------------------------------------------------------------------
|
|
@@ -744,11 +797,16 @@ export function celebrate(ctx, summary) {
|
|
|
744
797
|
w.write(` ${glyph} ${summary.total} commands · hook ${summary.hookStatus === "skipped-malformed" ? "skipped" : "active"} ${dimLine(ctx, hookNote)}\n`);
|
|
745
798
|
}
|
|
746
799
|
w.write("\n");
|
|
800
|
+
if (summary.isMinimal) {
|
|
801
|
+
w.write(" " + dimLine(ctx, "see docs/CONFIG.md for optional sections") + "\n");
|
|
802
|
+
}
|
|
747
803
|
w.write(
|
|
748
804
|
box(
|
|
749
805
|
[
|
|
750
|
-
` 1.
|
|
751
|
-
` 2. run
|
|
806
|
+
` 1. run npx forgedock labels setup — bootstrap GitHub labels`,
|
|
807
|
+
` 2. run npx forgedock doctor — verify the install is green`,
|
|
808
|
+
` 3. open claude in this repo`,
|
|
809
|
+
` 4. run /issue <title> then /work-on <number>`,
|
|
752
810
|
],
|
|
753
811
|
{ title: "what's next" },
|
|
754
812
|
),
|
package/bin/registry.mjs
CHANGED
|
@@ -376,3 +376,142 @@ export async function clearNudgeSeen(dir) {
|
|
|
376
376
|
delete registry.nudgeSeen[absDir];
|
|
377
377
|
});
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
// ---------------------------------------------------------------------------
|
|
381
|
+
// Claude Code version detection and breakpoints resolution (issue #1252)
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
|
|
384
|
+
// execFileSync is not imported at the top of this module (it only imports fs
|
|
385
|
+
// builtins). Import it lazily here using createRequire — safe in Node ESM.
|
|
386
|
+
import { createRequire } from "module";
|
|
387
|
+
const _require = createRequire(import.meta.url);
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Detect the installed Claude Code version by running `claude --version`.
|
|
391
|
+
*
|
|
392
|
+
* Returns the version string (e.g. "2.1.197") or null if Claude Code is not
|
|
393
|
+
* installed or the version cannot be parsed. Never throws — callers can safely
|
|
394
|
+
* check for null and degrade gracefully.
|
|
395
|
+
*
|
|
396
|
+
* @returns {string | null} Semver-style version string or null.
|
|
397
|
+
*/
|
|
398
|
+
export function detectClaudeVersion() {
|
|
399
|
+
try {
|
|
400
|
+
const { execFileSync } = _require("child_process");
|
|
401
|
+
const raw = execFileSync("claude", ["--version"], {
|
|
402
|
+
encoding: "utf-8",
|
|
403
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
404
|
+
timeout: 5000,
|
|
405
|
+
}).trim();
|
|
406
|
+
// Claude Code outputs e.g. "Claude Code 2.1.197" or just "2.1.197"
|
|
407
|
+
const m = raw.match(/(\d+\.\d+\.\d+)/);
|
|
408
|
+
return m ? m[1] : null;
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Compare two semver-style version strings (e.g. "2.1.152" vs "2.1.197").
|
|
416
|
+
*
|
|
417
|
+
* Returns:
|
|
418
|
+
* -1 if a < b
|
|
419
|
+
* 0 if a === b
|
|
420
|
+
* 1 if a > b
|
|
421
|
+
*
|
|
422
|
+
* Non-numeric components are compared lexicographically. Handles missing
|
|
423
|
+
* components by treating them as 0.
|
|
424
|
+
*
|
|
425
|
+
* @param {string} a
|
|
426
|
+
* @param {string} b
|
|
427
|
+
* @returns {-1 | 0 | 1}
|
|
428
|
+
*/
|
|
429
|
+
export function compareVersions(a, b) {
|
|
430
|
+
const pa = String(a).split(".").map((s) => parseInt(s, 10) || 0);
|
|
431
|
+
const pb = String(b).split(".").map((s) => parseInt(s, 10) || 0);
|
|
432
|
+
const len = Math.max(pa.length, pb.length);
|
|
433
|
+
for (let i = 0; i < len; i++) {
|
|
434
|
+
const va = pa[i] ?? 0;
|
|
435
|
+
const vb = pb[i] ?? 0;
|
|
436
|
+
if (va < vb) return -1;
|
|
437
|
+
if (va > vb) return 1;
|
|
438
|
+
}
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Load and parse docs/claude-breakpoints.json relative to FORGE_HOME.
|
|
444
|
+
*
|
|
445
|
+
* Returns the parsed breakpoints array or an empty array on any error (file
|
|
446
|
+
* missing, malformed JSON, etc.). Consumers must pass the FORGE_HOME path.
|
|
447
|
+
*
|
|
448
|
+
* @param {string} forgeHome - Absolute path to the ForgeDock repo root.
|
|
449
|
+
* @returns {Array<object>} Array of breakpoint objects.
|
|
450
|
+
*/
|
|
451
|
+
export function loadBreakpoints(forgeHome) {
|
|
452
|
+
try {
|
|
453
|
+
const bpPath = join(forgeHome, "docs", "claude-breakpoints.json");
|
|
454
|
+
const raw = readFileSync(bpPath, "utf-8");
|
|
455
|
+
const parsed = JSON.parse(raw);
|
|
456
|
+
return Array.isArray(parsed.breakpoints) ? parsed.breakpoints : [];
|
|
457
|
+
} catch {
|
|
458
|
+
return [];
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Resolve all breakpoints applicable to a given Claude Code version.
|
|
464
|
+
*
|
|
465
|
+
* A breakpoint is "applicable" if its version is > the supplied version,
|
|
466
|
+
* meaning the user is on an older version and will be affected by this
|
|
467
|
+
* breakpoint. This surfaces the set of features/changes the user is missing.
|
|
468
|
+
*
|
|
469
|
+
* If no version is supplied, all breakpoints are returned (useful for
|
|
470
|
+
* listing the full registry).
|
|
471
|
+
*
|
|
472
|
+
* Results are sorted by version ascending.
|
|
473
|
+
*
|
|
474
|
+
* @param {Array<object>} breakpoints - Array from loadBreakpoints().
|
|
475
|
+
* @param {string | null} installedVersion - The user's installed Claude Code version, or null.
|
|
476
|
+
* @returns {Array<object>} Breakpoints the user is missing or all breakpoints if no version.
|
|
477
|
+
*/
|
|
478
|
+
export function resolveBreakpoints(breakpoints, installedVersion) {
|
|
479
|
+
if (!installedVersion) return [...breakpoints].sort((a, b) => compareVersions(a.version, b.version));
|
|
480
|
+
return breakpoints
|
|
481
|
+
.filter((bp) => compareVersions(bp.version, installedVersion) > 0)
|
|
482
|
+
.sort((a, b) => compareVersions(a.version, b.version));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Resolve a single breakpoint by feature_key.
|
|
487
|
+
*
|
|
488
|
+
* Returns the breakpoint object with the matching `feature_key`, or null
|
|
489
|
+
* if not found. Useful for conditional logic: "does this install support
|
|
490
|
+
* effort_levels?" → `resolveBreakpoint(bps, 'effort_levels')`.
|
|
491
|
+
*
|
|
492
|
+
* @param {Array<object>} breakpoints - Array from loadBreakpoints().
|
|
493
|
+
* @param {string} featureKey - The `feature_key` value to look up.
|
|
494
|
+
* @returns {object | null} The matching breakpoint or null.
|
|
495
|
+
*/
|
|
496
|
+
export function resolveBreakpoint(breakpoints, featureKey) {
|
|
497
|
+
return breakpoints.find((bp) => bp.feature_key === featureKey) ?? null;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Check whether a detected Claude Code version meets or exceeds the version
|
|
502
|
+
* required for a given feature_key.
|
|
503
|
+
*
|
|
504
|
+
* Returns true if the feature is available, false if not, and null if the
|
|
505
|
+
* feature_key is unknown or the installed version is not detectable.
|
|
506
|
+
*
|
|
507
|
+
* @param {Array<object>} breakpoints - Array from loadBreakpoints().
|
|
508
|
+
* @param {string} featureKey - Feature to check.
|
|
509
|
+
* @param {string | null} installedVersion - Detected Claude Code version.
|
|
510
|
+
* @returns {boolean | null}
|
|
511
|
+
*/
|
|
512
|
+
export function hasFeature(breakpoints, featureKey, installedVersion) {
|
|
513
|
+
if (!installedVersion) return null;
|
|
514
|
+
const bp = resolveBreakpoint(breakpoints, featureKey);
|
|
515
|
+
if (!bp) return null;
|
|
516
|
+
return compareVersions(installedVersion, bp.version) >= 0;
|
|
517
|
+
}
|