forge-workflow 0.1.0-beta.4 → 0.1.0-beta.5
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/AGENTS.md +14 -7
- package/CHANGELOG.md +43 -1
- package/README.md +6 -2
- package/bin/forge-cmd.js +20 -0
- package/bin/forge.js +16 -374
- package/docs/INDEX.md +1 -1
- package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
- package/docs/guides/MIGRATION.md +4 -4
- package/docs/guides/SETUP.md +16 -16
- package/docs/reference/COMMANDS.md +8 -5
- package/docs/reference/INSIGHTS_RECAP.md +9 -20
- package/docs/reference/RELEASE.md +5 -3
- package/docs/reference/TOOLCHAIN.md +8 -0
- package/docs/reference/protected-state-surfaces.md +4 -4
- package/docs/reference/shepherd.md +54 -25
- package/lefthook.yml +12 -0
- package/lib/activation/ensure-forge-home.js +33 -15
- package/lib/adapters/pr-state-adapter.js +344 -142
- package/lib/audit-evidence.js +71 -110
- package/lib/capped-jsonl-log.js +236 -0
- package/lib/commands/_registry.js +2 -2
- package/lib/commands/clean.js +196 -32
- package/lib/commands/dev.js +4 -33
- package/lib/commands/hooks.js +223 -25
- package/lib/commands/insights.js +8 -3
- package/lib/commands/merge.js +600 -40
- package/lib/commands/pr.js +1 -1
- package/lib/commands/preflight.js +11 -2
- package/lib/commands/prime.js +21 -8
- package/lib/commands/push.js +41 -51
- package/lib/commands/recall.js +60 -16
- package/lib/commands/recap.js +6 -1
- package/lib/commands/release.js +17 -2
- package/lib/commands/setup.js +191 -94
- package/lib/commands/shepherd.js +13 -1
- package/lib/commands/ship.js +22 -23
- package/lib/commands/skill.js +119 -11
- package/lib/commands/status.js +17 -1
- package/lib/commands/test.js +24 -34
- package/lib/commands/worktree.js +220 -42
- package/lib/core/runtime-graph.js +1 -1
- package/lib/doc-assertions.js +297 -0
- package/lib/existing-tdd-gate.js +253 -0
- package/lib/forge-context.js +1 -4
- package/lib/forge-issues.js +56 -32
- package/lib/git-defaults.js +56 -0
- package/lib/harness-capability-matrix.js +3 -3
- package/lib/hook-renderer.js +93 -4
- package/lib/insights.js +96 -80
- package/lib/kernel/backing-issue.js +14 -2
- package/lib/kernel/broker.js +16 -0
- package/lib/kernel/cli-broker-factory.js +12 -1
- package/lib/kernel/close-on-merge.js +154 -0
- package/lib/kernel/fs-class.js +42 -25
- package/lib/kernel/sqlite-driver.js +153 -29
- package/lib/lefthook-wiring.js +21 -1
- package/lib/memory/router.js +16 -1
- package/lib/memory-digest.js +47 -15
- package/lib/memory-recall-events.js +145 -0
- package/lib/memory-recall.js +71 -10
- package/lib/merge-rules.js +8 -4
- package/lib/npm-publish-workflow.js +272 -0
- package/lib/orientation.js +68 -43
- package/lib/plugin-catalog.js +14 -4
- package/lib/pr-bundle.js +5 -6
- package/lib/pr-monitor/journal.js +18 -2
- package/lib/pr-monitor/reconcile-executor.js +224 -41
- package/lib/pr-monitor/render-summary.js +196 -0
- package/lib/pr-monitor/shepherd-lease.js +10 -1
- package/lib/pr-monitor/watch-lifecycle.js +13 -1
- package/lib/pr-pull.js +33 -14
- package/lib/pr-shepherd.js +34 -8
- package/lib/preflight/gates.js +65 -18
- package/lib/preflight/runner.js +5 -0
- package/lib/project-memory.js +33 -1
- package/lib/protected-state-authority.js +305 -0
- package/lib/protected-state-surfaces.js +64 -44
- package/lib/release-readiness.js +51 -4
- package/lib/shell-utils.js +1 -1
- package/lib/skills-sync.js +6 -3
- package/lib/smart-merge.js +28 -4
- package/lib/symlink-utils.js +74 -26
- package/lib/upgrade-safety.js +39 -0
- package/lib/using-forge.js +19 -6
- package/package.json +6 -7
- package/scripts/doc-asserting-tests.js +158 -0
- package/scripts/lib/behavioral-eval-runner.js +310 -0
- package/scripts/lib/behavioral-eval-runtime.js +456 -0
- package/scripts/lib/eval-evidence.js +328 -0
- package/scripts/lib/eval-runner.js +81 -41
- package/scripts/lib/immutable-eval-corpus.js +309 -0
- package/scripts/lib/promotion-evidence-loader.js +94 -0
- package/scripts/lib/promotion-scorecard.js +314 -0
- package/scripts/npm-release-receipt.js +134 -0
- package/scripts/process-tree.js +761 -0
- package/scripts/protected-state-check.js +47 -22
- package/scripts/run-command-eval.js +29 -1
- package/scripts/sync-d20-audit.js +172 -0
- package/scripts/test-full-suite.js +249 -37
- package/scripts/test.js +176 -43
- package/skills/review/SKILL.md +4 -11
- package/skills/review/evals/scorecard.json +3 -3
- package/skills/rollback/SKILL.md +4 -11
- package/skills/rollback/evals/scorecard.json +3 -3
- package/skills/shepherd/SKILL.md +20 -14
- package/skills/shepherd/evals/scorecard.json +2 -2
- package/skills/ship/SKILL.md +4 -12
- package/skills/ship/evals/scorecard.json +3 -3
- package/skills/worktree/SKILL.md +6 -1
- package/skills/worktree/evals/scorecard.json +2 -2
- package/lib/beads-setup.js +0 -538
- package/lib/beads-sync-scaffold.js +0 -189
- package/lib/pat-setup.js +0 -207
- package/lib/pr-monitor/render-sticky.js +0 -206
- package/lib/pr-monitor/upsert-sticky.js +0 -169
- package/scripts/beads-context.sh +0 -577
- package/scripts/beads-migrate-to-dolt.sh +0 -7
- package/scripts/beads-upgrade-smoke.sh +0 -284
- package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
package/lib/commands/setup.js
CHANGED
|
@@ -28,6 +28,7 @@ const VERSION = packageJson.version;
|
|
|
28
28
|
// packaged-ASSET read below goes through `getPackageRoot(packageDir)`; the
|
|
29
29
|
// module-`require` paths above keep `packageDir` (bundler handles those).
|
|
30
30
|
const { getPackageRoot } = require('../package-root');
|
|
31
|
+
const { cleanupDeprecatedSyncFiles } = require('../deprecated-sync-cleanup');
|
|
31
32
|
|
|
32
33
|
// Load PluginManager for discoverable agent architecture
|
|
33
34
|
const PluginManager = require('../plugin-manager');
|
|
@@ -53,9 +54,8 @@ const { askYesNo: _askYesNoBase } = require('../ui-utils');
|
|
|
53
54
|
const contextMerge = require('../context-merge');
|
|
54
55
|
const projectDiscovery = require('../project-discovery');
|
|
55
56
|
|
|
56
|
-
// Load lib modules for symlink
|
|
57
|
+
// Load lib modules for symlink setup
|
|
57
58
|
const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require('../symlink-utils');
|
|
58
|
-
const { scaffoldBeadsSync } = require('../beads-sync-scaffold');
|
|
59
59
|
const { resolveSyncBackend } = require('../sync-backend');
|
|
60
60
|
const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
|
|
61
61
|
|
|
@@ -69,11 +69,13 @@ const { smartMergeAgentsMd } = require('../smart-merge');
|
|
|
69
69
|
const { checkLefthookStatus } = require('../lefthook-check');
|
|
70
70
|
const {
|
|
71
71
|
FORGE_USER_LEFTHOOK_YML,
|
|
72
|
+
FORGE_USER_LEFTHOOK_YML_NO_TDD,
|
|
72
73
|
forgeShouldWriteLefthookConfig,
|
|
73
74
|
installNativeGitHooks,
|
|
74
75
|
verifyHooksActive,
|
|
75
76
|
resolveGitHooksDir,
|
|
76
77
|
} = require('../lefthook-wiring');
|
|
78
|
+
const { detectExistingTddGate, describeExistingGateDeferral } = require('../existing-tdd-gate');
|
|
77
79
|
const { resolveShellRuntime } = require('../runtime-health');
|
|
78
80
|
const {
|
|
79
81
|
buildCodexSkillInstallPlan,
|
|
@@ -99,7 +101,6 @@ let FORCE_MODE = false;
|
|
|
99
101
|
let VERBOSE_MODE = false;
|
|
100
102
|
let NON_INTERACTIVE = false;
|
|
101
103
|
let SYMLINK_ONLY = false;
|
|
102
|
-
let SYNC_ENABLED = false;
|
|
103
104
|
let actionLog = new SetupActionLog();
|
|
104
105
|
let PKG_MANAGER = 'npm';
|
|
105
106
|
let SETUP_NOTES = [];
|
|
@@ -174,7 +175,6 @@ function detectPackageManager() {
|
|
|
174
175
|
|
|
175
176
|
|
|
176
177
|
const WORKFLOW_RUNTIME_ASSETS = Object.freeze([
|
|
177
|
-
'scripts/beads-context.sh',
|
|
178
178
|
'scripts/conflict-detect.sh',
|
|
179
179
|
'scripts/dep-guard-analyze.js',
|
|
180
180
|
'scripts/dep-guard.sh',
|
|
@@ -218,7 +218,6 @@ function validateAgents(agentList) {
|
|
|
218
218
|
// Prerequisite check function
|
|
219
219
|
function checkPrerequisites(options = {}) {
|
|
220
220
|
const requireGithubCli = options.requireGithubCli !== false;
|
|
221
|
-
const requireBeadsCli = options.requireBeadsCli === true;
|
|
222
221
|
const requireJq = options.requireJq === true;
|
|
223
222
|
const commandRunner = options.commandRunner || safeExec;
|
|
224
223
|
const errors = [];
|
|
@@ -255,15 +254,13 @@ function checkPrerequisites(options = {}) {
|
|
|
255
254
|
}
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
console.log(' ✓ Kernel issue store');
|
|
266
|
-
}
|
|
257
|
+
// Issue tracking runs on the local Forge Kernel store, which auto-provisions
|
|
258
|
+
// on first use — there is no external CLI to install. Surface only whether
|
|
259
|
+
// team sync is wired up yet.
|
|
260
|
+
if (resolveSyncBackend({ projectRoot, env: options.env }) === 'local-noop') {
|
|
261
|
+
console.log(' ✓ Kernel issue store (local-noop sync: single-machine until a sync server is configured)');
|
|
262
|
+
} else {
|
|
263
|
+
console.log(' ✓ Kernel issue store');
|
|
267
264
|
}
|
|
268
265
|
|
|
269
266
|
// Check Node.js version
|
|
@@ -800,9 +797,7 @@ async function detectProjectStatus() {
|
|
|
800
797
|
claudeMdLines: 0,
|
|
801
798
|
// Project tools status — the Kernel issue store is always present (it
|
|
802
799
|
// auto-provisions on first use), so issue tracking needs no install probe.
|
|
803
|
-
hasBeads: true,
|
|
804
800
|
hasSkills: isSkillsInitialized(),
|
|
805
|
-
beadsInstallType: 'kernel',
|
|
806
801
|
skillsInstallType: checkForSkills(),
|
|
807
802
|
// Enhanced: Auto-detected project context
|
|
808
803
|
autoDetected: null
|
|
@@ -2063,7 +2058,8 @@ function createAgentLinkFile(agent, symlinkOnly = false) {
|
|
|
2063
2058
|
|
|
2064
2059
|
const result = createSymlinkOrCopy('AGENTS.md', agent.linkFile, { symlinkOnly });
|
|
2065
2060
|
if (result) {
|
|
2066
|
-
|
|
2061
|
+
const action = result === 'linked' ? 'Linked' : result === 'copied' ? 'Copied' : 'Preserved import';
|
|
2062
|
+
console.log(` ${action}: ${agent.linkFile}`);
|
|
2067
2063
|
}
|
|
2068
2064
|
}
|
|
2069
2065
|
|
|
@@ -2071,7 +2067,7 @@ function createAgentLinkFile(agent, symlinkOnly = false) {
|
|
|
2071
2067
|
|
|
2072
2068
|
|
|
2073
2069
|
// Setup specific agent
|
|
2074
|
-
async function setupAgent(agentKey, skipFiles = {}) {
|
|
2070
|
+
async function setupAgent(agentKey, skipFiles = {}, options = {}) {
|
|
2075
2071
|
const agent = AGENTS[agentKey];
|
|
2076
2072
|
if (!agent) return;
|
|
2077
2073
|
|
|
@@ -2111,11 +2107,13 @@ async function setupAgent(agentKey, skipFiles = {}) {
|
|
|
2111
2107
|
// Projects Forge's TDD-gate + protected-path enforcement onto each harness's
|
|
2112
2108
|
// native hook surface. Codex hooks are GLOBAL-config scope and intentionally
|
|
2113
2109
|
// not written at project setup (see lib/hook-renderer.js).
|
|
2114
|
-
if (
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2110
|
+
if (!options.skillsOnly) {
|
|
2111
|
+
if (agentKey === 'claude') {
|
|
2112
|
+
setupClaudeHooksConfig();
|
|
2113
|
+
}
|
|
2114
|
+
if (agent.customSetup === 'cursor') {
|
|
2115
|
+
setupCursorHooksConfig();
|
|
2116
|
+
}
|
|
2119
2117
|
}
|
|
2120
2118
|
|
|
2121
2119
|
// Create link file (SYMLINK_ONLY = --symlink flag disables copy fallback)
|
|
@@ -2756,6 +2754,48 @@ function resolveHookEnforcementState(root = projectRoot) {
|
|
|
2756
2754
|
}
|
|
2757
2755
|
}
|
|
2758
2756
|
|
|
2757
|
+
// True when .forge/config.yaml already states a rail.tdd_intent choice EXPLICITLY (either
|
|
2758
|
+
// config shape the resolver accepts). Setup must never overwrite that: otherwise the
|
|
2759
|
+
// `forge gate enable rail.tdd_intent` we advertise would be undone by the next `forge setup`.
|
|
2760
|
+
function hasExplicitTddRailChoice(root) {
|
|
2761
|
+
try {
|
|
2762
|
+
const { loadRawConfig } = require('../config-writer');
|
|
2763
|
+
const config = loadRawConfig(root) || {};
|
|
2764
|
+
const viaGates = config.workflow && config.workflow.gates && config.workflow.gates['rail.tdd_intent'];
|
|
2765
|
+
const viaRails = config.rails && config.rails['rail.tdd_intent'];
|
|
2766
|
+
return Boolean(
|
|
2767
|
+
(viaGates && viaGates.enabled !== undefined) || (viaRails && viaRails.enabled !== undefined)
|
|
2768
|
+
);
|
|
2769
|
+
} catch {
|
|
2770
|
+
return false; // unreadable config → treat as unset; setConfigOverride reports any real failure
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
/**
|
|
2775
|
+
* Defer Forge's TDD gate to a pre-existing one: turn `rail.tdd_intent` off in
|
|
2776
|
+
* .forge/config.yaml so the installed hook scripts are genuinely inert, instead of stacking a
|
|
2777
|
+
* second gate on the same commit (kernel 5b425a85 / 2699b234). No-op when nothing was detected
|
|
2778
|
+
* (a repo with no gate keeps the default-ON rail) or when the user already chose explicitly.
|
|
2779
|
+
*
|
|
2780
|
+
* @returns {{ deferred: boolean, reason?: string }}
|
|
2781
|
+
*/
|
|
2782
|
+
function applyExistingGateDeferral(root, detection) {
|
|
2783
|
+
if (!detection || !detection.found) return { deferred: false, reason: 'no-existing-gate' };
|
|
2784
|
+
if (hasExplicitTddRailChoice(root)) return { deferred: false, reason: 'explicit-user-choice' };
|
|
2785
|
+
|
|
2786
|
+
const { setConfigOverride } = require('../config-writer');
|
|
2787
|
+
try {
|
|
2788
|
+
setConfigOverride(root, ['workflow', 'gates', 'rail.tdd_intent', 'enabled'], false);
|
|
2789
|
+
} catch (error) {
|
|
2790
|
+
// An unreadable/malformed .forge/config.yaml must not make `forge setup` fail outright
|
|
2791
|
+
// just because a pre-existing gate was detected. Report and DON'T defer: the caller keys
|
|
2792
|
+
// hook wiring off `deferred`, so falling back to the normal install keeps config and
|
|
2793
|
+
// wiring agreeing, instead of turning the gate off in neither place or in only one.
|
|
2794
|
+
return { deferred: false, reason: 'config-write-failed', error: error.message };
|
|
2795
|
+
}
|
|
2796
|
+
return { deferred: true };
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2759
2799
|
function describeHookEnforcement(root = projectRoot) {
|
|
2760
2800
|
const state = resolveHookEnforcementState(root);
|
|
2761
2801
|
if (!state.resolved) {
|
|
@@ -2837,6 +2877,32 @@ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin
|
|
|
2837
2877
|
// init deliberately degrades to a warning rather than failing, and repair runs inside
|
|
2838
2878
|
// another command's flow.
|
|
2839
2879
|
const loud = options.loud === true;
|
|
2880
|
+
|
|
2881
|
+
// BEFORE wiring anything: does this repo already enforce TDD/source-test coupling on
|
|
2882
|
+
// pre-commit? If so, defer to it rather than silently stacking a second gate on the same
|
|
2883
|
+
// commit — the in-the-wild beta.3 report (kernel 5b425a85 / 2699b234). The deferral is
|
|
2884
|
+
// VISIBLE (report below) and reversible with one command.
|
|
2885
|
+
const existingGate = detectExistingTddGate(projectRoot);
|
|
2886
|
+
const deferral = applyExistingGateDeferral(projectRoot, existingGate);
|
|
2887
|
+
// Only claim a deferral when one actually happened. describeExistingGateDeferral() states
|
|
2888
|
+
// that Forge did NOT install its gate and that rail.tdd_intent is off — both false when we
|
|
2889
|
+
// declined to defer, so printing it unconditionally would misreport the installed state.
|
|
2890
|
+
if (deferral.deferred) {
|
|
2891
|
+
console.warn(describeExistingGateDeferral(existingGate));
|
|
2892
|
+
addSetupNote(`Deferred Forge's TDD gate to the existing pre-commit gate in ${existingGate.source}.`);
|
|
2893
|
+
} else if (existingGate.found) {
|
|
2894
|
+
const why = deferral.reason === 'explicit-user-choice'
|
|
2895
|
+
? 'rail.tdd_intent is set explicitly in .forge/config.yaml, so your choice stands'
|
|
2896
|
+
: `Forge could not update .forge/config.yaml (${deferral.error})`;
|
|
2897
|
+
console.warn(
|
|
2898
|
+
` ⚠ Detected an existing pre-commit TDD/coupling gate in ${existingGate.source}, but did NOT defer to it:\n` +
|
|
2899
|
+
` ${why}.\n` +
|
|
2900
|
+
' Forge\'s own TDD gate is installed as usual, so BOTH gates run on the same commit.\n' +
|
|
2901
|
+
' Want only yours? Run: forge gate disable rail.tdd_intent'
|
|
2902
|
+
);
|
|
2903
|
+
addSetupNote(`Existing pre-commit gate in ${existingGate.source} detected; Forge's TDD gate installed anyway (${deferral.reason}).`);
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2840
2906
|
// Honest header: when the TDD gate is disabled in config the hooks install but are inert,
|
|
2841
2907
|
// so don't announce "(TDD enforcement)" for a state the user turned off (issue eda6d866).
|
|
2842
2908
|
console.log(
|
|
@@ -2876,7 +2942,18 @@ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin
|
|
|
2876
2942
|
// clobber a config that already has active jobs (kernel c713fce7).
|
|
2877
2943
|
const lefthookTarget = path.join(projectRoot, 'lefthook.yml');
|
|
2878
2944
|
if (forgeShouldWriteLefthookConfig(lefthookTarget)) {
|
|
2879
|
-
|
|
2945
|
+
// When we actually DEFERRED, write the variant WITHOUT Forge's forge-tdd job so the
|
|
2946
|
+
// commit is never blocked by two gates. Key this off `deferral.deferred`, not off
|
|
2947
|
+
// `existingGate.found`: applyExistingGateDeferral declines to defer when the user has
|
|
2948
|
+
// an explicit rail.tdd_intent choice, and suppressing the wiring anyway would leave
|
|
2949
|
+
// config saying "enabled" while the hook was never installed — making
|
|
2950
|
+
// `forge gate enable rail.tdd_intent` silently ineffective in exactly the repos this
|
|
2951
|
+
// feature targets.
|
|
2952
|
+
fs.writeFileSync(
|
|
2953
|
+
lefthookTarget,
|
|
2954
|
+
deferral.deferred ? FORGE_USER_LEFTHOOK_YML_NO_TDD : FORGE_USER_LEFTHOOK_YML,
|
|
2955
|
+
'utf8'
|
|
2956
|
+
);
|
|
2880
2957
|
actionLog.add('lefthook.yml', 'created');
|
|
2881
2958
|
console.log(' ✓ Created lefthook.yml');
|
|
2882
2959
|
}
|
|
@@ -2924,7 +3001,12 @@ function installGitHooks(options = {}) { // NOSONAR — Extracted as-is from bin
|
|
|
2924
3001
|
// inert (B3). This repair path only warns (never sets a failure exit code) since it can
|
|
2925
3002
|
// run mid-stage under enforce-stage's repairWorkflowRuntimeAssets().
|
|
2926
3003
|
if (!lefthookInstalled) {
|
|
2927
|
-
|
|
3004
|
+
// Skip the native pre-commit hook when the repo already has its own TDD/coupling gate —
|
|
3005
|
+
// pre-push is still wired, so only the double-gated surface is given up.
|
|
3006
|
+
const native = installNativeGitHooks(
|
|
3007
|
+
projectRoot,
|
|
3008
|
+
existingGate.found ? { skipHooks: ['pre-commit'] } : {}
|
|
3009
|
+
);
|
|
2928
3010
|
if (native.installed) {
|
|
2929
3011
|
console.log(` ✓ Native git hooks installed (${native.written.join(', ')}) - lefthook fallback`);
|
|
2930
3012
|
} else if (native.skipped && native.skipped.length > 0) {
|
|
@@ -3093,7 +3175,7 @@ function initializeSkills(installType) {
|
|
|
3093
3175
|
|
|
3094
3176
|
// Ensure the issue store during interactive setup. No prompt: the Forge Kernel
|
|
3095
3177
|
// ships with Forge and auto-provisions, so there is nothing to install or pick.
|
|
3096
|
-
async function
|
|
3178
|
+
async function promptIssueStoreSetup(_question) {
|
|
3097
3179
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
3098
3180
|
console.log('Issue Store (Forge Kernel)');
|
|
3099
3181
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
@@ -3219,17 +3301,14 @@ async function promptSkillsSetup(question) {
|
|
|
3219
3301
|
console.log('');
|
|
3220
3302
|
}
|
|
3221
3303
|
|
|
3222
|
-
// Interactive setup for
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
// Interactive setup for Beads and Skills
|
|
3304
|
+
// Interactive setup for the issue store and Skills
|
|
3226
3305
|
async function setupProjectTools(rl, question) {
|
|
3227
3306
|
console.log('');
|
|
3228
3307
|
console.log('═══════════════════════════════════════════════════════════');
|
|
3229
3308
|
console.log(' STEP 2: Project Tools (Recommended)');
|
|
3230
3309
|
console.log('═══════════════════════════════════════════════════════════');
|
|
3231
3310
|
console.log('');
|
|
3232
|
-
console.log('Forge recommends
|
|
3311
|
+
console.log('Forge recommends these tools for enhanced workflows:');
|
|
3233
3312
|
console.log('');
|
|
3234
3313
|
console.log('• Issue tracking (Forge Kernel) - zero-install, git-backed');
|
|
3235
3314
|
console.log(' Persists tasks across sessions, tracks dependencies.');
|
|
@@ -3241,7 +3320,7 @@ async function setupProjectTools(rl, question) {
|
|
|
3241
3320
|
console.log('');
|
|
3242
3321
|
|
|
3243
3322
|
// Use helper functions to reduce complexity
|
|
3244
|
-
await
|
|
3323
|
+
await promptIssueStoreSetup(question);
|
|
3245
3324
|
await promptSkillsSetup(question);
|
|
3246
3325
|
}
|
|
3247
3326
|
|
|
@@ -3395,7 +3474,8 @@ async function ensureGitHooksInstalled(targetRoot = projectRoot) {
|
|
|
3395
3474
|
|
|
3396
3475
|
|
|
3397
3476
|
// Quick setup with defaults
|
|
3398
|
-
async function quickSetup(selectedAgents, skipExternal) {
|
|
3477
|
+
async function quickSetup(selectedAgents, skipExternal, options = {}) {
|
|
3478
|
+
const skillsOnly = options.skillsOnly === true;
|
|
3399
3479
|
showBanner('Quick Setup');
|
|
3400
3480
|
console.log('');
|
|
3401
3481
|
console.log('Quick mode: Using defaults...');
|
|
@@ -3403,7 +3483,6 @@ async function quickSetup(selectedAgents, skipExternal) {
|
|
|
3403
3483
|
|
|
3404
3484
|
// Check prerequisites
|
|
3405
3485
|
checkPrerequisites({
|
|
3406
|
-
requireBeadsCli: true,
|
|
3407
3486
|
requireGithubCli: true,
|
|
3408
3487
|
requireJq: true,
|
|
3409
3488
|
});
|
|
@@ -3423,41 +3502,46 @@ async function quickSetup(selectedAgents, skipExternal) {
|
|
|
3423
3502
|
|
|
3424
3503
|
ensureWorkflowShellPolicy(selectedAgents);
|
|
3425
3504
|
|
|
3426
|
-
// Auto-install lefthook if missing
|
|
3427
|
-
|
|
3505
|
+
// Auto-install lefthook if missing unless the caller explicitly requested skills only.
|
|
3506
|
+
if (!skillsOnly) {
|
|
3507
|
+
autoInstallLefthook();
|
|
3508
|
+
}
|
|
3428
3509
|
|
|
3429
3510
|
// Auto-setup project tools (Kernel issue store, Skills)
|
|
3430
3511
|
await autoSetupToolsInQuickMode();
|
|
3431
3512
|
|
|
3432
3513
|
// Setup Claude first if selected, then setup remaining agents
|
|
3433
3514
|
if (selectedAgents.includes('claude')) {
|
|
3434
|
-
await setupAgent('claude');
|
|
3515
|
+
await setupAgent('claude', {}, { skillsOnly });
|
|
3435
3516
|
}
|
|
3436
|
-
await setupSelectedAgents(selectedAgents);
|
|
3517
|
+
await setupSelectedAgents(selectedAgents, undefined, { skillsOnly });
|
|
3437
3518
|
ensureWorkflowRuntimeAssets(selectedAgents);
|
|
3438
3519
|
|
|
3439
3520
|
// Detect Husky and migrate before installing Lefthook hooks
|
|
3440
|
-
|
|
3521
|
+
if (!skillsOnly) {
|
|
3522
|
+
await handleHuskyMigration();
|
|
3523
|
+
}
|
|
3441
3524
|
|
|
3442
3525
|
// Install git hooks for TDD enforcement. LOUD: this is the `forge setup` handler,
|
|
3443
3526
|
// so an inert-hooks result must fail non-zero (B3), not end green.
|
|
3444
3527
|
console.log('');
|
|
3445
|
-
|
|
3528
|
+
if (skillsOnly) {
|
|
3529
|
+
console.log('Skipping git and harness hooks (--skills-only).');
|
|
3530
|
+
} else {
|
|
3531
|
+
installGitHooks({ loud: true });
|
|
3532
|
+
}
|
|
3446
3533
|
|
|
3447
3534
|
// Configure external services with defaults (unless skipped)
|
|
3448
3535
|
configureDefaultExternalServices(skipExternal);
|
|
3449
3536
|
|
|
3450
|
-
|
|
3451
|
-
if (SYNC_ENABLED) {
|
|
3452
|
-
await handleSyncScaffold();
|
|
3453
|
-
}
|
|
3537
|
+
removeDeprecatedSyncFiles();
|
|
3454
3538
|
|
|
3455
3539
|
// Progressive setup summary
|
|
3456
3540
|
console.log('');
|
|
3457
3541
|
console.log(renderSetupSummary(actionLog, selectedAgents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
|
|
3458
3542
|
printSetupNotes();
|
|
3459
3543
|
// One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
|
|
3460
|
-
// Hooks +
|
|
3544
|
+
// Hooks + issue store already handled above, so init skips those side effects.
|
|
3461
3545
|
await finalizeWorkflowConfig({ hooksAlreadyInstalled: true });
|
|
3462
3546
|
console.log('');
|
|
3463
3547
|
}
|
|
@@ -3617,13 +3701,13 @@ async function promptForOverwriteDecisions(question, projectStatus, flags = {})
|
|
|
3617
3701
|
|
|
3618
3702
|
|
|
3619
3703
|
// Helper: Setup all selected agents - extracted to reduce cognitive complexity
|
|
3620
|
-
async function setupSelectedAgents(selectedAgents, skipFiles) {
|
|
3704
|
+
async function setupSelectedAgents(selectedAgents, skipFiles, options = {}) {
|
|
3621
3705
|
const totalAgents = selectedAgents.length;
|
|
3622
3706
|
for (const [index, agentKey] of selectedAgents.entries()) {
|
|
3623
3707
|
const agent = AGENTS[agentKey];
|
|
3624
3708
|
console.log(`\n[${index + 1}/${totalAgents}] Setting up ${agent.name}...`);
|
|
3625
3709
|
if (agentKey !== 'claude') { // Claude already done above
|
|
3626
|
-
await setupAgent(agentKey, skipFiles);
|
|
3710
|
+
await setupAgent(agentKey, skipFiles, options);
|
|
3627
3711
|
}
|
|
3628
3712
|
}
|
|
3629
3713
|
|
|
@@ -3691,7 +3775,6 @@ async function interactiveSetupWithFlags(flags) {
|
|
|
3691
3775
|
|
|
3692
3776
|
// Check agent-independent prerequisites first
|
|
3693
3777
|
checkPrerequisites({
|
|
3694
|
-
requireBeadsCli: true,
|
|
3695
3778
|
requireGithubCli: false,
|
|
3696
3779
|
requireJq: true,
|
|
3697
3780
|
});
|
|
@@ -3740,11 +3823,11 @@ async function interactiveSetupWithFlags(flags) {
|
|
|
3740
3823
|
|
|
3741
3824
|
// Setup Claude first if selected (delegated to helper), then remaining agents
|
|
3742
3825
|
if (selectedAgents.includes('claude')) {
|
|
3743
|
-
await setupAgent('claude', skipFiles);
|
|
3826
|
+
await setupAgent('claude', skipFiles, flags);
|
|
3744
3827
|
}
|
|
3745
3828
|
|
|
3746
3829
|
// Setup each selected agent with progress indication (delegated to helper)
|
|
3747
|
-
await setupSelectedAgents(selectedAgents, skipFiles);
|
|
3830
|
+
await setupSelectedAgents(selectedAgents, skipFiles, flags);
|
|
3748
3831
|
ensureWorkflowRuntimeAssets(selectedAgents);
|
|
3749
3832
|
|
|
3750
3833
|
// Handle external services step (delegated to helper)
|
|
@@ -3758,7 +3841,7 @@ async function interactiveSetupWithFlags(flags) {
|
|
|
3758
3841
|
|
|
3759
3842
|
// One-step onboarding: this path never writes .forge/config.yaml itself, so
|
|
3760
3843
|
// run `forge init` when the config is still absent (ac0b38c7).
|
|
3761
|
-
await finalizeWorkflowConfig();
|
|
3844
|
+
await finalizeWorkflowConfig({ hooksAlreadyInstalled: flags.skillsOnly === true });
|
|
3762
3845
|
}
|
|
3763
3846
|
|
|
3764
3847
|
// Main
|
|
@@ -3831,7 +3914,7 @@ function determineSelectedAgents(flags) {
|
|
|
3831
3914
|
// Shared setup executor — used by handleSetupCommand
|
|
3832
3915
|
|
|
3833
3916
|
// Dry-run setup — enumerate planned actions without writing files
|
|
3834
|
-
function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
|
|
3917
|
+
function dryRunSetup(agents, options = {}) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
|
|
3835
3918
|
const collector = new ActionCollector();
|
|
3836
3919
|
|
|
3837
3920
|
// Helper: add create or skip based on whether file exists
|
|
@@ -3901,10 +3984,12 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
|
|
|
3901
3984
|
}
|
|
3902
3985
|
}
|
|
3903
3986
|
|
|
3904
|
-
// Git hooks
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3987
|
+
// Git and harness hooks are opt-out for skills-only setup.
|
|
3988
|
+
if (options.skillsOnly !== true) {
|
|
3989
|
+
addFileAction('lefthook.yml', 'Git hook configuration');
|
|
3990
|
+
addFileAction('.forge/hooks/check-tdd.js', 'TDD enforcement hook');
|
|
3991
|
+
addFileAction('.forge/hooks/forge-native-hook.js', 'Native-hook enforcement adapter (Claude/Cursor)');
|
|
3992
|
+
}
|
|
3908
3993
|
|
|
3909
3994
|
// Print dry-run summary
|
|
3910
3995
|
console.log('');
|
|
@@ -3919,14 +4004,19 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
|
|
|
3919
4004
|
|
|
3920
4005
|
|
|
3921
4006
|
async function executeSetup(config) {
|
|
3922
|
-
const {
|
|
4007
|
+
const {
|
|
4008
|
+
agents,
|
|
4009
|
+
skipExternal,
|
|
4010
|
+
keepExisting = false,
|
|
4011
|
+
commandRunner,
|
|
4012
|
+
skillsOnly = false,
|
|
4013
|
+
} = config;
|
|
3923
4014
|
|
|
3924
4015
|
showBanner('Installing for specified agents...');
|
|
3925
4016
|
console.log('');
|
|
3926
4017
|
|
|
3927
4018
|
// Check prerequisites
|
|
3928
4019
|
checkPrerequisites({
|
|
3929
|
-
requireBeadsCli: true,
|
|
3930
4020
|
requireGithubCli: requiresGithubCliForSetup(agents),
|
|
3931
4021
|
requireJq: true,
|
|
3932
4022
|
commandRunner,
|
|
@@ -3953,60 +4043,62 @@ async function executeSetup(config) {
|
|
|
3953
4043
|
|
|
3954
4044
|
// Setup Claude first if selected, then remaining agents
|
|
3955
4045
|
if (agents.includes('claude')) {
|
|
3956
|
-
await setupAgent('claude', skipFiles);
|
|
4046
|
+
await setupAgent('claude', skipFiles, { skillsOnly });
|
|
3957
4047
|
}
|
|
3958
|
-
await setupSelectedAgents(agents, skipFiles);
|
|
4048
|
+
await setupSelectedAgents(agents, skipFiles, { skillsOnly });
|
|
3959
4049
|
ensureWorkflowRuntimeAssets(agents);
|
|
3960
4050
|
ensureWorkflowShellPolicy(agents);
|
|
3961
|
-
|
|
4051
|
+
if (!skillsOnly) {
|
|
4052
|
+
repairDeclaredLefthookDependency(agents);
|
|
4053
|
+
}
|
|
3962
4054
|
|
|
3963
4055
|
// Detect Husky and migrate before installing Lefthook hooks
|
|
3964
|
-
|
|
4056
|
+
if (!skillsOnly) {
|
|
4057
|
+
await handleHuskyMigration();
|
|
4058
|
+
}
|
|
3965
4059
|
|
|
3966
4060
|
// Install git hooks for TDD enforcement. LOUD: this is the `forge setup` handler,
|
|
3967
4061
|
// so an inert-hooks result must fail non-zero (B3), not end green.
|
|
3968
4062
|
console.log('');
|
|
3969
|
-
|
|
4063
|
+
if (skillsOnly) {
|
|
4064
|
+
console.log('Skipping git and harness hooks (--skills-only).');
|
|
4065
|
+
} else {
|
|
4066
|
+
installGitHooks({ loud: true });
|
|
4067
|
+
}
|
|
3970
4068
|
|
|
3971
4069
|
// External services (unless skipped)
|
|
3972
4070
|
await handleExternalServices(skipExternal, agents);
|
|
3973
4071
|
|
|
3974
|
-
|
|
3975
|
-
if (SYNC_ENABLED) {
|
|
3976
|
-
await handleSyncScaffold();
|
|
3977
|
-
}
|
|
4072
|
+
removeDeprecatedSyncFiles();
|
|
3978
4073
|
|
|
3979
4074
|
// Progressive setup summary
|
|
3980
4075
|
console.log('');
|
|
3981
4076
|
console.log(renderSetupSummary(actionLog, agents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
|
|
3982
4077
|
printSetupNotes();
|
|
3983
4078
|
// One-step onboarding: run `forge init` when config is still absent (ac0b38c7).
|
|
3984
|
-
// Hooks +
|
|
4079
|
+
// Hooks + issue store already handled above, so init skips those side effects.
|
|
3985
4080
|
await finalizeWorkflowConfig({ hooksAlreadyInstalled: true });
|
|
3986
4081
|
console.log('');
|
|
3987
4082
|
}
|
|
3988
4083
|
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
4084
|
+
/**
|
|
4085
|
+
* Remove generated Beads/GitHub sync files left behind by older installs.
|
|
4086
|
+
*
|
|
4087
|
+
* Runs on every setup — there is no longer a flag for it. Only files whose
|
|
4088
|
+
* content matches a known generated template are removed, so user-owned files
|
|
4089
|
+
* at the same paths are preserved.
|
|
4090
|
+
*/
|
|
4091
|
+
function removeDeprecatedSyncFiles() {
|
|
3996
4092
|
try {
|
|
3997
|
-
const result =
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
console.log(` Removed deprecated sync file: ${f}`);
|
|
4093
|
+
const result = cleanupDeprecatedSyncFiles(projectRoot, { packageDir: getPackageRoot(packageDir) });
|
|
4094
|
+
for (const file of result.removed || []) {
|
|
4095
|
+
console.log(` Removed deprecated sync file: ${file}`);
|
|
4001
4096
|
}
|
|
4002
4097
|
} catch (err) {
|
|
4003
|
-
console.error(` Error
|
|
4098
|
+
console.error(` Error removing deprecated sync files: ${err.message}`);
|
|
4004
4099
|
}
|
|
4005
4100
|
}
|
|
4006
4101
|
|
|
4007
|
-
// Helper: Handle setup command in non-quick mode
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
4102
|
// Helper: Handle setup command in non-quick mode
|
|
4011
4103
|
async function handleSetupCommand(selectedAgents, flags) {
|
|
4012
4104
|
if (!Array.isArray(selectedAgents) || selectedAgents.length === 0) {
|
|
@@ -4024,6 +4116,7 @@ async function handleSetupCommand(selectedAgents, flags) {
|
|
|
4024
4116
|
skipExternal: flags.skipExternal,
|
|
4025
4117
|
keepExisting: flags.keep,
|
|
4026
4118
|
commandRunner: flags.commandRunner,
|
|
4119
|
+
skillsOnly: flags.skillsOnly,
|
|
4027
4120
|
});
|
|
4028
4121
|
} finally {
|
|
4029
4122
|
projectRoot = savedRoot;
|
|
@@ -4168,6 +4261,7 @@ const SETUP_FLAG_DEFAULTS = Object.freeze({
|
|
|
4168
4261
|
sync: false,
|
|
4169
4262
|
symlink: false,
|
|
4170
4263
|
nonInteractive: false,
|
|
4264
|
+
skillsOnly: false,
|
|
4171
4265
|
});
|
|
4172
4266
|
|
|
4173
4267
|
const SIMPLE_SETUP_FLAG_UPDATES = Object.freeze({
|
|
@@ -4186,6 +4280,8 @@ const SIMPLE_SETUP_FLAG_UPDATES = Object.freeze({
|
|
|
4186
4280
|
'--symlink': { symlink: true },
|
|
4187
4281
|
'--yes': { yes: true, nonInteractive: true },
|
|
4188
4282
|
'-y': { yes: true, nonInteractive: true },
|
|
4283
|
+
'--skills-only': { skillsOnly: true },
|
|
4284
|
+
'--no-hooks': { skillsOnly: true },
|
|
4189
4285
|
});
|
|
4190
4286
|
|
|
4191
4287
|
function parseAgentFlag(argv, currentIndex, isFlagToken) {
|
|
@@ -4262,9 +4358,9 @@ function mergeSetupFlags(flags, argv) {
|
|
|
4262
4358
|
standard: Boolean(flags.standard || setupFlags.standard),
|
|
4263
4359
|
full: Boolean(flags.full || setupFlags.full),
|
|
4264
4360
|
skipExternal: Boolean(flags.skipExternal || setupFlags.skipExternal),
|
|
4265
|
-
sync: Boolean(flags.sync || setupFlags.sync),
|
|
4266
4361
|
symlink: Boolean(flags.symlink || setupFlags.symlink),
|
|
4267
4362
|
nonInteractive: Boolean(flags.nonInteractive || setupFlags.nonInteractive),
|
|
4363
|
+
skillsOnly: Boolean(flags.skillsOnly || setupFlags.skillsOnly),
|
|
4268
4364
|
};
|
|
4269
4365
|
}
|
|
4270
4366
|
|
|
@@ -4309,7 +4405,6 @@ module.exports = {
|
|
|
4309
4405
|
if (flags.verbose) VERBOSE_MODE = true;
|
|
4310
4406
|
if (flags.nonInteractive || flags.yes) NON_INTERACTIVE = true;
|
|
4311
4407
|
if (flags.symlink) SYMLINK_ONLY = true;
|
|
4312
|
-
if (flags.sync) SYNC_ENABLED = true;
|
|
4313
4408
|
actionLog = new SetupActionLog();
|
|
4314
4409
|
resetSetupNotes();
|
|
4315
4410
|
PKG_MANAGER = detectPackageManager();
|
|
@@ -4327,6 +4422,12 @@ module.exports = {
|
|
|
4327
4422
|
}
|
|
4328
4423
|
|
|
4329
4424
|
const [profile] = selectedProfiles;
|
|
4425
|
+
if (flags.skillsOnly) {
|
|
4426
|
+
return {
|
|
4427
|
+
success: false,
|
|
4428
|
+
error: `--skills-only cannot be combined with --${profile}.`,
|
|
4429
|
+
};
|
|
4430
|
+
}
|
|
4330
4431
|
return initCommand.handler([`--profile=${profile}`, '--yes', ...(flags.force ? ['--force'] : [])], flags, projectRoot);
|
|
4331
4432
|
}
|
|
4332
4433
|
|
|
@@ -4351,7 +4452,7 @@ module.exports = {
|
|
|
4351
4452
|
|
|
4352
4453
|
if (flags.dryRun) {
|
|
4353
4454
|
if (selectedAgents.length === 0) selectedAgents = ['claude'];
|
|
4354
|
-
dryRunSetup(selectedAgents);
|
|
4455
|
+
dryRunSetup(selectedAgents, { skillsOnly: flags.skillsOnly });
|
|
4355
4456
|
return { success: true };
|
|
4356
4457
|
}
|
|
4357
4458
|
|
|
@@ -4360,12 +4461,7 @@ module.exports = {
|
|
|
4360
4461
|
if (selectedAgents.length === 0 || (flags.yes && !flags.agents)) {
|
|
4361
4462
|
selectedAgents = Object.keys(AGENTS);
|
|
4362
4463
|
}
|
|
4363
|
-
await quickSetup(selectedAgents, flags.skipExternal);
|
|
4364
|
-
return { success: true };
|
|
4365
|
-
}
|
|
4366
|
-
|
|
4367
|
-
if (flags.sync && selectedAgents.length === 0) {
|
|
4368
|
-
await handleSyncScaffold();
|
|
4464
|
+
await quickSetup(selectedAgents, flags.skipExternal, { skillsOnly: flags.skillsOnly });
|
|
4369
4465
|
return { success: true };
|
|
4370
4466
|
}
|
|
4371
4467
|
|
|
@@ -4406,6 +4502,8 @@ module.exports = {
|
|
|
4406
4502
|
ensureGitHooksInstalled,
|
|
4407
4503
|
forgeShouldWriteLefthookConfig,
|
|
4408
4504
|
FORGE_USER_LEFTHOOK_YML,
|
|
4505
|
+
applyExistingGateDeferral,
|
|
4506
|
+
installGitHooks,
|
|
4409
4507
|
setupClaudeMcpConfig,
|
|
4410
4508
|
setupCursorMcpConfig,
|
|
4411
4509
|
setupClaudePermissions,
|
|
@@ -4430,14 +4528,13 @@ module.exports = {
|
|
|
4430
4528
|
backupAndRemoveLegacyCursorRules,
|
|
4431
4529
|
|
|
4432
4530
|
// State accessors for testing
|
|
4433
|
-
_getState: () => ({ projectRoot, FORCE_MODE, VERBOSE_MODE, NON_INTERACTIVE, SYMLINK_ONLY,
|
|
4531
|
+
_getState: () => ({ projectRoot, FORCE_MODE, VERBOSE_MODE, NON_INTERACTIVE, SYMLINK_ONLY, PKG_MANAGER }),
|
|
4434
4532
|
_setState: (state) => {
|
|
4435
4533
|
if (state.projectRoot !== undefined) projectRoot = state.projectRoot;
|
|
4436
4534
|
if (state.FORCE_MODE !== undefined) FORCE_MODE = state.FORCE_MODE;
|
|
4437
4535
|
if (state.VERBOSE_MODE !== undefined) VERBOSE_MODE = state.VERBOSE_MODE;
|
|
4438
4536
|
if (state.NON_INTERACTIVE !== undefined) NON_INTERACTIVE = state.NON_INTERACTIVE;
|
|
4439
4537
|
if (state.SYMLINK_ONLY !== undefined) SYMLINK_ONLY = state.SYMLINK_ONLY;
|
|
4440
|
-
if (state.SYNC_ENABLED !== undefined) SYNC_ENABLED = state.SYNC_ENABLED;
|
|
4441
4538
|
if (state.PKG_MANAGER !== undefined) PKG_MANAGER = state.PKG_MANAGER;
|
|
4442
4539
|
},
|
|
4443
4540
|
};
|
package/lib/commands/shepherd.js
CHANGED
|
@@ -39,6 +39,7 @@ const { watchLoop } = require('../pr-monitor/watch');
|
|
|
39
39
|
const { startPrWatcherDetached } = require('../pr-monitor/watch-lifecycle');
|
|
40
40
|
const reconcileExecutor = require('../pr-monitor/reconcile-executor');
|
|
41
41
|
const monitorJournal = require('../pr-monitor/journal');
|
|
42
|
+
const brokerMod = require('../kernel/broker');
|
|
42
43
|
const { EVENT_TYPES: T } = require('../pr-monitor/events');
|
|
43
44
|
const { autoShepherdRailEnabled } = require('./ship');
|
|
44
45
|
|
|
@@ -197,7 +198,18 @@ async function buildMonitorContext(pr, projectRoot, deps) {
|
|
|
197
198
|
if (!validation.valid) {
|
|
198
199
|
return { error: `Invalid pr-state adapter: ${validation.errors.join('; ')}` };
|
|
199
200
|
}
|
|
200
|
-
|
|
201
|
+
let gitCommonDir = deps.gitCommonDir;
|
|
202
|
+
if (!gitCommonDir) {
|
|
203
|
+
try {
|
|
204
|
+
const resolveGitCommonDir = deps.resolveGitCommonDir || brokerMod.resolveGitCommonDir;
|
|
205
|
+
gitCommonDir = resolveGitCommonDir(projectRoot || process.cwd(), { warn: () => {} });
|
|
206
|
+
} catch {
|
|
207
|
+
/* unavailable common-dir keeps the legacy per-root journal fallback */
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
dir = dir || monitorJournal.journalDir({
|
|
211
|
+
root: projectRoot || process.cwd(), gitCommonDir, repo: ctx.repo, pr: ctx.pr,
|
|
212
|
+
});
|
|
201
213
|
gather = gather || (() => gatherMonitorSnapshot({ ...ctx, adapter, self: deps.self }));
|
|
202
214
|
enrich = enrich || makeCheckFailureEnricher({
|
|
203
215
|
...ctx,
|