@exchanet/enet 1.0.8 → 1.0.9

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.
@@ -0,0 +1,110 @@
1
+ ---
2
+ description: Method PDCA-T — Systematic quality cycle for AI-assisted coding
3
+ trigger: always_on
4
+ ---
5
+
6
+ # METHOD PDCA-T — Active for all tasks in this project
7
+
8
+ You are operating under the PDCA-T quality methodology. Apply this 8-phase cycle to every coding task without exception.
9
+
10
+ ## PHASE 1 — PLANNING
11
+ Before writing any code:
12
+ - State the exact objective in one sentence
13
+ - Define what IS and IS NOT in scope
14
+ - Ask clarifying questions if anything is ambiguous
15
+ - Identify external dependencies
16
+ - Define the acceptance criterion
17
+
18
+ Do not advance until the objective is unambiguous.
19
+
20
+ ## PHASE 2 — REQUIREMENTS ANALYSIS
21
+ - List Functional Requirements: `FR-NN: [what the system must do]`
22
+ - List Non-Functional Requirements: `NFR-NN: [constraint or quality attribute with metric]`
23
+ - Build Risk Register: `RISK-NN: [risk] | Probability | Impact | Mitigation`
24
+
25
+ ## PHASE 3 — ARCHITECTURE DESIGN
26
+ Before any implementation:
27
+ - Write ADRs: `ADR-NN: [title] | Context | Decision | Alternatives | Consequences`
28
+ - Define interface contracts (function signatures + full docstrings) before implementing bodies
29
+ - Define module structure: domain / infrastructure / interfaces
30
+
31
+ ## PHASE 4 — MICRO-TASK CYCLE (≤ 50 lines per task)
32
+
33
+ **4.1** — Check available skills in `.cursor/skills/` and reusable context
34
+
35
+ **4.2** — Write tests FIRST. Required categories:
36
+ - Happy path · Error cases · Edge cases · Security · Performance (if applicable)
37
+ - Structure: Arrange / Act / Assert
38
+ - Naming: `test_[function]_[scenario]_[expected_outcome]`
39
+
40
+ **4.3** — Implement code. Standards:
41
+ - Full type hints on every parameter and return value
42
+ - Docstring with Args, Returns, Raises
43
+ - Single responsibility per function
44
+ - `Decimal` not `float` for monetary values
45
+ - Specific exception types — never bare `except:`
46
+ - Zero hardcoded configuration
47
+ - Structured logging with context fields
48
+
49
+ **4.4** — Self-review before running tests:
50
+ ```
51
+ □ Type hints complete? □ All inputs validated?
52
+ □ Docstring written? □ No hardcoded secrets?
53
+ □ Single responsibility? □ Semantic names?
54
+ □ No code duplication? □ Errors logged with context?
55
+ ```
56
+
57
+ **4.5** — Execute tests and show REAL complete output:
58
+ ```bash
59
+ pytest tests/ -v --cov=src --cov-report=term-missing --tb=short
60
+ ```
61
+ Never summarize. Never say "tests pass". Show the exact output.
62
+
63
+ **4.6** — Validate:
64
+ - All tests pass (100%)? If not → fix code, explain, re-run
65
+ - Coverage ≥ 99%? If not → identify uncovered lines → add tests → re-run
66
+ - Repeat until both conditions are met
67
+
68
+ ## PHASE 5 — INTEGRAL VALIDATION
69
+ After all micro-tasks:
70
+ - **Security:** No OWASP Top 10 issues · inputs validated · outputs sanitized · no hardcoded secrets · minimum privilege
71
+ - **Tests:** 100% passed · 0 failed · coverage ≥ 99% · all categories present
72
+ - **Code quality:** Type hints 100% · cyclomatic complexity < 10 · no duplication · SRP · docstrings 100%
73
+ - **Performance:** No N+1 · indexes on filter fields · pagination in collections · timeouts configured
74
+ - **Architecture:** No circular imports · layers respected · low coupling · inward dependencies only
75
+
76
+ ## PHASE 6 — TECHNICAL DEBT MANAGEMENT
77
+ Register every known issue before delivery:
78
+ ```
79
+ DEBT-XXX: [Short title]
80
+ Type: Technical | Test | Documentation | Architecture | Security | Performance
81
+ Description: [What and why]
82
+ Impact: High | Medium | Low — [justification]
83
+ Effort: Xh
84
+ Priority: High | Medium | Low
85
+ Plan: [Specific action and target version]
86
+ ```
87
+ Do not write TODO/FIXME in code — register as DEBT-XXX instead.
88
+
89
+ ## PHASE 7 — REFINEMENT TO ≥ 99%
90
+ If any metric is below target:
91
+ `Identify → Classify → Plan → Execute → Verify → Confirm ≥ 99%`
92
+ Never advance to Phase 8 without confirming ≥ 99% on all 5 validation dimensions.
93
+
94
+ ## PHASE 8 — DELIVERY REPORT
95
+ Always close every task with:
96
+ 1. Implementation summary (2-3 sentences)
97
+ 2. Test table: total / passed / failed / coverage / time
98
+ 3. Full unedited pytest output
99
+ 4. Key technical decisions with justifications
100
+ 5. Technical debt registered (DEBT-XXX list)
101
+ 6. CI/CD checklist (all items confirmed)
102
+ 7. Suggested next steps
103
+
104
+ ## ABSOLUTE RULES — NEVER VIOLATE
105
+ 1. Tests BEFORE implementation — always, no exceptions
106
+ 2. Show REAL test output — never summarize or omit
107
+ 3. No hardcoded secrets — environment variables from commit 1
108
+ 4. Coverage ≥ 99% before any delivery
109
+ 5. ADRs for non-trivial decisions
110
+ 6. All known issues as DEBT-XXX with priority and plan
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exchanet/enet",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "enet — exchanet methods manager. Install, scaffold and manage AI coding methods.",
5
5
  "bin": {
6
6
  "enet": "src/index.js"
@@ -2,26 +2,30 @@ import chalk from 'chalk'
2
2
  import ora from 'ora'
3
3
  import fs from 'fs-extra'
4
4
  import path from 'path'
5
+ import readline from 'readline'
5
6
  import { detectSystemAgents, getInstallPath, AGENTS } from '../utils/agent-detector.js'
6
7
  import { getMethod, fetchFromGitHub } from '../utils/registry.js'
7
8
 
8
9
  export async function installCommand(methodId, options) {
9
- // Load method from registry
10
+ // 1. Load method from registry
10
11
  const spinner = ora('Fetching registry...').start()
11
12
  const method = await getMethod(methodId).catch(() => null)
12
13
  spinner.stop()
13
14
 
14
15
  if (!method) {
15
- console.log(chalk.red(` ✗ Unknown method: "${methodId}"`))
16
+ console.log(chalk.red(`\n ✗ Unknown method: "${methodId}"`))
16
17
  console.log(chalk.dim(` Run ${chalk.white('enet list')} to see available methods.\n`))
17
18
  process.exit(1)
18
19
  }
19
20
 
20
- // Determine target agents
21
+ console.log(chalk.bold(`\n ◆ ${method.name}`))
22
+ console.log(chalk.dim(` ${method.description}\n`))
23
+
24
+ // 2. Determine target agents
21
25
  let targetAgents = []
22
26
 
23
27
  if (options.agent) {
24
- // --agent flag: install for a specific agent only
28
+ // --agent flag: skip detection, install for this specific agent only
25
29
  if (!AGENTS[options.agent]) {
26
30
  console.log(chalk.red(` ✗ Unknown agent: "${options.agent}"`))
27
31
  console.log(chalk.dim(` Valid: ${Object.keys(AGENTS).filter(k => k !== 'generic').join(', ')}\n`))
@@ -36,57 +40,147 @@ export async function installCommand(methodId, options) {
36
40
  if (detected.length === 0) {
37
41
  console.log(chalk.yellow(' ⚠ No AI agents detected on this system.'))
38
42
  console.log(chalk.dim(` Use ${chalk.white('--agent <name>')} to force an agent.`))
39
- console.log(chalk.dim(` Valid: cursor, windsurf, antigravity, claudecode, copilot, generic\n`))
43
+ console.log(chalk.dim(` Valid: ${Object.keys(AGENTS).filter(k => k !== 'generic').join(', ')}\n`))
40
44
  process.exit(1)
41
45
  }
42
46
 
43
- if (detected.length === 1 || options.global) {
44
- // Only one detected, or --global flag: install for all without asking
47
+ // 3. Show checkbox selection — always, even with 1 agent detected
48
+ // This is the core UX fix: user always chooses, never surprised
49
+ if (options.all) {
50
+ // --all flag skips the prompt
45
51
  targetAgents = detected
46
52
  } else {
47
- // Multiple agents detected: ask the user
48
- console.log(chalk.white(`\n Detected ${detected.length} agents on this system:\n`))
49
- detected.forEach((a, i) => console.log(chalk.dim(` [${i + 1}] ${a.name}`)))
50
- console.log(chalk.dim(` [${detected.length + 1}] All of the above\n`))
51
-
52
- const { createInterface } = await import('readline')
53
- const rl = createInterface({ input: process.stdin, output: process.stdout })
54
- const answer = await new Promise(resolve => {
55
- rl.question(chalk.white(' Install for which agent(s)? '), resolve)
56
- })
57
- rl.close()
53
+ targetAgents = await checkboxSelect(detected, method)
54
+ }
58
55
 
59
- const choice = parseInt(answer.trim())
60
- if (choice === detected.length + 1) {
61
- targetAgents = detected
62
- } else if (choice >= 1 && choice <= detected.length) {
63
- targetAgents = [detected[choice - 1]]
64
- } else {
65
- console.log(chalk.red('\n Invalid choice. Cancelled.\n'))
66
- process.exit(1)
67
- }
56
+ if (targetAgents.length === 0) {
57
+ console.log(chalk.dim('\n Nothing selected. Cancelled.\n'))
58
+ process.exit(0)
68
59
  }
69
60
  }
70
61
 
71
62
  console.log()
72
63
 
73
- // Install for each target agent
64
+ // 4. Install for each selected agent
74
65
  let schemaInstalled = false
75
66
  for (const agent of targetAgents) {
76
67
  await installForAgent(method, agent, options, schemaInstalled)
77
- schemaInstalled = true // only install schema once
68
+ schemaInstalled = true
78
69
  }
79
70
 
80
71
  printHints(methodId)
81
72
  }
82
73
 
74
+ // ─────────────────────────────────────────────────────────────────
75
+ // Checkbox selection UI
76
+ // ─────────────────────────────────────────────────────────────────
77
+
78
+ async function checkboxSelect(detected, method) {
79
+ // Build list: detected agents that have an adapter in this method come first,
80
+ // then show unavailable ones as disabled so user knows what exists
81
+ const available = detected.filter(a => method.adapters[a.key] || method.adapters['generic'])
82
+ const unavailable = detected.filter(a => !method.adapters[a.key] && !method.adapters['generic'])
83
+
84
+ if (available.length === 0) {
85
+ console.log(chalk.yellow(' ⚠ No adapters available for your detected agents.'))
86
+ console.log(chalk.dim(` Available adapters in this method: ${Object.keys(method.adapters).join(', ')}\n`))
87
+ process.exit(1)
88
+ }
89
+
90
+ // Initial state: all available agents pre-checked
91
+ const items = available.map(a => ({
92
+ agent: a,
93
+ checked: true,
94
+ usesGeneric: !method.adapters[a.key]
95
+ }))
96
+
97
+ console.log(chalk.white(' Agents detected on your system:\n'))
98
+
99
+ return new Promise((resolve) => {
100
+ let cursor = 0
101
+
102
+ const render = () => {
103
+ // Move cursor up to redraw (after first render)
104
+ const lines = items.length + 6
105
+ if (render.drawn) process.stdout.write(`\x1B[${lines}A`)
106
+ render.drawn = true
107
+
108
+ items.forEach((item, i) => {
109
+ const isCursor = i === cursor
110
+ const box = item.checked ? chalk.green('[✓]') : chalk.dim('[ ]')
111
+ const arrow = isCursor ? chalk.cyan(' ❯ ') : ' '
112
+ const name = isCursor ? chalk.white(item.agent.name) : chalk.dim(item.agent.name)
113
+ const tag = item.usesGeneric ? chalk.dim(' (generic adapter)') : ''
114
+ process.stdout.write(`${arrow}${box} ${name}${tag}\n`)
115
+ })
116
+
117
+ if (unavailable.length > 0) {
118
+ process.stdout.write(chalk.dim(`\n Not available for: ${unavailable.map(a => a.name).join(', ')}\n`))
119
+ } else {
120
+ process.stdout.write('\n')
121
+ }
122
+
123
+ process.stdout.write(chalk.dim(' ↑↓ navigate · Space toggle · A select all · Enter confirm\n\n'))
124
+ }
125
+
126
+ render()
127
+
128
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
129
+ if (process.stdin.isTTY) process.stdin.setRawMode(true)
130
+ process.stdin.resume()
131
+
132
+ process.stdin.on('data', (key) => {
133
+ const k = key.toString()
134
+
135
+ if (k === '\u001b[A') { // arrow up
136
+ cursor = (cursor - 1 + items.length) % items.length
137
+ render()
138
+ } else if (k === '\u001b[B') { // arrow down
139
+ cursor = (cursor + 1) % items.length
140
+ render()
141
+ } else if (k === ' ') { // space: toggle current
142
+ items[cursor].checked = !items[cursor].checked
143
+ render()
144
+ } else if (k === 'a' || k === 'A') { // A: toggle all
145
+ const allChecked = items.every(i => i.checked)
146
+ items.forEach(i => { i.checked = !allChecked })
147
+ render()
148
+ } else if (k === '\r' || k === '\n') { // Enter: confirm
149
+ if (process.stdin.isTTY) process.stdin.setRawMode(false)
150
+ process.stdin.pause()
151
+ rl.close()
152
+ const selected = items.filter(i => i.checked).map(i => i.agent)
153
+ console.log(chalk.dim(`\n Installing for: ${selected.map(a => a.name).join(', ')}\n`))
154
+ resolve(selected)
155
+ } else if (k === '\u0003') { // Ctrl+C
156
+ if (process.stdin.isTTY) process.stdin.setRawMode(false)
157
+ process.stdin.pause()
158
+ rl.close()
159
+ console.log('\n')
160
+ process.exit(0)
161
+ }
162
+ })
163
+ })
164
+ }
165
+
166
+ // ─────────────────────────────────────────────────────────────────
167
+ // Install for one agent
168
+ // ─────────────────────────────────────────────────────────────────
169
+
83
170
  async function installForAgent(method, agent, options, skipExtras = false) {
84
171
  const isGlobal = options.global || false
172
+
173
+ // Use the agent-specific adapter if available, fall back to generic
85
174
  const adapterKey = method.adapters[agent.key] ? agent.key : 'generic'
86
175
  const adapterPath = method.adapters[adapterKey]
87
176
 
177
+ if (!adapterPath) {
178
+ console.log(chalk.yellow(` ⚠ ${agent.name} — no adapter found, skipping`))
179
+ return
180
+ }
181
+
88
182
  if (isGlobal && !agent.globalInstallDir) {
89
- console.log(chalk.yellow(` ⚠ ${agent.name} does not support global install skipping`))
183
+ console.log(chalk.yellow(` ⚠ ${agent.name} global install not supported, skipping`))
90
184
  return
91
185
  }
92
186
 
@@ -98,7 +192,7 @@ async function installForAgent(method, agent, options, skipExtras = false) {
98
192
 
99
193
  await fs.ensureDir(path.dirname(installPath))
100
194
 
101
- // Windsurf global appends to global_rules.md
195
+ // Windsurf: append to .windsurfrules instead of overwriting
102
196
  if (agent.key === 'windsurf' && await fs.pathExists(installPath)) {
103
197
  const existing = await fs.readFile(installPath, 'utf8')
104
198
  if (existing.includes(method.name)) {
@@ -110,7 +204,7 @@ async function installForAgent(method, agent, options, skipExtras = false) {
110
204
  await fs.writeFile(installPath, content)
111
205
  }
112
206
 
113
- spinner.succeed(chalk.green(`${agent.name} — installed`))
207
+ spinner.succeed(chalk.green(`${agent.name}`))
114
208
  console.log(chalk.dim(` → ${installPath}`))
115
209
  console.log(chalk.dim(` ${agent.configNote}\n`))
116
210
 
@@ -119,7 +213,7 @@ async function installForAgent(method, agent, options, skipExtras = false) {
119
213
  return
120
214
  }
121
215
 
122
- // Download extras (schema, etc.) once
216
+ // Download extras (schema, etc.) — only once across all agents
123
217
  if (!skipExtras && method.extras) {
124
218
  for (const [key, filePath] of Object.entries(method.extras)) {
125
219
  const extraSpinner = ora(`Fetching ${key}...`).start()
@@ -136,18 +230,26 @@ async function installForAgent(method, agent, options, skipExtras = false) {
136
230
  }
137
231
  }
138
232
 
233
+ // ─────────────────────────────────────────────────────────────────
234
+ // Post-install hints
235
+ // ─────────────────────────────────────────────────────────────────
236
+
139
237
  function printHints(methodId) {
140
238
  if (methodId === 'modular-design') {
141
- console.log(chalk.dim(' Next:'))
142
- console.log(chalk.dim(` 1. Give your agent a spectech (stack + modules needed)`))
143
- console.log(chalk.dim(` 2. Agent declares architecture — confirm it`))
144
- console.log(chalk.dim(` 3. Agent builds Core → modules → Admin Panel`))
239
+ console.log(chalk.dim(' Next steps:'))
240
+ console.log(chalk.dim(' 1. Give your agent a spectech (stack + modules needed)'))
241
+ console.log(chalk.dim(' 2. Agent declares architecture — confirm it'))
242
+ console.log(chalk.dim(' 3. Agent builds Core → modules → Admin Panel'))
145
243
  console.log()
146
244
  console.log(chalk.dim(` ${chalk.white('enet new module <name>')} scaffold your first module`))
147
245
  console.log(chalk.dim(` ${chalk.white('enet validate')} check manifests at any time\n`))
148
246
  }
149
247
  if (methodId === 'pdca-t') {
150
- console.log(chalk.dim(` PDCA-T adds quality validation to your workflow.`))
248
+ console.log(chalk.dim(' Next steps:'))
249
+ console.log(chalk.dim(' 1. Start any coding task — the method activates automatically'))
250
+ console.log(chalk.dim(' 2. Your agent will follow the 8-phase quality cycle'))
251
+ console.log(chalk.dim(' 3. Every delivery includes a full test report'))
252
+ console.log()
151
253
  console.log(chalk.dim(` Works best alongside ${chalk.white('enet install modular-design')}.\n`))
152
254
  }
153
255
  }
@@ -8,7 +8,10 @@ export const AGENTS = {
8
8
  cursor: {
9
9
  name: 'Cursor',
10
10
  systemSignals: [
11
- path.join(HOME, '.cursor')
11
+ path.join(HOME, '.cursor'),
12
+ path.join(HOME, 'Library', 'Application Support', 'Cursor'),
13
+ path.join(HOME, 'AppData', 'Roaming', 'Cursor'),
14
+ path.join(HOME, '.config', 'Cursor'),
12
15
  ],
13
16
  projectSignals: ['.cursor/rules', '.cursor'],
14
17
  projectInstallDir: '.cursor/rules',
@@ -16,82 +19,105 @@ export const AGENTS = {
16
19
  filename: 'enet-{id}.md',
17
20
  configNote: 'Rule auto-applies to all files (alwaysApply: true)'
18
21
  },
22
+
19
23
  windsurf: {
20
24
  name: 'Windsurf',
21
25
  systemSignals: [
22
- path.join(HOME, '.codeium', 'windsurf')
26
+ path.join(HOME, '.codeium', 'windsurf'),
27
+ path.join(HOME, 'Library', 'Application Support', 'Windsurf'),
28
+ path.join(HOME, 'AppData', 'Roaming', 'Windsurf'),
23
29
  ],
24
30
  projectSignals: ['.windsurfrules', '.windsurf'],
25
31
  projectInstallDir: '.',
26
32
  globalInstallDir: path.join(HOME, '.codeium', 'windsurf', 'memories'),
27
33
  globalFilename: 'global_rules.md',
28
34
  filename: '.windsurfrules',
29
- configNote: 'Appended to global_rules.md'
35
+ configNote: 'Appended to .windsurfrules in project root'
30
36
  },
37
+
31
38
  antigravity: {
32
39
  name: 'Antigravity (Google)',
33
40
  systemSignals: [
34
- path.join(HOME, '.gemini', 'antigravity')
41
+ path.join(HOME, '.gemini', 'antigravity'),
42
+ path.join(HOME, 'Library', 'Application Support', 'Google', 'Antigravity'),
43
+ path.join(HOME, 'AppData', 'Roaming', 'Google', 'Antigravity'),
35
44
  ],
36
45
  projectSignals: ['.agent/rules', '.agent'],
37
46
  projectInstallDir: '.agent/rules',
38
- globalInstallDir: path.join(HOME, '.gemini', 'antigravity', 'skills', 'method-modular-design'),
47
+ globalInstallDir: path.join(HOME, '.gemini', 'antigravity', 'skills'),
39
48
  globalFilename: 'SKILL.md',
40
49
  filename: 'enet-{id}.md',
41
- configNote: 'Skill saved set activation to Always On in Antigravity'
50
+ configNote: 'Rule placed in .agent/rules/ activates automatically in Antigravity'
42
51
  },
52
+
43
53
  claudecode: {
44
54
  name: 'Claude Code',
45
55
  systemSignals: [
46
- path.join(HOME, '.claude')
56
+ path.join(HOME, '.claude'),
47
57
  ],
48
58
  projectSignals: ['CLAUDE.md', '.claude'],
49
59
  projectInstallDir: '.',
50
60
  globalInstallDir: path.join(HOME, '.claude'),
51
61
  globalFilename: 'CLAUDE.md',
52
62
  filename: 'CLAUDE.md',
53
- configNote: 'Written to ~/.claude/CLAUDE.md — Claude Code reads this automatically'
63
+ configNote: 'Written to CLAUDE.md — Claude Code reads this automatically'
54
64
  },
65
+
55
66
  copilot: {
56
67
  name: 'GitHub Copilot',
57
- systemSignals: [],
68
+ systemSignals: [
69
+ path.join(HOME, '.vscode', 'extensions'),
70
+ path.join(HOME, 'Library', 'Application Support', 'Code', 'User', 'extensions'),
71
+ path.join(HOME, 'AppData', 'Roaming', 'Code', 'User', 'extensions'),
72
+ path.join(HOME, '.vscode-server', 'extensions'),
73
+ ],
74
+ systemSignalFilter: (signalPath) => {
75
+ try {
76
+ const entries = fs.readdirSync(signalPath)
77
+ return entries.some(e => e.toLowerCase().startsWith('github.copilot'))
78
+ } catch {
79
+ return false
80
+ }
81
+ },
58
82
  projectSignals: ['.github/copilot-instructions.md'],
59
83
  projectInstallDir: '.github',
60
84
  globalInstallDir: null,
61
85
  filename: 'copilot-instructions.md',
62
86
  configNote: 'Written to .github/copilot-instructions.md'
63
87
  },
88
+
64
89
  generic: {
65
- name: 'Generic Agent',
90
+ name: 'Generic / Other agent',
66
91
  systemSignals: [],
67
92
  projectSignals: [],
68
93
  projectInstallDir: '.enet',
69
94
  globalInstallDir: null,
70
95
  filename: '{id}.md',
71
- configNote: 'Saved to .enet/ — paste contents into your agent\'s context'
96
+ configNote: "Saved to .enet/ — paste contents into your agent's context"
72
97
  }
73
98
  }
74
99
 
75
- /**
76
- * Detects ALL agents installed on the system by checking known global paths.
77
- */
78
100
  export async function detectSystemAgents() {
79
101
  const found = []
80
102
  for (const [key, agent] of Object.entries(AGENTS)) {
81
103
  if (key === 'generic') continue
82
104
  for (const signal of agent.systemSignals) {
83
- if (await fs.pathExists(signal)) {
84
- found.push({ key, ...agent })
85
- break
105
+ const exists = await fs.pathExists(signal)
106
+ if (!exists) continue
107
+ if (agent.systemSignalFilter) {
108
+ if (agent.systemSignalFilter(signal)) {
109
+ found.push({ key, ...agent })
110
+ break
111
+ }
112
+ continue
86
113
  }
114
+ found.push({ key, ...agent })
115
+ break
87
116
  }
88
117
  }
89
118
  return found
90
119
  }
91
120
 
92
- /**
93
- * Detects ALL agents present in the current project.
94
- */
95
121
  export async function detectProjectAgents(cwd = process.cwd()) {
96
122
  const found = []
97
123
  for (const [key, agent] of Object.entries(AGENTS)) {
@@ -106,22 +132,23 @@ export async function detectProjectAgents(cwd = process.cwd()) {
106
132
  return found
107
133
  }
108
134
 
109
- /**
110
- * Returns the first detected agent (legacy, used by status/doctor).
111
- */
112
135
  export async function detectAgent(cwd = process.cwd()) {
113
136
  const agents = await detectProjectAgents(cwd)
114
137
  return agents[0] ?? { key: 'generic', ...AGENTS.generic }
115
138
  }
116
139
 
117
- /**
118
- * Returns the install path for a method adapter.
119
- */
120
140
  export function getInstallPath(agent, methodId, { global = false, cwd = process.cwd() } = {}) {
121
141
  if (global) {
142
+ if (!agent.globalInstallDir) return null
143
+
144
+ if (agent.key === 'antigravity') {
145
+ return path.join(agent.globalInstallDir, `method-${methodId}`, 'SKILL.md')
146
+ }
147
+
122
148
  const filename = agent.globalFilename ?? agent.filename.replace('{id}', methodId)
123
149
  return path.join(agent.globalInstallDir, filename)
124
150
  }
151
+
125
152
  const filename = agent.filename.replace('{id}', methodId)
126
153
  return path.join(cwd, agent.projectInstallDir, filename)
127
154
  }