forge-workflow 0.0.4 → 0.0.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/.claude/commands/dev.md +340 -340
- package/.claude/commands/plan.md +521 -521
- package/.claude/commands/premerge.md +176 -176
- package/.claude/commands/research.md +42 -42
- package/.claude/commands/review.md +442 -442
- package/.claude/commands/rollback.md +721 -721
- package/.claude/commands/ship.md +164 -164
- package/.claude/commands/sonarcloud.md +152 -152
- package/.claude/commands/status.md +48 -48
- package/.claude/commands/validate.md +282 -282
- package/.claude/commands/verify.md +221 -221
- package/.claude/rules/greptile-review-process.md +285 -285
- package/.claude/rules/workflow.md +105 -105
- package/.claude/scripts/greptile-resolve.sh +526 -526
- package/.claude/scripts/load-env.sh +32 -32
- package/.cline/workflows/dev.md +337 -337
- package/.cline/workflows/plan.md +518 -518
- package/.cline/workflows/premerge.md +173 -173
- package/.cline/workflows/research.md +39 -39
- package/.cline/workflows/review.md +439 -439
- package/.cline/workflows/rollback.md +718 -718
- package/.cline/workflows/ship.md +161 -161
- package/.cline/workflows/sonarcloud.md +146 -146
- package/.cline/workflows/status.md +45 -45
- package/.cline/workflows/validate.md +279 -279
- package/.cline/workflows/verify.md +218 -218
- package/.codex/config.toml +11 -11
- package/.codex/skills/dev/SKILL.md +340 -340
- package/.codex/skills/plan/SKILL.md +521 -521
- package/.codex/skills/premerge/SKILL.md +176 -176
- package/.codex/skills/research/SKILL.md +42 -42
- package/.codex/skills/review/SKILL.md +442 -442
- package/.codex/skills/rollback/SKILL.md +721 -721
- package/.codex/skills/ship/SKILL.md +164 -164
- package/.codex/skills/sonarcloud/SKILL.md +149 -149
- package/.codex/skills/status/SKILL.md +48 -48
- package/.codex/skills/validate/SKILL.md +282 -282
- package/.codex/skills/verify/SKILL.md +221 -221
- package/.cursor/commands/dev.md +337 -337
- package/.cursor/commands/plan.md +518 -518
- package/.cursor/commands/premerge.md +173 -173
- package/.cursor/commands/research.md +39 -39
- package/.cursor/commands/review.md +439 -439
- package/.cursor/commands/rollback.md +718 -718
- package/.cursor/commands/ship.md +161 -161
- package/.cursor/commands/sonarcloud.md +146 -146
- package/.cursor/commands/status.md +45 -45
- package/.cursor/commands/validate.md +279 -279
- package/.cursor/commands/verify.md +218 -218
- package/.cursor/rules/permissions-guidance.mdc +37 -37
- package/.forge/hooks/check-tdd.js +240 -240
- package/.github/PLUGIN_TEMPLATE.json +32 -32
- package/.github/prompts/dev.prompt.md +342 -342
- package/.github/prompts/plan.prompt.md +523 -523
- package/.github/prompts/premerge.prompt.md +178 -178
- package/.github/prompts/research.prompt.md +44 -44
- package/.github/prompts/review.prompt.md +444 -444
- package/.github/prompts/rollback.prompt.md +723 -723
- package/.github/prompts/ship.prompt.md +166 -166
- package/.github/prompts/sonarcloud.prompt.md +151 -151
- package/.github/prompts/status.prompt.md +50 -50
- package/.github/prompts/validate.prompt.md +284 -284
- package/.github/prompts/verify.prompt.md +223 -223
- package/.github/workflows/beads-to-github.yml +56 -0
- package/.github/workflows/github-to-beads.yml +97 -0
- package/.kilocode/workflows/dev.md +341 -341
- package/.kilocode/workflows/plan.md +522 -522
- package/.kilocode/workflows/premerge.md +177 -177
- package/.kilocode/workflows/research.md +43 -43
- package/.kilocode/workflows/review.md +443 -443
- package/.kilocode/workflows/rollback.md +722 -722
- package/.kilocode/workflows/ship.md +165 -165
- package/.kilocode/workflows/sonarcloud.md +150 -150
- package/.kilocode/workflows/status.md +49 -49
- package/.kilocode/workflows/validate.md +283 -283
- package/.kilocode/workflows/verify.md +222 -222
- package/.mcp.json.example +12 -12
- package/.opencode/commands/dev.md +340 -340
- package/.opencode/commands/plan.md +521 -521
- package/.opencode/commands/premerge.md +176 -176
- package/.opencode/commands/research.md +42 -42
- package/.opencode/commands/review.md +442 -442
- package/.opencode/commands/rollback.md +721 -721
- package/.opencode/commands/ship.md +164 -164
- package/.opencode/commands/sonarcloud.md +149 -149
- package/.opencode/commands/status.md +48 -48
- package/.opencode/commands/validate.md +282 -282
- package/.opencode/commands/verify.md +221 -221
- package/.roo/commands/dev.md +341 -341
- package/.roo/commands/plan.md +522 -522
- package/.roo/commands/premerge.md +177 -177
- package/.roo/commands/research.md +43 -43
- package/.roo/commands/review.md +443 -443
- package/.roo/commands/rollback.md +722 -722
- package/.roo/commands/ship.md +165 -165
- package/.roo/commands/sonarcloud.md +150 -150
- package/.roo/commands/status.md +49 -49
- package/.roo/commands/validate.md +283 -283
- package/.roo/commands/verify.md +222 -222
- package/AGENTS.md +175 -175
- package/CLAUDE.md +100 -100
- package/README.md +429 -416
- package/bin/forge-cmd.js +313 -313
- package/bin/forge-preflight.js +309 -309
- package/bin/forge.js +4596 -4303
- package/docs/AGENT_INSTALL_PROMPT.md +342 -342
- package/docs/BEADS_GITHUB_SYNC.md +251 -251
- package/docs/ENHANCED_ONBOARDING.md +602 -602
- package/docs/EXAMPLES.md +482 -482
- package/docs/GREPTILE_SETUP.md +400 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +106 -106
- package/docs/ROADMAP.md +359 -359
- package/docs/SETUP.md +663 -631
- package/docs/TOOLCHAIN.md +630 -630
- package/docs/VALIDATION.md +363 -363
- package/install.sh +40 -1056
- package/lefthook.yml +39 -39
- package/lib/agents/README.md +198 -198
- package/lib/agents/claude.plugin.json +28 -28
- package/lib/agents/cline.plugin.json +22 -22
- package/lib/agents/codex.plugin.json +19 -19
- package/lib/agents/copilot.plugin.json +24 -24
- package/lib/agents/cursor.plugin.json +25 -25
- package/lib/agents/kilocode.plugin.json +22 -22
- package/lib/agents/opencode.plugin.json +20 -20
- package/lib/agents/roo.plugin.json +23 -23
- package/lib/agents-config.js +2112 -2112
- package/lib/beads-health-check.js +143 -0
- package/lib/beads-setup.js +341 -0
- package/lib/beads-sync-scaffold.js +260 -0
- package/lib/commands/dev.js +513 -513
- package/lib/commands/plan.js +692 -692
- package/lib/commands/recommend.js +119 -119
- package/lib/commands/ship.js +377 -377
- package/lib/commands/status.js +378 -378
- package/lib/commands/validate.js +602 -602
- package/lib/context-merge.js +359 -359
- package/lib/dep-guard/analyzer.js +294 -294
- package/lib/dep-guard/behavior-detector.js +98 -98
- package/lib/dep-guard/contract-detector.js +162 -162
- package/lib/dep-guard/import-detector.js +498 -498
- package/lib/dep-guard/path-utils.js +13 -13
- package/lib/dep-guard/rubric.js +120 -120
- package/lib/dep-guard/task-parser.js +318 -318
- package/lib/detect-agent.js +191 -191
- package/lib/detect-worktree.js +47 -47
- package/lib/file-hash.js +26 -26
- package/lib/husky-migration.js +450 -0
- package/lib/lefthook-check.js +65 -0
- package/lib/pat-setup.js +207 -0
- package/lib/plugin-catalog.js +350 -350
- package/lib/plugin-manager.js +166 -166
- package/lib/plugin-recommender.js +141 -141
- package/lib/project-discovery.js +491 -491
- package/lib/setup-action-log.js +139 -139
- package/lib/setup-summary-renderer.js +106 -106
- package/lib/setup-utils.js +96 -0
- package/lib/setup.js +192 -192
- package/lib/smart-merge.js +64 -0
- package/lib/symlink-utils.js +81 -0
- package/lib/workflow-profiles.js +197 -197
- package/package.json +131 -128
- package/scripts/beads-context.sh +291 -0
- package/scripts/beads-context.test.js +563 -0
- package/scripts/behavioral-judge.sh +378 -0
- package/scripts/benchmark.js +85 -0
- package/scripts/branch-protection.js +183 -0
- package/scripts/check-agents.js +172 -0
- package/scripts/commitlint.js +42 -0
- package/scripts/conflict-detect.sh +323 -0
- package/scripts/dep-guard-analyze.js +71 -0
- package/scripts/dep-guard.sh +811 -0
- package/scripts/eval_win.py +249 -0
- package/scripts/file-index.sh +399 -0
- package/scripts/github-beads-sync/comment.mjs +64 -0
- package/scripts/github-beads-sync/config.mjs +148 -0
- package/scripts/github-beads-sync/github-api.mjs +131 -0
- package/scripts/github-beads-sync/index.mjs +332 -0
- package/scripts/github-beads-sync/label-mapper.mjs +54 -0
- package/scripts/github-beads-sync/mapping.mjs +78 -0
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +31 -0
- package/scripts/github-beads-sync/reverse-sync.mjs +138 -0
- package/scripts/github-beads-sync/run-bd.mjs +159 -0
- package/scripts/github-beads-sync/sanitize.mjs +121 -0
- package/scripts/github-beads-sync.config.json +26 -0
- package/scripts/improve-command.js +375 -0
- package/scripts/lib/eval-runner.js +229 -0
- package/scripts/lib/eval-schema.js +135 -0
- package/scripts/lib/eval-storage.js +78 -0
- package/scripts/lib/grading.js +203 -0
- package/scripts/lib/transcript-parser.js +63 -0
- package/scripts/lint.js +47 -0
- package/scripts/migrate-to-bun-test.js +412 -0
- package/scripts/run-command-eval.js +236 -0
- package/scripts/smart-status.sh +782 -0
- package/scripts/sync-commands.js +571 -0
- package/scripts/sync-utils.sh +460 -0
- package/scripts/test-dashboard.js +123 -0
- package/scripts/test.js +44 -0
- package/scripts/validate.sh +94 -0
- package/skills/parallel-deep-research/SKILL.md +108 -108
- package/skills/parallel-deep-research/evals/README.md +27 -27
- package/skills/parallel-deep-research/evals/evals.json +62 -62
- package/skills/sonarcloud-analysis/SKILL.md +171 -171
- package/skills/sonarcloud-analysis/evals/README.md +27 -27
- package/skills/sonarcloud-analysis/evals/evals.json +50 -50
- package/skills/sonarcloud-analysis/references/api-reference.md +466 -466
- package/.cursor/hooks/state/continual-learning-index.json +0 -19
- package/.cursor/hooks/state/continual-learning.json +0 -8
package/lib/detect-agent.js
CHANGED
|
@@ -1,191 +1,191 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Auto-Detection Module
|
|
3
|
-
*
|
|
4
|
-
* 4-layer detection strategy:
|
|
5
|
-
* Layer 1: AI_AGENT env var (universal standard)
|
|
6
|
-
* Layer 2: Agent-specific env vars (high confidence)
|
|
7
|
-
* Layer 3: VSCode path parsing (medium confidence)
|
|
8
|
-
* Layer 4: Config file signatures (medium-low confidence)
|
|
9
|
-
*
|
|
10
|
-
* @module detect-agent
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const fs = require('node:fs');
|
|
14
|
-
const path = require('node:path');
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Layer 2 mapping: env var → agent name.
|
|
18
|
-
* Order matters — first match wins within this layer.
|
|
19
|
-
* Each entry: [envVarName, agentName]
|
|
20
|
-
* @type {Array<[string, string]>}
|
|
21
|
-
*/
|
|
22
|
-
const AGENT_ENV_VARS = [
|
|
23
|
-
// Forge-supported agents only (7 agents)
|
|
24
|
-
// Claude Code (with cowork sub-check handled separately)
|
|
25
|
-
['CLAUDECODE', 'claude'],
|
|
26
|
-
['CLAUDE_CODE', 'claude'],
|
|
27
|
-
// Cursor
|
|
28
|
-
['CURSOR_TRACE_ID', 'cursor'],
|
|
29
|
-
['CURSOR_AGENT', 'cursor'],
|
|
30
|
-
// Codex (OpenAI)
|
|
31
|
-
['CODEX_SANDBOX', 'codex'],
|
|
32
|
-
['CODEX_CI', 'codex'],
|
|
33
|
-
['CODEX_THREAD_ID', 'codex'],
|
|
34
|
-
// OpenCode
|
|
35
|
-
['OPENCODE_CLIENT', 'opencode'],
|
|
36
|
-
// GitHub Copilot
|
|
37
|
-
['COPILOT_MODEL', 'github-copilot'],
|
|
38
|
-
['COPILOT_ALLOW_ALL', 'github-copilot'],
|
|
39
|
-
['COPILOT_GITHUB_TOKEN', 'github-copilot'],
|
|
40
|
-
// Cline, Roo Code, Kilocode — no env vars (VSCode extensions, config-file-only)
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Layer 4 mapping: config file/dir paths → agent name.
|
|
45
|
-
* Paths are relative to project root.
|
|
46
|
-
* @type {Array<[string, string]>}
|
|
47
|
-
*/
|
|
48
|
-
const CONFIG_SIGNATURES = [
|
|
49
|
-
// Forge-supported agents only (7 agents)
|
|
50
|
-
[path.join('.claude', 'settings.json'), 'claude'],
|
|
51
|
-
['.cursorrules', 'cursor'],
|
|
52
|
-
[path.join('.cursor', 'rules'), 'cursor'],
|
|
53
|
-
['.clinerules', 'cline'],
|
|
54
|
-
['.cline', 'cline'],
|
|
55
|
-
[path.join('.roo', 'rules'), 'roo-code'],
|
|
56
|
-
['.roo', 'roo-code'],
|
|
57
|
-
['.kilocode', 'kilocode'],
|
|
58
|
-
['codex.md', 'codex'],
|
|
59
|
-
['.codex', 'codex'],
|
|
60
|
-
[path.join('.opencode', 'commands'), 'opencode'],
|
|
61
|
-
['.opencode', 'opencode'],
|
|
62
|
-
[path.join('.github', 'copilot-instructions.md'), 'github-copilot'],
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Detect the actively running AI agent from environment signals.
|
|
67
|
-
*
|
|
68
|
-
* Covers layers 1-3 of the 4-layer detection strategy:
|
|
69
|
-
* 1. AI_AGENT env var (universal, highest priority)
|
|
70
|
-
* 2. Agent-specific env vars (high confidence)
|
|
71
|
-
* 3. VSCode path parsing (medium confidence)
|
|
72
|
-
* Layer 4 (config file signatures) is in detectConfiguredAgents().
|
|
73
|
-
*
|
|
74
|
-
* @param {Record<string, string>} [env=process.env] - Environment variables
|
|
75
|
-
* @returns {{ name: string, source: 'env'|'path', confidence: 'high'|'medium' } | null}
|
|
76
|
-
*/
|
|
77
|
-
function detectActiveAgent(env = process.env) {
|
|
78
|
-
// Layer 1: AI_AGENT universal env var
|
|
79
|
-
if (env.AI_AGENT) {
|
|
80
|
-
return { name: env.AI_AGENT, source: 'env', confidence: 'high' };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Layer 2: Agent-specific env vars
|
|
84
|
-
// Special sub-check: Claude Code cowork mode
|
|
85
|
-
if ((env.CLAUDECODE || env.CLAUDE_CODE) && env.CLAUDE_CODE_IS_COWORK) {
|
|
86
|
-
return { name: 'cowork', source: 'env', confidence: 'high' };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
for (const [varName, agentName] of AGENT_ENV_VARS) {
|
|
90
|
-
if (env[varName]) {
|
|
91
|
-
return { name: agentName, source: 'env', confidence: 'high' };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Layer 3: VSCode path parsing
|
|
96
|
-
const pathResult = _detectFromVSCodePaths(env);
|
|
97
|
-
if (pathResult && pathResult.agent) {
|
|
98
|
-
return { name: pathResult.agent, source: 'path', confidence: 'medium' };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Parse VSCode-related env vars for editor/agent identification.
|
|
106
|
-
*
|
|
107
|
-
* @param {Record<string, string>} env - Environment variables
|
|
108
|
-
* @returns {{ agent: string|null, editor: string|null } | null}
|
|
109
|
-
* @private
|
|
110
|
-
*/
|
|
111
|
-
function _detectFromVSCodePaths(env) {
|
|
112
|
-
const pathsToCheck = [
|
|
113
|
-
env.VSCODE_CODE_CACHE_PATH,
|
|
114
|
-
env.VSCODE_NLS_CONFIG,
|
|
115
|
-
].filter(Boolean);
|
|
116
|
-
|
|
117
|
-
if (pathsToCheck.length === 0) return null;
|
|
118
|
-
|
|
119
|
-
const combined = pathsToCheck.join(' ').toLowerCase();
|
|
120
|
-
|
|
121
|
-
// Only detect Forge-supported agents via VSCode paths
|
|
122
|
-
if (combined.includes('cursor')) {
|
|
123
|
-
return { agent: 'cursor', editor: 'cursor' };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Generic VSCode (or unsupported VSCode forks) — not a specific agent
|
|
127
|
-
if (combined.includes('code')) {
|
|
128
|
-
return { agent: null, editor: 'vscode' };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Detect all agents with config files present in the project root (Layer 4).
|
|
136
|
-
*
|
|
137
|
-
* @param {string} projectRoot - Absolute path to project root
|
|
138
|
-
* @returns {string[]} Array of unique agent names detected from config files
|
|
139
|
-
*/
|
|
140
|
-
function detectConfiguredAgents(projectRoot) {
|
|
141
|
-
const detected = new Set();
|
|
142
|
-
|
|
143
|
-
for (const [relativePath, agentName] of CONFIG_SIGNATURES) {
|
|
144
|
-
const fullPath = path.join(projectRoot, relativePath);
|
|
145
|
-
try {
|
|
146
|
-
if (fs.existsSync(fullPath)) {
|
|
147
|
-
detected.add(agentName);
|
|
148
|
-
}
|
|
149
|
-
} catch (_err) {
|
|
150
|
-
// Permission error or other fs issue — skip silently
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return [...detected];
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Full environment detection combining all 4 layers.
|
|
159
|
-
*
|
|
160
|
-
* @param {string} projectRoot - Absolute path to project root
|
|
161
|
-
* @param {Record<string, string>} [env=process.env] - Environment variables
|
|
162
|
-
* @returns {{
|
|
163
|
-
* activeAgent: string|null,
|
|
164
|
-
* activeAgentSource: 'env'|'path'|null,
|
|
165
|
-
* confidence: 'high'|'medium'|null,
|
|
166
|
-
* configuredAgents: string[],
|
|
167
|
-
* editor: string|null
|
|
168
|
-
* }}
|
|
169
|
-
*/
|
|
170
|
-
function detectEnvironment(projectRoot, env = process.env) {
|
|
171
|
-
const active = detectActiveAgent(env);
|
|
172
|
-
const configuredAgents = detectConfiguredAgents(projectRoot);
|
|
173
|
-
|
|
174
|
-
// Get editor from VSCode paths (_detectFromVSCodePaths is cheap and deterministic)
|
|
175
|
-
const vscodePaths = _detectFromVSCodePaths(env);
|
|
176
|
-
const editor = vscodePaths ? vscodePaths.editor : null;
|
|
177
|
-
|
|
178
|
-
return {
|
|
179
|
-
activeAgent: active ? active.name : null,
|
|
180
|
-
activeAgentSource: active ? active.source : null,
|
|
181
|
-
confidence: active ? active.confidence : null,
|
|
182
|
-
configuredAgents,
|
|
183
|
-
editor,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
module.exports = {
|
|
188
|
-
detectActiveAgent,
|
|
189
|
-
detectConfiguredAgents,
|
|
190
|
-
detectEnvironment,
|
|
191
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Agent Auto-Detection Module
|
|
3
|
+
*
|
|
4
|
+
* 4-layer detection strategy:
|
|
5
|
+
* Layer 1: AI_AGENT env var (universal standard)
|
|
6
|
+
* Layer 2: Agent-specific env vars (high confidence)
|
|
7
|
+
* Layer 3: VSCode path parsing (medium confidence)
|
|
8
|
+
* Layer 4: Config file signatures (medium-low confidence)
|
|
9
|
+
*
|
|
10
|
+
* @module detect-agent
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const path = require('node:path');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Layer 2 mapping: env var → agent name.
|
|
18
|
+
* Order matters — first match wins within this layer.
|
|
19
|
+
* Each entry: [envVarName, agentName]
|
|
20
|
+
* @type {Array<[string, string]>}
|
|
21
|
+
*/
|
|
22
|
+
const AGENT_ENV_VARS = [
|
|
23
|
+
// Forge-supported agents only (7 agents)
|
|
24
|
+
// Claude Code (with cowork sub-check handled separately)
|
|
25
|
+
['CLAUDECODE', 'claude'],
|
|
26
|
+
['CLAUDE_CODE', 'claude'],
|
|
27
|
+
// Cursor
|
|
28
|
+
['CURSOR_TRACE_ID', 'cursor'],
|
|
29
|
+
['CURSOR_AGENT', 'cursor'],
|
|
30
|
+
// Codex (OpenAI)
|
|
31
|
+
['CODEX_SANDBOX', 'codex'],
|
|
32
|
+
['CODEX_CI', 'codex'],
|
|
33
|
+
['CODEX_THREAD_ID', 'codex'],
|
|
34
|
+
// OpenCode
|
|
35
|
+
['OPENCODE_CLIENT', 'opencode'],
|
|
36
|
+
// GitHub Copilot
|
|
37
|
+
['COPILOT_MODEL', 'github-copilot'],
|
|
38
|
+
['COPILOT_ALLOW_ALL', 'github-copilot'],
|
|
39
|
+
['COPILOT_GITHUB_TOKEN', 'github-copilot'],
|
|
40
|
+
// Cline, Roo Code, Kilocode — no env vars (VSCode extensions, config-file-only)
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Layer 4 mapping: config file/dir paths → agent name.
|
|
45
|
+
* Paths are relative to project root.
|
|
46
|
+
* @type {Array<[string, string]>}
|
|
47
|
+
*/
|
|
48
|
+
const CONFIG_SIGNATURES = [
|
|
49
|
+
// Forge-supported agents only (7 agents)
|
|
50
|
+
[path.join('.claude', 'settings.json'), 'claude'],
|
|
51
|
+
['.cursorrules', 'cursor'],
|
|
52
|
+
[path.join('.cursor', 'rules'), 'cursor'],
|
|
53
|
+
['.clinerules', 'cline'],
|
|
54
|
+
['.cline', 'cline'],
|
|
55
|
+
[path.join('.roo', 'rules'), 'roo-code'],
|
|
56
|
+
['.roo', 'roo-code'],
|
|
57
|
+
['.kilocode', 'kilocode'],
|
|
58
|
+
['codex.md', 'codex'],
|
|
59
|
+
['.codex', 'codex'],
|
|
60
|
+
[path.join('.opencode', 'commands'), 'opencode'],
|
|
61
|
+
['.opencode', 'opencode'],
|
|
62
|
+
[path.join('.github', 'copilot-instructions.md'), 'github-copilot'],
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Detect the actively running AI agent from environment signals.
|
|
67
|
+
*
|
|
68
|
+
* Covers layers 1-3 of the 4-layer detection strategy:
|
|
69
|
+
* 1. AI_AGENT env var (universal, highest priority)
|
|
70
|
+
* 2. Agent-specific env vars (high confidence)
|
|
71
|
+
* 3. VSCode path parsing (medium confidence)
|
|
72
|
+
* Layer 4 (config file signatures) is in detectConfiguredAgents().
|
|
73
|
+
*
|
|
74
|
+
* @param {Record<string, string>} [env=process.env] - Environment variables
|
|
75
|
+
* @returns {{ name: string, source: 'env'|'path', confidence: 'high'|'medium' } | null}
|
|
76
|
+
*/
|
|
77
|
+
function detectActiveAgent(env = process.env) {
|
|
78
|
+
// Layer 1: AI_AGENT universal env var
|
|
79
|
+
if (env.AI_AGENT) {
|
|
80
|
+
return { name: env.AI_AGENT, source: 'env', confidence: 'high' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Layer 2: Agent-specific env vars
|
|
84
|
+
// Special sub-check: Claude Code cowork mode
|
|
85
|
+
if ((env.CLAUDECODE || env.CLAUDE_CODE) && env.CLAUDE_CODE_IS_COWORK) {
|
|
86
|
+
return { name: 'cowork', source: 'env', confidence: 'high' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const [varName, agentName] of AGENT_ENV_VARS) {
|
|
90
|
+
if (env[varName]) {
|
|
91
|
+
return { name: agentName, source: 'env', confidence: 'high' };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Layer 3: VSCode path parsing
|
|
96
|
+
const pathResult = _detectFromVSCodePaths(env);
|
|
97
|
+
if (pathResult && pathResult.agent) {
|
|
98
|
+
return { name: pathResult.agent, source: 'path', confidence: 'medium' };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse VSCode-related env vars for editor/agent identification.
|
|
106
|
+
*
|
|
107
|
+
* @param {Record<string, string>} env - Environment variables
|
|
108
|
+
* @returns {{ agent: string|null, editor: string|null } | null}
|
|
109
|
+
* @private
|
|
110
|
+
*/
|
|
111
|
+
function _detectFromVSCodePaths(env) {
|
|
112
|
+
const pathsToCheck = [
|
|
113
|
+
env.VSCODE_CODE_CACHE_PATH,
|
|
114
|
+
env.VSCODE_NLS_CONFIG,
|
|
115
|
+
].filter(Boolean);
|
|
116
|
+
|
|
117
|
+
if (pathsToCheck.length === 0) return null;
|
|
118
|
+
|
|
119
|
+
const combined = pathsToCheck.join(' ').toLowerCase();
|
|
120
|
+
|
|
121
|
+
// Only detect Forge-supported agents via VSCode paths
|
|
122
|
+
if (combined.includes('cursor')) {
|
|
123
|
+
return { agent: 'cursor', editor: 'cursor' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Generic VSCode (or unsupported VSCode forks) — not a specific agent
|
|
127
|
+
if (combined.includes('code')) {
|
|
128
|
+
return { agent: null, editor: 'vscode' };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Detect all agents with config files present in the project root (Layer 4).
|
|
136
|
+
*
|
|
137
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
138
|
+
* @returns {string[]} Array of unique agent names detected from config files
|
|
139
|
+
*/
|
|
140
|
+
function detectConfiguredAgents(projectRoot) {
|
|
141
|
+
const detected = new Set();
|
|
142
|
+
|
|
143
|
+
for (const [relativePath, agentName] of CONFIG_SIGNATURES) {
|
|
144
|
+
const fullPath = path.join(projectRoot, relativePath);
|
|
145
|
+
try {
|
|
146
|
+
if (fs.existsSync(fullPath)) {
|
|
147
|
+
detected.add(agentName);
|
|
148
|
+
}
|
|
149
|
+
} catch (_err) {
|
|
150
|
+
// Permission error or other fs issue — skip silently
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return [...detected];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Full environment detection combining all 4 layers.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} projectRoot - Absolute path to project root
|
|
161
|
+
* @param {Record<string, string>} [env=process.env] - Environment variables
|
|
162
|
+
* @returns {{
|
|
163
|
+
* activeAgent: string|null,
|
|
164
|
+
* activeAgentSource: 'env'|'path'|null,
|
|
165
|
+
* confidence: 'high'|'medium'|null,
|
|
166
|
+
* configuredAgents: string[],
|
|
167
|
+
* editor: string|null
|
|
168
|
+
* }}
|
|
169
|
+
*/
|
|
170
|
+
function detectEnvironment(projectRoot, env = process.env) {
|
|
171
|
+
const active = detectActiveAgent(env);
|
|
172
|
+
const configuredAgents = detectConfiguredAgents(projectRoot);
|
|
173
|
+
|
|
174
|
+
// Get editor from VSCode paths (_detectFromVSCodePaths is cheap and deterministic)
|
|
175
|
+
const vscodePaths = _detectFromVSCodePaths(env);
|
|
176
|
+
const editor = vscodePaths ? vscodePaths.editor : null;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
activeAgent: active ? active.name : null,
|
|
180
|
+
activeAgentSource: active ? active.source : null,
|
|
181
|
+
confidence: active ? active.confidence : null,
|
|
182
|
+
configuredAgents,
|
|
183
|
+
editor,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
module.exports = {
|
|
188
|
+
detectActiveAgent,
|
|
189
|
+
detectConfiguredAgents,
|
|
190
|
+
detectEnvironment,
|
|
191
|
+
};
|
package/lib/detect-worktree.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { execFileSync } = require('child_process');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Detect if the current directory is inside a git worktree.
|
|
8
|
-
* Uses git rev-parse --git-dir vs --git-common-dir — they differ in worktrees.
|
|
9
|
-
*
|
|
10
|
-
* @param {string} [cwd=process.cwd()] - Directory to check
|
|
11
|
-
* @returns {{ inWorktree: boolean, branch?: string, mainWorktree?: string }}
|
|
12
|
-
*/
|
|
13
|
-
function detectWorktree(cwd = process.cwd()) {
|
|
14
|
-
try {
|
|
15
|
-
const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], {
|
|
16
|
-
encoding: 'utf8', cwd, stdio: ['pipe', 'pipe', 'pipe']
|
|
17
|
-
}).trim();
|
|
18
|
-
|
|
19
|
-
const gitCommonDir = execFileSync('git', ['rev-parse', '--git-common-dir'], {
|
|
20
|
-
encoding: 'utf8', cwd, stdio: ['pipe', 'pipe', 'pipe']
|
|
21
|
-
}).trim();
|
|
22
|
-
|
|
23
|
-
// Resolve to absolute paths for reliable comparison
|
|
24
|
-
const absGitDir = path.resolve(cwd, gitDir);
|
|
25
|
-
const absCommonDir = path.resolve(cwd, gitCommonDir);
|
|
26
|
-
|
|
27
|
-
// In a worktree, git-dir is like .git/worktrees/<name>
|
|
28
|
-
// while git-common-dir is the main .git directory
|
|
29
|
-
if (absGitDir !== absCommonDir) {
|
|
30
|
-
const branch = execFileSync('git', ['branch', '--show-current'], {
|
|
31
|
-
encoding: 'utf8', cwd, stdio: ['pipe', 'pipe', 'pipe']
|
|
32
|
-
}).trim();
|
|
33
|
-
|
|
34
|
-
// Main worktree is one level above the common .git dir
|
|
35
|
-
const mainWorktree = path.resolve(absCommonDir, '..');
|
|
36
|
-
|
|
37
|
-
return { inWorktree: true, branch, mainWorktree };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return { inWorktree: false };
|
|
41
|
-
} catch (_err) {
|
|
42
|
-
// Not in a git repo or git not available
|
|
43
|
-
return { inWorktree: false };
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = { detectWorktree };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect if the current directory is inside a git worktree.
|
|
8
|
+
* Uses git rev-parse --git-dir vs --git-common-dir — they differ in worktrees.
|
|
9
|
+
*
|
|
10
|
+
* @param {string} [cwd=process.cwd()] - Directory to check
|
|
11
|
+
* @returns {{ inWorktree: boolean, branch?: string, mainWorktree?: string }}
|
|
12
|
+
*/
|
|
13
|
+
function detectWorktree(cwd = process.cwd()) {
|
|
14
|
+
try {
|
|
15
|
+
const gitDir = execFileSync('git', ['rev-parse', '--git-dir'], {
|
|
16
|
+
encoding: 'utf8', cwd, stdio: ['pipe', 'pipe', 'pipe']
|
|
17
|
+
}).trim();
|
|
18
|
+
|
|
19
|
+
const gitCommonDir = execFileSync('git', ['rev-parse', '--git-common-dir'], {
|
|
20
|
+
encoding: 'utf8', cwd, stdio: ['pipe', 'pipe', 'pipe']
|
|
21
|
+
}).trim();
|
|
22
|
+
|
|
23
|
+
// Resolve to absolute paths for reliable comparison
|
|
24
|
+
const absGitDir = path.resolve(cwd, gitDir);
|
|
25
|
+
const absCommonDir = path.resolve(cwd, gitCommonDir);
|
|
26
|
+
|
|
27
|
+
// In a worktree, git-dir is like .git/worktrees/<name>
|
|
28
|
+
// while git-common-dir is the main .git directory
|
|
29
|
+
if (absGitDir !== absCommonDir) {
|
|
30
|
+
const branch = execFileSync('git', ['branch', '--show-current'], {
|
|
31
|
+
encoding: 'utf8', cwd, stdio: ['pipe', 'pipe', 'pipe']
|
|
32
|
+
}).trim();
|
|
33
|
+
|
|
34
|
+
// Main worktree is one level above the common .git dir
|
|
35
|
+
const mainWorktree = path.resolve(absCommonDir, '..');
|
|
36
|
+
|
|
37
|
+
return { inWorktree: true, branch, mainWorktree };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { inWorktree: false };
|
|
41
|
+
} catch (_err) {
|
|
42
|
+
// Not in a git repo or git not available
|
|
43
|
+
return { inWorktree: false };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { detectWorktree };
|
package/lib/file-hash.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
const crypto = require('crypto');
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Compute SHA-256 hex digest of a string.
|
|
6
|
-
* @param {string} content
|
|
7
|
-
* @returns {string} 64-char lowercase hex hash
|
|
8
|
-
*/
|
|
9
|
-
function contentHash(content) {
|
|
10
|
-
return crypto.createHash('sha256').update(content).digest('hex');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Check whether an existing file's content matches the given string.
|
|
15
|
-
* Returns false if the file does not exist.
|
|
16
|
-
* @param {string} filePath
|
|
17
|
-
* @param {string} newContent
|
|
18
|
-
* @returns {boolean}
|
|
19
|
-
*/
|
|
20
|
-
function fileMatchesContent(filePath, newContent) {
|
|
21
|
-
if (!fs.existsSync(filePath)) return false;
|
|
22
|
-
const existing = fs.readFileSync(filePath, 'utf8');
|
|
23
|
-
return contentHash(existing) === contentHash(newContent);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
module.exports = { contentHash, fileMatchesContent };
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compute SHA-256 hex digest of a string.
|
|
6
|
+
* @param {string} content
|
|
7
|
+
* @returns {string} 64-char lowercase hex hash
|
|
8
|
+
*/
|
|
9
|
+
function contentHash(content) {
|
|
10
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check whether an existing file's content matches the given string.
|
|
15
|
+
* Returns false if the file does not exist.
|
|
16
|
+
* @param {string} filePath
|
|
17
|
+
* @param {string} newContent
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
function fileMatchesContent(filePath, newContent) {
|
|
21
|
+
if (!fs.existsSync(filePath)) return false;
|
|
22
|
+
const existing = fs.readFileSync(filePath, 'utf8');
|
|
23
|
+
return contentHash(existing) === contentHash(newContent);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { contentHash, fileMatchesContent };
|