forgedock 1.1.11 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -3
- package/bin/cli-spawn-shared.mjs +25 -0
- package/bin/engine/phases.mjs +178 -28
- package/bin/engine/state.mjs +39 -7
- package/bin/engine-cli.mjs +85 -3
- package/bin/engine.mjs +71 -8
- package/bin/forgedock.mjs +322 -20
- package/bin/hooks/pre-tool-use.mjs +345 -10
- package/bin/init-enrich-api.mjs +12 -4
- package/bin/init-enrich-cli.mjs +328 -0
- package/bin/init-enrich.mjs +137 -0
- package/bin/journey.mjs +116 -15
- package/bin/runner.mjs +653 -10
- package/commands/adopt.md +1 -1
- package/commands/analytics.md +10 -7
- package/commands/audit-agents.md +29 -6
- package/commands/audit.md +9 -20
- package/commands/autopilot.md +52 -78
- package/commands/changelog.md +1 -1
- package/commands/ci-audit.md +19 -10
- package/commands/cleanup.md +1 -1
- package/commands/compat-audit.md +1 -1
- package/commands/deploy-info.md +1 -1
- package/commands/deploy-pr.md +1 -1
- package/commands/design-bench.md +1 -1
- package/commands/design-render-critique-loop.md +1 -1
- package/commands/diagnose.md +1 -1
- package/commands/forge-stats.md +6 -5
- package/commands/forgedock-init.md +1 -1
- package/commands/geo-audit.md +9 -6
- package/commands/incident-response.md +15 -5
- package/commands/issue.md +302 -15
- package/commands/milestone.md +45 -18
- package/commands/optimize.md +1 -1
- package/commands/orchestrate/phase-1-resolve.md +20 -5
- package/commands/orchestrate/phase-4-execution.md +8 -1
- package/commands/orchestrate/phase-5-cleanup.md +43 -2
- package/commands/orchestrate/phase-6-report.md +1 -0
- package/commands/orchestrate.md +1 -1
- package/commands/pipeline-health/phase-4-proposals.md +40 -10
- package/commands/pipeline-health/phase-5-report.md +140 -13
- package/commands/pipeline-health.md +1 -1
- package/commands/pipeline-resume.md +1 -1
- package/commands/pipeline-status.md +1 -1
- package/commands/qa-sweep.md +27 -4
- package/commands/recover-orphans.md +1 -1
- package/commands/replay.md +16 -2
- package/commands/review-pr-agents/protocols.md +4 -0
- package/commands/review-pr-agents/spec-cli.md +179 -0
- package/commands/review-pr-agents.md +7 -4
- package/commands/review-pr-staging.md +44 -15
- package/commands/review-pr.md +122 -28
- package/commands/rollback.md +15 -5
- package/commands/security-audit.md +14 -11
- package/commands/signal-planner.md +32 -12
- package/commands/spec-doctor.md +1 -1
- package/commands/sync-ecosystem.md +54 -11
- package/commands/test-gate.md +49 -11
- package/commands/upgrade-deps.md +518 -0
- package/commands/validate.md +16 -5
- package/commands/work-on/build/architect.md +1 -1
- package/commands/work-on/build/context.md +1 -1
- package/commands/work-on/build/implement.md +2 -2
- package/commands/work-on/build/validate.md +64 -2
- package/commands/work-on/build.md +10 -2
- package/commands/work-on/close.md +1 -1
- package/commands/work-on/decompose.md +35 -25
- package/commands/work-on/investigate.md +2 -2
- package/commands/work-on/remediate.md +1 -1
- package/commands/work-on/review.md +5 -3
- package/commands/work-on-monolithic.md +1 -1
- package/commands/work-on.md +28 -7
- package/docs/AIDER.md +264 -0
- package/docs/AUTOPILOT-CRON.md +130 -0
- package/docs/CI.md +18 -0
- package/docs/CONFIG.md +41 -2
- package/docs/CURSOR.md +135 -0
- package/docs/OPENCODE.md +312 -0
- package/docs/demo.gif +0 -0
- package/docs/eval/README.md +4 -4
- package/docs/launch/show-hn.md +3 -3
- package/docs/site/annotations-explained.md +2 -2
- package/docs/site/architecture.md +180 -0
- package/docs/site/command-reference.md +38 -0
- package/docs/site/configuration.md +243 -0
- package/docs/site/for-companies.md +4 -4
- package/docs/site/forge-annotation-protocol.md +1 -1
- package/docs/site/github-is-the-memory.md +2 -2
- package/docs/site/how-it-works.md +1 -1
- package/docs/site/public/llms-full.txt +380 -0
- package/docs/site/public/llms.txt +35 -0
- package/docs/spec/review-protocol.md +6 -0
- package/docs/spec-graph-schema.md +3 -3
- package/package.json +2 -2
- package/packages/protocol/README.md +75 -0
- package/packages/protocol/src/card.js +114 -0
- package/packages/protocol/src/cli.js +4 -77
- package/packages/protocol/src/emit.js +14 -3
- package/packages/protocol/src/index.js +2 -1
- package/packages/protocol/src/parse.js +17 -8
- package/packages/protocol/src/types.js +1 -1
- package/packages/protocol/src/validate.js +11 -0
- package/scripts/check-command-side-effects.sh +261 -0
- package/scripts/check-spec-markers.sh +273 -0
- package/scripts/classify-lane.sh +29 -7
- package/scripts/forge-run.sh +322 -0
- package/templates/cross-repo-trigger.yml +282 -0
- package/templates/cursor/forge.mdc +148 -0
- package/templates/github-actions/forgedock-autopilot.yml +113 -0
package/bin/journey.mjs
CHANGED
|
@@ -601,6 +601,7 @@ export function makeCtx(overrides = {}) {
|
|
|
601
601
|
const argv = overrides.argv ?? process.argv.slice(2);
|
|
602
602
|
const nodeVersion = overrides.nodeVersion ?? process.versions.node;
|
|
603
603
|
const enrichFn = overrides.enrichFn ?? enrich;
|
|
604
|
+
const isCliAvailableFn = overrides.isCliAvailableFn ?? isClaudeCliAvailable;
|
|
604
605
|
const openFn = overrides.openFn ?? openUrl;
|
|
605
606
|
const confirmFn = overrides.confirmFn ?? confirm;
|
|
606
607
|
const platform = overrides.platform ?? process.platform;
|
|
@@ -626,6 +627,7 @@ export function makeCtx(overrides = {}) {
|
|
|
626
627
|
release,
|
|
627
628
|
linkStrategy: "symlink",
|
|
628
629
|
enrichFn,
|
|
630
|
+
isCliAvailableFn,
|
|
629
631
|
openFn,
|
|
630
632
|
confirmFn,
|
|
631
633
|
exec: (cmd, args) =>
|
|
@@ -745,7 +747,8 @@ export async function preflight(ctx) {
|
|
|
745
747
|
// Act II — Forging: command symlinks + SessionStart hook (Task 6)
|
|
746
748
|
// ---------------------------------------------------------------------------
|
|
747
749
|
|
|
748
|
-
import { mkdir, symlink, readlink, lstat, readdir, rename, copyFile, readFile, writeFile, unlink } from "fs/promises";
|
|
750
|
+
import { mkdir, symlink, readlink, lstat, readdir, rename, copyFile, readFile, writeFile, unlink, rm } from "fs/promises";
|
|
751
|
+
import { compareVersions } from "./registry.mjs";
|
|
749
752
|
import { relative, dirname as pathDirname } from "path";
|
|
750
753
|
import {
|
|
751
754
|
installSessionStartHook,
|
|
@@ -1071,6 +1074,54 @@ async function copyDirIfChanged(srcDir, destDir) {
|
|
|
1071
1074
|
return { copied, unchanged };
|
|
1072
1075
|
}
|
|
1073
1076
|
|
|
1077
|
+
/**
|
|
1078
|
+
* Recursively delete entries in `destDir` that no longer exist in `srcDir`.
|
|
1079
|
+
* The mirror-image of copyDirIfChanged(): that function is additive-only (it
|
|
1080
|
+
* copies new/changed files but never removes anything), so a file dropped or
|
|
1081
|
+
* renamed upstream would otherwise linger in the persisted `~/.forge/` copy
|
|
1082
|
+
* forever (forge#2133). A missing `srcDir` or `destDir` is treated as
|
|
1083
|
+
* "nothing to reconcile" rather than an error — mirrors copyDirIfChanged()'s
|
|
1084
|
+
* own ENOENT handling.
|
|
1085
|
+
*
|
|
1086
|
+
* @param {string} srcDir
|
|
1087
|
+
* @param {string} destDir
|
|
1088
|
+
* @returns {Promise<{ removed: number }>}
|
|
1089
|
+
*/
|
|
1090
|
+
async function removeOrphans(srcDir, destDir) {
|
|
1091
|
+
let destEntries;
|
|
1092
|
+
try {
|
|
1093
|
+
destEntries = await readdir(destDir, { withFileTypes: true });
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
if (err.code === "ENOENT") return { removed: 0 };
|
|
1096
|
+
throw err;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
let srcNames = null;
|
|
1100
|
+
try {
|
|
1101
|
+
const srcEntries = await readdir(srcDir, { withFileTypes: true });
|
|
1102
|
+
srcNames = new Set(srcEntries.map((e) => e.name));
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
if (err.code !== "ENOENT") throw err;
|
|
1105
|
+
// srcDir missing entirely — every dest entry is an orphan.
|
|
1106
|
+
srcNames = new Set();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
let removed = 0;
|
|
1110
|
+
for (const entry of destEntries) {
|
|
1111
|
+
const destPath = join(destDir, entry.name);
|
|
1112
|
+
if (!srcNames.has(entry.name)) {
|
|
1113
|
+
await rm(destPath, { recursive: true, force: true });
|
|
1114
|
+
removed++;
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
if (entry.isDirectory()) {
|
|
1118
|
+
const sub = await removeOrphans(join(srcDir, entry.name), destPath);
|
|
1119
|
+
removed += sub.removed;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
return { removed };
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1074
1125
|
/**
|
|
1075
1126
|
* Copy ForgeDock's own installable payload (bin/, commands/, scripts/,
|
|
1076
1127
|
* templates/) from wherever the package currently resolved — npm global
|
|
@@ -1113,6 +1164,7 @@ async function copyDirIfChanged(srcDir, destDir) {
|
|
|
1113
1164
|
* version: string,
|
|
1114
1165
|
* filesCopied?: number,
|
|
1115
1166
|
* filesUnchanged?: number,
|
|
1167
|
+
* filesRemoved?: number,
|
|
1116
1168
|
* }>}
|
|
1117
1169
|
*/
|
|
1118
1170
|
export async function persistHome(ctx) {
|
|
@@ -1140,15 +1192,55 @@ export async function persistHome(ctx) {
|
|
|
1140
1192
|
// proceed with version === ""
|
|
1141
1193
|
}
|
|
1142
1194
|
|
|
1195
|
+
// Downgrade guard (forge#2133): if a newer version is already persisted at
|
|
1196
|
+
// ~/.forge/version than the source package we're about to copy from, skip
|
|
1197
|
+
// the copy entirely rather than silently overwriting newer files with
|
|
1198
|
+
// older ones. This happens when a stale resolved package (e.g. a plain
|
|
1199
|
+
// `npx forgedock update` that resolved an old global install) runs after
|
|
1200
|
+
// ~/.forge/ was already refreshed to a newer version by some other path
|
|
1201
|
+
// (e.g. `npx forgedock@latest`). Both `version` and the persisted value
|
|
1202
|
+
// must be non-empty for the guard to apply — an unreadable/missing
|
|
1203
|
+
// ~/.forge/version is treated as "no guard needed" so first-run persist
|
|
1204
|
+
// is never blocked.
|
|
1205
|
+
const persistedVersionPath = join(persistedHome, "version");
|
|
1206
|
+
let existingPersistedVersion = "";
|
|
1207
|
+
let persistedVersionFileExists = true;
|
|
1208
|
+
try {
|
|
1209
|
+
existingPersistedVersion = readFileSync(persistedVersionPath, "utf-8").trim();
|
|
1210
|
+
} catch {
|
|
1211
|
+
// missing/unreadable — proceed, nothing to guard against. Tracked
|
|
1212
|
+
// separately from existingPersistedVersion's "" default so the
|
|
1213
|
+
// versionChanged check below (a fresh persist with no prior version
|
|
1214
|
+
// file) is still correctly treated as "changed" even when the source
|
|
1215
|
+
// package's own version also resolves to "" (e.g. missing package.json).
|
|
1216
|
+
persistedVersionFileExists = false;
|
|
1217
|
+
}
|
|
1218
|
+
if (
|
|
1219
|
+
version &&
|
|
1220
|
+
existingPersistedVersion &&
|
|
1221
|
+
compareVersions(version, existingPersistedVersion) < 0
|
|
1222
|
+
) {
|
|
1223
|
+
return {
|
|
1224
|
+
forgeHome: persistedHome,
|
|
1225
|
+
migrated: false,
|
|
1226
|
+
skipped: true,
|
|
1227
|
+
reason: `refusing to downgrade ~/.forge/ from v${existingPersistedVersion} to v${version} — source package is older than what's already persisted`,
|
|
1228
|
+
version: existingPersistedVersion,
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1143
1232
|
try {
|
|
1144
1233
|
await mkdir(persistedHome, { recursive: true });
|
|
1145
1234
|
|
|
1146
1235
|
let filesCopied = 0;
|
|
1147
1236
|
let filesUnchanged = 0;
|
|
1237
|
+
let filesRemoved = 0;
|
|
1148
1238
|
for (const name of PERSIST_HOME_DIRS) {
|
|
1149
1239
|
const res = await copyDirIfChanged(join(source, name), join(persistedHome, name));
|
|
1150
1240
|
filesCopied += res.copied;
|
|
1151
1241
|
filesUnchanged += res.unchanged;
|
|
1242
|
+
const pruned = await removeOrphans(join(source, name), join(persistedHome, name));
|
|
1243
|
+
filesRemoved += pruned.removed;
|
|
1152
1244
|
}
|
|
1153
1245
|
|
|
1154
1246
|
// Also persist package.json itself (not just PERSIST_HOME_DIRS) — several
|
|
@@ -1179,18 +1271,19 @@ export async function persistHome(ctx) {
|
|
|
1179
1271
|
|
|
1180
1272
|
// Write ~/.forge/version — content-compared like everything else here so
|
|
1181
1273
|
// an unchanged version doesn't touch the file's mtime on every re-run.
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1274
|
+
// Reuses persistedVersionPath/existingPersistedVersion read above for the
|
|
1275
|
+
// downgrade guard rather than re-reading the same file a second time. A
|
|
1276
|
+
// version file that didn't exist yet (persistedVersionFileExists: false)
|
|
1277
|
+
// always counts as "changed", regardless of whether the source's own
|
|
1278
|
+
// version happens to also be "" (e.g. missing package.json) — otherwise
|
|
1279
|
+
// "" !== "" would wrongly read as unchanged on a fresh, never-persisted
|
|
1280
|
+
// ~/.forge/.
|
|
1281
|
+
let versionChanged = !persistedVersionFileExists || existingPersistedVersion !== version;
|
|
1189
1282
|
if (versionChanged) {
|
|
1190
|
-
const tmpVersionPath =
|
|
1283
|
+
const tmpVersionPath = persistedVersionPath + ".tmp";
|
|
1191
1284
|
try {
|
|
1192
1285
|
writeFileSync(tmpVersionPath, version + "\n", "utf-8");
|
|
1193
|
-
renameSync(tmpVersionPath,
|
|
1286
|
+
renameSync(tmpVersionPath, persistedVersionPath);
|
|
1194
1287
|
} catch (err) {
|
|
1195
1288
|
try { unlinkSync(tmpVersionPath); } catch { /* best-effort cleanup */ }
|
|
1196
1289
|
throw err;
|
|
@@ -1199,11 +1292,12 @@ export async function persistHome(ctx) {
|
|
|
1199
1292
|
|
|
1200
1293
|
return {
|
|
1201
1294
|
forgeHome: persistedHome,
|
|
1202
|
-
migrated: filesCopied > 0 || versionChanged,
|
|
1295
|
+
migrated: filesCopied > 0 || filesRemoved > 0 || versionChanged,
|
|
1203
1296
|
skipped: false,
|
|
1204
1297
|
version,
|
|
1205
1298
|
filesCopied,
|
|
1206
1299
|
filesUnchanged,
|
|
1300
|
+
filesRemoved,
|
|
1207
1301
|
};
|
|
1208
1302
|
} catch (err) {
|
|
1209
1303
|
// Fail-open (forge#383): a permission error, disk-full, etc. must never
|
|
@@ -1720,7 +1814,8 @@ export async function forge(ctx) {
|
|
|
1720
1814
|
// ---------------------------------------------------------------------------
|
|
1721
1815
|
|
|
1722
1816
|
import { detectConfig } from "./init-detect.mjs";
|
|
1723
|
-
import { enrich } from "./init-enrich
|
|
1817
|
+
import { enrich, resolveEnrichBackend } from "./init-enrich.mjs";
|
|
1818
|
+
import { isClaudeCliAvailable } from "./runner.mjs";
|
|
1724
1819
|
import { annotatedReviewScreen, box, confirm, getLogoTagline } from "./tui.mjs";
|
|
1725
1820
|
|
|
1726
1821
|
const badgeOf = (field) => field.confidence;
|
|
@@ -1751,16 +1846,22 @@ export async function read(ctx) {
|
|
|
1751
1846
|
];
|
|
1752
1847
|
await revealRows(rows, { mode: ctx.mode, motion: ctx.motion, writer: w });
|
|
1753
1848
|
|
|
1754
|
-
|
|
1849
|
+
const enrichBackend = resolveEnrichBackend({
|
|
1850
|
+
cwd: ctx.cwd,
|
|
1851
|
+
env: ctx.env,
|
|
1852
|
+
isCliAvailableFn: ctx.isCliAvailableFn,
|
|
1853
|
+
});
|
|
1854
|
+
|
|
1855
|
+
if (enrichBackend !== "none") {
|
|
1755
1856
|
try {
|
|
1756
1857
|
w.write(" " + dimLine(ctx, "✦ enriching with AI…") + "\n");
|
|
1757
|
-
const enriched = await ctx.enrichFn(draft);
|
|
1858
|
+
const enriched = await ctx.enrichFn(draft, { backend: enrichBackend, cwd: ctx.cwd, env: ctx.env });
|
|
1758
1859
|
if (enriched && typeof enriched === "object") draft = enriched;
|
|
1759
1860
|
} catch {
|
|
1760
1861
|
w.write(" " + dimLine(ctx, "✦ AI enrichment unavailable — continuing with detection only") + "\n");
|
|
1761
1862
|
}
|
|
1762
1863
|
} else {
|
|
1763
|
-
w.write(" " + dimLine(ctx, "✦ no ANTHROPIC_API_KEY — skipping AI enrichment") + "\n");
|
|
1864
|
+
w.write(" " + dimLine(ctx, "✦ no Claude Code CLI or ANTHROPIC_API_KEY — skipping AI enrichment") + "\n");
|
|
1764
1865
|
}
|
|
1765
1866
|
|
|
1766
1867
|
return { draft, description };
|