forge-workflow 0.0.8 → 0.0.10
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/.claude/commands/dev.md +2 -2
- package/.claude/commands/plan.md +2 -2
- package/.claude/commands/premerge.md +2 -2
- package/.claude/commands/review.md +5 -2
- package/.claude/commands/ship.md +6 -5
- package/.claude/commands/status.md +4 -4
- package/.claude/rules/greptile-review-process.md +4 -4
- package/.cline/workflows/dev.md +2 -2
- package/.cline/workflows/plan.md +2 -2
- package/.cline/workflows/premerge.md +2 -2
- package/.cline/workflows/review.md +5 -2
- package/.cline/workflows/ship.md +6 -5
- package/.cline/workflows/status.md +4 -4
- package/.codex/skills/dev/SKILL.md +2 -2
- package/.codex/skills/plan/SKILL.md +2 -2
- package/.codex/skills/premerge/SKILL.md +2 -2
- package/.codex/skills/review/SKILL.md +5 -2
- package/.codex/skills/ship/SKILL.md +6 -5
- package/.codex/skills/status/SKILL.md +4 -4
- package/.cursor/commands/dev.md +2 -2
- package/.cursor/commands/plan.md +2 -2
- package/.cursor/commands/premerge.md +2 -2
- package/.cursor/commands/review.md +5 -2
- package/.cursor/commands/ship.md +6 -5
- package/.cursor/commands/status.md +4 -4
- package/.github/prompts/dev.prompt.md +2 -2
- package/.github/prompts/plan.prompt.md +2 -2
- package/.github/prompts/premerge.prompt.md +2 -2
- package/.github/prompts/review.prompt.md +5 -2
- package/.github/prompts/ship.prompt.md +6 -5
- package/.github/prompts/status.prompt.md +4 -4
- package/.github/workflows/beads-to-github.yml +44 -11
- package/.github/workflows/github-to-beads.yml +11 -8
- package/.kilocode/workflows/dev.md +2 -2
- package/.kilocode/workflows/plan.md +2 -2
- package/.kilocode/workflows/premerge.md +2 -2
- package/.kilocode/workflows/review.md +5 -2
- package/.kilocode/workflows/ship.md +6 -5
- package/.kilocode/workflows/status.md +4 -4
- package/.opencode/commands/dev.md +2 -2
- package/.opencode/commands/plan.md +2 -2
- package/.opencode/commands/premerge.md +2 -2
- package/.opencode/commands/review.md +5 -2
- package/.opencode/commands/ship.md +6 -5
- package/.opencode/commands/status.md +4 -4
- package/.roo/commands/dev.md +2 -2
- package/.roo/commands/plan.md +2 -2
- package/.roo/commands/premerge.md +2 -2
- package/.roo/commands/review.md +5 -2
- package/.roo/commands/ship.md +6 -5
- package/.roo/commands/status.md +4 -4
- package/AGENTS.md +10 -9
- package/CLAUDE.md +12 -0
- package/README.md +12 -6
- package/bin/forge.js +24 -8
- package/docs/BEADS_GITHUB_SYNC.md +32 -2
- package/docs/EXAMPLES.md +22 -22
- package/docs/ROADMAP.md +3 -3
- package/docs/TOOLCHAIN.md +150 -160
- package/lib/agents/codex.plugin.json +3 -0
- package/lib/agents-config.js +18 -12
- package/lib/beads-bootstrap.js +225 -0
- package/lib/beads-health-check.js +55 -10
- package/lib/beads-setup.js +104 -28
- package/lib/beads-sync-scaffold.js +11 -6
- package/lib/codex-skills.js +54 -1
- package/lib/commands/_issue.js +11 -1
- package/lib/commands/issues.js +49 -0
- package/lib/commands/plan.js +5 -2
- package/lib/commands/recommend.js +22 -1
- package/lib/commands/setup.js +247 -27
- package/lib/commands/ship.js +188 -5
- package/lib/commands/status.js +187 -19
- package/lib/commands/team.js +11 -1
- package/lib/commands/test.js +125 -25
- package/lib/commands/validate.js +232 -9
- package/lib/commands/worktree.js +27 -54
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/detect-worktree.js +9 -10
- package/lib/forge-issues.js +326 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/lefthook-check.js +5 -2
- package/lib/project-memory.js +564 -0
- package/lib/runtime-health.js +100 -12
- package/lib/setup-action-log.js +2 -0
- package/lib/setup-summary-renderer.js +15 -11
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +177 -0
- package/lib/status/beads-snapshot.js +102 -0
- package/lib/status/presenter.js +65 -0
- package/lib/workflow/enforce-stage.js +14 -8
- package/lib/workflow/state-manager.js +349 -0
- package/package.json +12 -4
- package/scripts/beads-context.sh +124 -5
- package/scripts/beads-context.test.js +21 -4
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +263 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +45 -222
- package/scripts/forge-team/lib/hooks.sh +1 -1
- package/scripts/forge-team/lib/sync-github.sh +160 -28
- package/scripts/forge-team/lib/verify.sh +1 -1
- package/scripts/forge-team/lib/workload.sh +56 -27
- package/scripts/forge-team/tests/sync-github.test.sh +195 -58
- package/scripts/forge-team/tests/workload.test.sh +35 -4
- package/scripts/github-beads-sync/index.mjs +122 -98
- package/scripts/github-beads-sync/mapping.mjs +54 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +2 -2
- package/scripts/github-beads-sync/reverse-sync.mjs +31 -7
- package/scripts/github-beads-sync/run-bd.mjs +4 -2
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/preflight.sh +181 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +84 -330
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +39 -0
- package/scripts/test-ci-shard.js +244 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +335 -38
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { runIssueOperation } = require('../forge-issues');
|
|
4
|
+
|
|
5
|
+
const SUPPORTED_SUBCOMMANDS = ['create', 'list', 'show', 'close', 'update'];
|
|
6
|
+
|
|
7
|
+
function normalizeArgs(args = []) {
|
|
8
|
+
return args.filter(arg => arg !== '--');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function formatHelp() {
|
|
12
|
+
return [
|
|
13
|
+
'Usage: forge issues <subcommand> [...]',
|
|
14
|
+
'',
|
|
15
|
+
'Supported subcommands:',
|
|
16
|
+
' create Create an issue through Forge',
|
|
17
|
+
' list List issues through Forge',
|
|
18
|
+
' show Show a single issue through Forge',
|
|
19
|
+
' close Close an issue through Forge',
|
|
20
|
+
' update Update an issue through Forge',
|
|
21
|
+
].join('\n');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
name: 'issues',
|
|
26
|
+
description: 'Manage issues through the Forge authority layer',
|
|
27
|
+
usage: 'forge issues <subcommand> [...]',
|
|
28
|
+
flags: {},
|
|
29
|
+
handler: async (args, _flags, projectRoot, opts = {}) => {
|
|
30
|
+
const [subcommand, ...rest] = normalizeArgs(args);
|
|
31
|
+
|
|
32
|
+
if (!subcommand || subcommand === '--help' || subcommand === '-h') {
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
output: formatHelp(),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!SUPPORTED_SUBCOMMANDS.includes(subcommand)) {
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
error: `Unknown issue subcommand '${subcommand}'.\n\n${formatHelp()}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const runner = opts.runIssueOperation || runIssueOperation;
|
|
47
|
+
return runner(subcommand, rest, projectRoot, opts);
|
|
48
|
+
},
|
|
49
|
+
};
|
package/lib/commands/plan.js
CHANGED
|
@@ -254,8 +254,11 @@ function createBeadsIssue(featureName, researchPath, scope) {
|
|
|
254
254
|
getExecOptions()
|
|
255
255
|
);
|
|
256
256
|
|
|
257
|
-
// Extract issue ID from output (format: "Created issue: forge-xxx")
|
|
258
|
-
|
|
257
|
+
// Extract issue ID from output (format: "Created issue: forge-xxx" or "forge-xxx.N" for dotted sub-IDs).
|
|
258
|
+
// Character class is [a-z0-9] (not [a-zA-Z0-9]) because the /i flag makes A-Z redundant.
|
|
259
|
+
const createPattern = /Created issue:\s*(forge-[a-z0-9]+(?:\.[a-z0-9]+)*)/i;
|
|
260
|
+
const fallbackPattern = /(forge-[a-z0-9]+(?:\.[a-z0-9]+)*)/i;
|
|
261
|
+
const match = createPattern.exec(result) || fallbackPattern.exec(result);
|
|
259
262
|
|
|
260
263
|
if (!match) {
|
|
261
264
|
return {
|
|
@@ -116,4 +116,25 @@ function handleRecommend(flags, projectPath) {
|
|
|
116
116
|
return { budgetMode, recommendations };
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
async function handler(_args = [], flags = {}, projectRoot) {
|
|
120
|
+
const result = handleRecommend(flags, projectRoot);
|
|
121
|
+
if (result.error) {
|
|
122
|
+
return { success: false, error: result.error };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const output = [
|
|
126
|
+
'',
|
|
127
|
+
`🔧 Forge Tool Recommendations (Budget: ${result.budgetMode})`,
|
|
128
|
+
formatRecommendations(result.recommendations),
|
|
129
|
+
].join('\n');
|
|
130
|
+
|
|
131
|
+
return { success: true, output };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
name: 'recommend',
|
|
136
|
+
description: 'Show tool recommendations for the current project',
|
|
137
|
+
handler,
|
|
138
|
+
formatRecommendations,
|
|
139
|
+
handleRecommend
|
|
140
|
+
};
|
package/lib/commands/setup.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const fs = require('node:fs');
|
|
13
|
+
const os = require('node:os');
|
|
13
14
|
const path = require('node:path');
|
|
14
15
|
const readline = require('node:readline');
|
|
15
16
|
const { execSync, execFileSync } = require('node:child_process');
|
|
@@ -35,7 +36,7 @@ const { createSymlinkOrCopy: libCreateSymlinkOrCopy } = require(path.join(packag
|
|
|
35
36
|
const beadsSetupLib = require(path.join(packageDir, 'lib', 'beads-setup'));
|
|
36
37
|
const { beadsHealthCheck } = require(path.join(packageDir, 'lib', 'beads-health-check'));
|
|
37
38
|
const { setupPAT } = require(path.join(packageDir, 'lib', 'pat-setup'));
|
|
38
|
-
const {
|
|
39
|
+
const { DEFAULT_BEADS_VERSION, detectDefaultBranch, templateWorkflows, scaffoldBeadsSync } = require(path.join(packageDir, 'lib', 'beads-sync-scaffold'));
|
|
39
40
|
|
|
40
41
|
// Load incremental setup modules
|
|
41
42
|
const { detectEnvironment } = require('../detect-agent');
|
|
@@ -46,7 +47,11 @@ const { renderSetupSummary } = require('../setup-summary-renderer');
|
|
|
46
47
|
const { smartMergeAgentsMd } = require('../smart-merge');
|
|
47
48
|
const { checkLefthookStatus } = require('../lefthook-check');
|
|
48
49
|
const { resolveShellRuntime } = require('../runtime-health');
|
|
49
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
buildCodexSkillInstallPlan,
|
|
52
|
+
formatCodexSkillsInstallDir,
|
|
53
|
+
listCodexSkillEntries,
|
|
54
|
+
} = require('../codex-skills');
|
|
50
55
|
const {
|
|
51
56
|
generateCopilotConfig,
|
|
52
57
|
generateCursorConfig,
|
|
@@ -69,6 +74,8 @@ let SYMLINK_ONLY = false;
|
|
|
69
74
|
let SYNC_ENABLED = false;
|
|
70
75
|
let actionLog = new SetupActionLog();
|
|
71
76
|
let PKG_MANAGER = 'npm';
|
|
77
|
+
let SETUP_NOTES = [];
|
|
78
|
+
let CODEX_SETUP_REPORT = null;
|
|
72
79
|
|
|
73
80
|
/**
|
|
74
81
|
* Load agent definitions from plugin architecture
|
|
@@ -81,7 +88,7 @@ function loadAgentsFromPlugins() {
|
|
|
81
88
|
agents[id] = {
|
|
82
89
|
name: plugin.name,
|
|
83
90
|
description: plugin.description || '',
|
|
84
|
-
dirs:
|
|
91
|
+
dirs: getRepoRelativePluginDirectories(plugin),
|
|
85
92
|
hasCommands: plugin.capabilities?.commands || plugin.setup?.copyCommands || false,
|
|
86
93
|
hasSkill: plugin.capabilities?.skills || plugin.setup?.createSkill || false,
|
|
87
94
|
linkFile: plugin.files?.rootConfig || '',
|
|
@@ -95,6 +102,17 @@ function loadAgentsFromPlugins() {
|
|
|
95
102
|
return agents;
|
|
96
103
|
}
|
|
97
104
|
|
|
105
|
+
function isRepoRelativePluginPath(candidate) {
|
|
106
|
+
return typeof candidate === 'string'
|
|
107
|
+
&& candidate.length > 0
|
|
108
|
+
&& !path.isAbsolute(candidate)
|
|
109
|
+
&& !/^[~$%]/.test(candidate);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function getRepoRelativePluginDirectories(plugin) {
|
|
113
|
+
return Object.values(plugin.directories || {}).filter(isRepoRelativePluginPath);
|
|
114
|
+
}
|
|
115
|
+
|
|
98
116
|
const AGENTS = loadAgentsFromPlugins();
|
|
99
117
|
Object.freeze(AGENTS);
|
|
100
118
|
Object.values(AGENTS).forEach(agent => Object.freeze(agent));
|
|
@@ -187,11 +205,112 @@ function validateAgents(agentList) {
|
|
|
187
205
|
return valid;
|
|
188
206
|
}
|
|
189
207
|
|
|
208
|
+
function parseDoltRemoteNames(remoteListOutput) {
|
|
209
|
+
const output = String(remoteListOutput || '').trim();
|
|
210
|
+
if (!output || /^No remotes configured\.?$/i.test(output)) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return output
|
|
215
|
+
.split(/\r?\n/)
|
|
216
|
+
.map(line => line.trim())
|
|
217
|
+
.filter(Boolean)
|
|
218
|
+
.map(line => line.split(/\s+/)[0])
|
|
219
|
+
.filter(Boolean);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function readBeadsSyncRemoteConfig(projectDir = projectRoot) {
|
|
223
|
+
const jsonConfigPath = path.join(projectDir, '.beads', 'config.json');
|
|
224
|
+
if (fs.existsSync(jsonConfigPath)) {
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(fs.readFileSync(jsonConfigPath, 'utf8'));
|
|
227
|
+
if (typeof parsed.sync_remote === 'string' && parsed.sync_remote.trim()) {
|
|
228
|
+
return parsed.sync_remote.trim();
|
|
229
|
+
}
|
|
230
|
+
} catch (_error) {
|
|
231
|
+
// Ignore malformed config and fall back to other sources.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const yamlConfigPath = path.join(projectDir, '.beads', 'config.yaml');
|
|
236
|
+
if (fs.existsSync(yamlConfigPath)) {
|
|
237
|
+
const configuredRemote = parseBeadsYamlSyncRemote(fs.readFileSync(yamlConfigPath, 'utf8'));
|
|
238
|
+
if (configuredRemote) {
|
|
239
|
+
return configuredRemote;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return '';
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function parseBeadsYamlSyncRemote(content) {
|
|
247
|
+
for (const rawLine of String(content || '').split(/\r?\n/)) {
|
|
248
|
+
const trimmedLine = rawLine.trimStart();
|
|
249
|
+
if (!trimmedLine.startsWith('sync-remote:')) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const rawValue = trimmedLine.slice('sync-remote:'.length).trim();
|
|
254
|
+
if (!rawValue) {
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const quote = rawValue[0];
|
|
259
|
+
if ((quote === '"' || quote === '\'') && rawValue.length > 1) {
|
|
260
|
+
const closingQuoteIndex = rawValue.indexOf(quote, 1);
|
|
261
|
+
if (closingQuoteIndex > 1) {
|
|
262
|
+
return rawValue.slice(1, closingQuoteIndex).trim();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const commentIndex = rawValue.indexOf('#');
|
|
267
|
+
const unquotedValue = commentIndex === -1 ? rawValue : rawValue.slice(0, commentIndex);
|
|
268
|
+
return unquotedValue.trim();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return '';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function resolveExpectedBeadsRemote(options = {}) {
|
|
275
|
+
const projectDir = options.projectDir || projectRoot;
|
|
276
|
+
const env = options.env || process.env;
|
|
277
|
+
const configuredRemote = readBeadsSyncRemoteConfig(projectDir);
|
|
278
|
+
if (configuredRemote) {
|
|
279
|
+
return configuredRemote;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (typeof env.BD_SYNC_REMOTE === 'string' && env.BD_SYNC_REMOTE.trim()) {
|
|
283
|
+
return env.BD_SYNC_REMOTE.trim();
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const gitRemoteProbe = options.gitRemoteProbe
|
|
287
|
+
|| ((remoteName) => safeExec(`git -C "${projectDir}" remote get-url ${remoteName}`));
|
|
288
|
+
if (gitRemoteProbe('upstream')) {
|
|
289
|
+
return 'upstream';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return 'origin';
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function hasBeadsDoltRemote(commandRunner, remoteName = 'origin') {
|
|
296
|
+
const output = commandRunner('bd dolt remote list');
|
|
297
|
+
if (!output) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return parseDoltRemoteNames(output).includes(remoteName);
|
|
302
|
+
}
|
|
303
|
+
|
|
190
304
|
// Prerequisite check function
|
|
191
305
|
function checkPrerequisites(options = {}) {
|
|
192
306
|
const requireGithubCli = options.requireGithubCli !== false;
|
|
193
307
|
const requireBeadsCli = options.requireBeadsCli === true;
|
|
194
308
|
const requireJq = options.requireJq === true;
|
|
309
|
+
const expectedBeadsRemote = options.expectedBeadsRemote || resolveExpectedBeadsRemote({
|
|
310
|
+
env: options.env,
|
|
311
|
+
gitRemoteProbe: options.gitRemoteProbe,
|
|
312
|
+
projectDir: options.projectDir,
|
|
313
|
+
});
|
|
195
314
|
const commandRunner = options.commandRunner || safeExec;
|
|
196
315
|
const errors = [];
|
|
197
316
|
const warnings = [];
|
|
@@ -230,6 +349,20 @@ function checkPrerequisites(options = {}) {
|
|
|
230
349
|
const bdVersion = commandRunner('bd --version');
|
|
231
350
|
if (bdVersion) {
|
|
232
351
|
console.log(` ✓ ${bdVersion.split('\n')[0]}`);
|
|
352
|
+
if (requireBeadsCli) {
|
|
353
|
+
const hasRemote = hasBeadsDoltRemote(commandRunner, expectedBeadsRemote);
|
|
354
|
+
if (hasRemote === false) {
|
|
355
|
+
warnings.push(
|
|
356
|
+
`Beads Dolt remote '${expectedBeadsRemote}' is not configured. ` +
|
|
357
|
+
`Sync will remain local until you run: bd dolt remote add ${expectedBeadsRemote} <url>`
|
|
358
|
+
);
|
|
359
|
+
} else if (hasRemote === null) {
|
|
360
|
+
warnings.push(
|
|
361
|
+
`Unable to inspect Beads Dolt remotes. ` +
|
|
362
|
+
`Sync may remain local until '${expectedBeadsRemote}' is configured and bd dolt remote list succeeds.`
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
233
366
|
} else if (requireBeadsCli) {
|
|
234
367
|
errors.push('bd (Beads CLI) - Install from https://github.com/steveyegge/beads');
|
|
235
368
|
}
|
|
@@ -343,12 +476,60 @@ function writeFile(filePath, content) {
|
|
|
343
476
|
return fileUtils.writeFile(filePath, content, projectRoot);
|
|
344
477
|
}
|
|
345
478
|
|
|
479
|
+
function writeManagedAbsoluteFile(absolutePath, content, displayPath) {
|
|
480
|
+
try {
|
|
481
|
+
if (!FORCE_MODE && fileMatchesContent(absolutePath, content)) {
|
|
482
|
+
actionLog.add(displayPath, 'skipped', 'identical content');
|
|
483
|
+
return true;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const dir = path.dirname(absolutePath);
|
|
487
|
+
if (!fs.existsSync(dir)) {
|
|
488
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const existed = fs.existsSync(absolutePath);
|
|
492
|
+
fs.writeFileSync(absolutePath, content, { mode: 0o644 });
|
|
493
|
+
actionLog.add(displayPath, FORCE_MODE ? 'force-created' : (existed ? 'updated' : 'created'));
|
|
494
|
+
return true;
|
|
495
|
+
} catch (err) {
|
|
496
|
+
console.error(` × Failed to write ${displayPath}: ${err.message}`);
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
346
501
|
|
|
347
502
|
|
|
348
503
|
function readFile(filePath) {
|
|
349
504
|
return fileUtils.readFile(filePath);
|
|
350
505
|
}
|
|
351
506
|
|
|
507
|
+
function resetSetupNotes() {
|
|
508
|
+
SETUP_NOTES = [];
|
|
509
|
+
CODEX_SETUP_REPORT = null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function addSetupNote(message) {
|
|
513
|
+
if (!message || SETUP_NOTES.includes(message)) {
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
SETUP_NOTES.push(message);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function getSetupSummaryStatus() {
|
|
520
|
+
return SETUP_NOTES.length > 0 ? 'partial' : 'complete';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function printSetupNotes() {
|
|
524
|
+
if (SETUP_NOTES.length === 0) {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
for (const note of SETUP_NOTES) {
|
|
529
|
+
console.log(` Warning: ${note}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
352
533
|
|
|
353
534
|
|
|
354
535
|
function copyFile(src, dest) { // NOSONAR — Extracted as-is from bin/forge.js; complexity reduction deferred
|
|
@@ -1741,15 +1922,42 @@ function createAgentSkill(agent, agentKey) {
|
|
|
1741
1922
|
|
|
1742
1923
|
// Helper: Create Codex per-stage skills from canonical commands
|
|
1743
1924
|
function createCodexSkills() {
|
|
1744
|
-
const
|
|
1925
|
+
const installPlan = buildCodexSkillInstallPlan(packageDir, { env: process.env, homeDir: os.homedir() });
|
|
1926
|
+
const installRoot = formatCodexSkillsInstallDir({ env: process.env, homeDir: os.homedir() });
|
|
1927
|
+
|
|
1928
|
+
CODEX_SETUP_REPORT = {
|
|
1929
|
+
installRoot,
|
|
1930
|
+
skillCount: installPlan.length,
|
|
1931
|
+
status: 'complete',
|
|
1932
|
+
message: '',
|
|
1933
|
+
};
|
|
1745
1934
|
|
|
1746
|
-
|
|
1747
|
-
|
|
1935
|
+
if (installPlan.length === 0) {
|
|
1936
|
+
CODEX_SETUP_REPORT.status = 'partial';
|
|
1937
|
+
CODEX_SETUP_REPORT.message = 'Codex setup could not find the packaged stage skill templates.';
|
|
1938
|
+
addSetupNote(CODEX_SETUP_REPORT.message);
|
|
1939
|
+
console.log(' Warning: Codex stage skill templates were not found in the Forge package');
|
|
1940
|
+
return CODEX_SETUP_REPORT;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
let failed = 0;
|
|
1944
|
+
for (const entry of installPlan) {
|
|
1945
|
+
if (!writeManagedAbsoluteFile(entry.absolutePath, entry.content, entry.displayPath)) {
|
|
1946
|
+
failed += 1;
|
|
1947
|
+
}
|
|
1748
1948
|
}
|
|
1749
1949
|
|
|
1750
|
-
if (
|
|
1751
|
-
|
|
1950
|
+
if (failed > 0) {
|
|
1951
|
+
CODEX_SETUP_REPORT.status = 'partial';
|
|
1952
|
+
CODEX_SETUP_REPORT.message = `Codex repo instructions installed, but skills are not discoverable in this environment. Forge could not install ${failed}/${installPlan.length} Codex skills into ${installRoot}. Use \`bunx forge status\`, \`bunx forge plan\`, and the other Forge CLI stages until global Codex skills can be installed.`;
|
|
1953
|
+
addSetupNote(CODEX_SETUP_REPORT.message);
|
|
1954
|
+
console.log(` Warning: Codex skill install incomplete (${installPlan.length - failed}/${installPlan.length}) -> ${installRoot}`);
|
|
1955
|
+
return CODEX_SETUP_REPORT;
|
|
1752
1956
|
}
|
|
1957
|
+
|
|
1958
|
+
CODEX_SETUP_REPORT.message = `Codex setup complete — installed ${installPlan.length} stage skills to ${installRoot}`;
|
|
1959
|
+
console.log(` Installed: Codex stage skills (${installPlan.length}) -> ${installRoot}`);
|
|
1960
|
+
return CODEX_SETUP_REPORT;
|
|
1753
1961
|
}
|
|
1754
1962
|
|
|
1755
1963
|
// Helper: Setup MCP config for Claude
|
|
@@ -2210,9 +2418,10 @@ async function setupAgentsWithProgress(selectedAgents, claudeCommands, skipFiles
|
|
|
2210
2418
|
* Display final setup summary
|
|
2211
2419
|
*/
|
|
2212
2420
|
function displaySetupSummary(selectedAgents) {
|
|
2421
|
+
const partial = getSetupSummaryStatus() === 'partial';
|
|
2213
2422
|
console.log('');
|
|
2214
2423
|
console.log('==============================================');
|
|
2215
|
-
console.log(` Forge v${VERSION} Setup Complete!`);
|
|
2424
|
+
console.log(` Forge v${VERSION} Setup ${partial ? 'Partially Complete' : 'Complete'}!`);
|
|
2216
2425
|
console.log('==============================================');
|
|
2217
2426
|
console.log('');
|
|
2218
2427
|
console.log('What\'s installed:');
|
|
@@ -2230,8 +2439,9 @@ function displaySetupSummary(selectedAgents) {
|
|
|
2230
2439
|
console.log(` - ${agent.dirs[0]}/ (${workflowCount} workflow commands)`);
|
|
2231
2440
|
}
|
|
2232
2441
|
if (key === 'codex') {
|
|
2233
|
-
const skillCount = listCodexSkillEntries(packageDir).length;
|
|
2234
|
-
|
|
2442
|
+
const skillCount = CODEX_SETUP_REPORT?.skillCount ?? listCodexSkillEntries(packageDir).length;
|
|
2443
|
+
const codexRoot = CODEX_SETUP_REPORT?.installRoot || formatCodexSkillsInstallDir({ env: process.env, homeDir: os.homedir() });
|
|
2444
|
+
console.log(` - ${codexRoot}/<stage>/SKILL.md (${skillCount} stage skills)`);
|
|
2235
2445
|
} else if (agent.hasSkill) {
|
|
2236
2446
|
const skillDir = agent.dirs.find(d => d.includes('/skills/'));
|
|
2237
2447
|
if (skillDir) {
|
|
@@ -2263,10 +2473,11 @@ function displaySetupSummary(selectedAgents) {
|
|
|
2263
2473
|
console.log('');
|
|
2264
2474
|
console.log('Project Tools Status:');
|
|
2265
2475
|
console.log('');
|
|
2476
|
+
printSetupNotes();
|
|
2266
2477
|
|
|
2267
2478
|
// Beads status
|
|
2268
2479
|
if (isBeadsInitialized()) {
|
|
2269
|
-
console.log(' ✓ Beads initialized - Track work:
|
|
2480
|
+
console.log(' ✓ Beads initialized - Track work: forge ready');
|
|
2270
2481
|
} else if (checkForBeads()) {
|
|
2271
2482
|
console.log(' ! Beads available - Run: bd init');
|
|
2272
2483
|
} else {
|
|
@@ -2523,13 +2734,15 @@ function installGitHooks() { // NOSONAR — Extracted as-is from bin/forge.js; c
|
|
|
2523
2734
|
execFileSync('lefthook', ['install'], { stdio: 'inherit', cwd: projectRoot });
|
|
2524
2735
|
console.log(' ✓ Lefthook hooks installed (global)');
|
|
2525
2736
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
console.warn('Lefthook installation failed:', err.message);
|
|
2739
|
+
console.warn(' ⚠ Lefthook hooks were not installed; raw git push remains unsafe in this worktree.');
|
|
2740
|
+
console.log(' ℹ Lefthook not found. Install it:');
|
|
2741
|
+
console.log(' bun add -d lefthook (recommended)');
|
|
2742
|
+
console.log(' OR: bun add -g lefthook (global)');
|
|
2743
|
+
console.log(' Then run: bunx lefthook install');
|
|
2744
|
+
console.log(` Run ${PKG_MANAGER} install in this worktree, then rerun setup.`);
|
|
2745
|
+
}
|
|
2533
2746
|
|
|
2534
2747
|
console.log('');
|
|
2535
2748
|
|
|
@@ -2574,7 +2787,9 @@ function repairDeclaredLefthookDependency(selectedAgents) {
|
|
|
2574
2787
|
return { attempted: true, repaired: true };
|
|
2575
2788
|
} catch (err) {
|
|
2576
2789
|
console.warn('Lefthook install failed:', err.message);
|
|
2790
|
+
console.warn(' ⚠ Lefthook repair failed; raw git push remains unsafe in this worktree.');
|
|
2577
2791
|
console.log(` ⚠ ${status.message}`);
|
|
2792
|
+
console.log(` Run ${PKG_MANAGER} install in this worktree, then rerun setup.`);
|
|
2578
2793
|
console.log('');
|
|
2579
2794
|
return { attempted: true, repaired: false, error: err };
|
|
2580
2795
|
}
|
|
@@ -3018,7 +3233,7 @@ async function setupProjectTools(rl, question) {
|
|
|
3018
3233
|
console.log('');
|
|
3019
3234
|
console.log('• Beads - Git-backed issue tracking');
|
|
3020
3235
|
console.log(' Persists tasks across sessions, tracks dependencies.');
|
|
3021
|
-
console.log(' Command:
|
|
3236
|
+
console.log(' Command: forge ready, forge create, forge close');
|
|
3022
3237
|
console.log('');
|
|
3023
3238
|
console.log('• Skills - Universal SKILL.md management');
|
|
3024
3239
|
console.log(' Manage AI agent skills across all agents.');
|
|
@@ -3103,7 +3318,9 @@ function autoInstallLefthook() { // NOSONAR — Extracted as-is from bin/forge.j
|
|
|
3103
3318
|
console.log(' ✓ Lefthook binary restored');
|
|
3104
3319
|
} catch (err) {
|
|
3105
3320
|
console.warn('Lefthook install failed:', err.message);
|
|
3321
|
+
console.warn(' ⚠ Lefthook repair failed; raw git push remains unsafe in this worktree.');
|
|
3106
3322
|
console.log(` ⚠ ${status.message}`);
|
|
3323
|
+
console.log(` Run ${PKG_MANAGER} install in this worktree, then rerun setup.`);
|
|
3107
3324
|
}
|
|
3108
3325
|
console.log('');
|
|
3109
3326
|
return;
|
|
@@ -3250,7 +3467,8 @@ async function quickSetup(selectedAgents, skipExternal) {
|
|
|
3250
3467
|
|
|
3251
3468
|
// Progressive setup summary
|
|
3252
3469
|
console.log('');
|
|
3253
|
-
console.log(renderSetupSummary(actionLog, selectedAgents, VERBOSE_MODE));
|
|
3470
|
+
console.log(renderSetupSummary(actionLog, selectedAgents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
|
|
3471
|
+
printSetupNotes();
|
|
3254
3472
|
console.log('');
|
|
3255
3473
|
}
|
|
3256
3474
|
|
|
@@ -3752,9 +3970,9 @@ function dryRunSetup(agents) { // NOSONAR — Extracted as-is from bin/forge.js;
|
|
|
3752
3970
|
|
|
3753
3971
|
// Agent skill
|
|
3754
3972
|
if (agentKey === 'codex') {
|
|
3755
|
-
const skillEntries =
|
|
3973
|
+
const skillEntries = buildCodexSkillInstallPlan(packageDir, { env: process.env, homeDir: os.homedir() });
|
|
3756
3974
|
for (const entry of skillEntries) {
|
|
3757
|
-
addFileAction(
|
|
3975
|
+
addFileAction(entry.displayPath, 'Codex stage skill');
|
|
3758
3976
|
}
|
|
3759
3977
|
} else if (agent.hasSkill) {
|
|
3760
3978
|
const skillDir = agent.dirs.find(d => d.includes('/skills/'));
|
|
@@ -3853,7 +4071,8 @@ async function executeSetup(config) {
|
|
|
3853
4071
|
|
|
3854
4072
|
// Progressive setup summary
|
|
3855
4073
|
console.log('');
|
|
3856
|
-
console.log(renderSetupSummary(actionLog, agents, VERBOSE_MODE));
|
|
4074
|
+
console.log(renderSetupSummary(actionLog, agents, VERBOSE_MODE, { status: getSetupSummaryStatus() }));
|
|
4075
|
+
printSetupNotes();
|
|
3857
4076
|
console.log('');
|
|
3858
4077
|
}
|
|
3859
4078
|
|
|
@@ -3874,9 +4093,9 @@ async function handleSyncScaffold() {
|
|
|
3874
4093
|
console.log(` Skipped: ${f} (already exists)`);
|
|
3875
4094
|
}
|
|
3876
4095
|
|
|
3877
|
-
// Detect default branch and
|
|
4096
|
+
// Detect default branch and pin forge-managed workflows to the repo baseline Beads version.
|
|
3878
4097
|
const branch = detectDefaultBranch(projectRoot);
|
|
3879
|
-
const beadsVersion =
|
|
4098
|
+
const beadsVersion = DEFAULT_BEADS_VERSION;
|
|
3880
4099
|
const workflowDir = path.join(projectRoot, '.github', 'workflows');
|
|
3881
4100
|
templateWorkflows(workflowDir, branch, beadsVersion, result.filesCreated || []);
|
|
3882
4101
|
console.log(` Branch: ${branch}, Beads version: ${beadsVersion}`);
|
|
@@ -3976,7 +4195,7 @@ function detectConfiguredAgents(dir) {
|
|
|
3976
4195
|
};
|
|
3977
4196
|
|
|
3978
4197
|
pluginManager.getAllPlugins().forEach((plugin, id) => {
|
|
3979
|
-
const dirs =
|
|
4198
|
+
const dirs = getRepoRelativePluginDirectories(plugin);
|
|
3980
4199
|
const files = Object.values(plugin.files || {});
|
|
3981
4200
|
const markers = [...dirs, ...files].filter(Boolean);
|
|
3982
4201
|
const isConfigured = markers.some((marker) => fs.existsSync(path.join(dir, marker)));
|
|
@@ -4198,6 +4417,7 @@ module.exports = {
|
|
|
4198
4417
|
if (flags.symlink) SYMLINK_ONLY = true;
|
|
4199
4418
|
if (flags.sync) SYNC_ENABLED = true;
|
|
4200
4419
|
actionLog = new SetupActionLog();
|
|
4420
|
+
resetSetupNotes();
|
|
4201
4421
|
PKG_MANAGER = detectPackageManager();
|
|
4202
4422
|
|
|
4203
4423
|
// Determine agents to install
|