@exchanet/enet 1.0.7 → 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.
@@ -1,13 +1,13 @@
1
- # METHOD REFLEX — Agent Instructions
1
+ # METHOD MODULAR DESIGN — Agent Instructions
2
2
  > Universal Modular Architecture · v1.0.0 · @exchanet
3
3
 
4
4
  ---
5
5
 
6
6
  ## YOUR PRIMARY DIRECTIVE / TU DIRECTIVA PRINCIPAL
7
7
 
8
- You are building a modular application using Method REFLEX. Every module you create must be self-describing via a `manifest.json` file. The manifest is written **before any code**. The system reads manifests to automatically generate the Admin Panel, dashboard, and settings UI. You never manually wire backend logic to the Admin Panel — the manifest does it.
8
+ You are building a modular application using Method Modular Design. Every module you create must be self-describing via a `manifest.json` file. The manifest is written **before any code**. The system reads manifests to automatically generate the Admin Panel, dashboard, and settings UI. You never manually wire backend logic to the Admin Panel — the manifest does it.
9
9
 
10
- Estás construyendo una aplicación modular usando Method REFLEX. Cada módulo que crees debe ser auto-descriptivo a través de un archivo `manifest.json`. El manifest se escribe **antes de cualquier código**. El sistema lee los manifests para generar automáticamente el Panel Admin, dashboard y UI de configuración. Nunca cablearás manualmente la lógica backend al Panel Admin — el manifest lo hace.
10
+ Estás construyendo una aplicación modular usando Method Modular Design. Cada módulo que crees debe ser auto-descriptivo a través de un archivo `manifest.json`. El manifest se escribe **antes de cualquier código**. El sistema lee los manifests para generar automáticamente el Panel Admin, dashboard y UI de configuración. Nunca cablearás manualmente la lógica backend al Panel Admin — el manifest lo hace.
11
11
 
12
12
  **If a module has no manifest, it does not exist.**
13
13
  **Si un módulo no tiene manifest, no existe.**
@@ -133,4 +133,4 @@ Before marking any module complete / Antes de marcar cualquier módulo como comp
133
133
  ---
134
134
 
135
135
  *For full documentation / Para documentación completa: `METHOD.md`*
136
- *Method REFLEX v1.0.0 · @exchanet · MIT*
136
+ *Method Modular Design v1.0.0 · @exchanet · MIT*
@@ -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
@@ -0,0 +1,191 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://github.com/exchanet/method-reflex/manifest.schema.json",
4
+ "title": "Method REFLEX — Module Manifest",
5
+ "description": "Universal contract for self-describing modules. Every module must have a valid manifest.",
6
+ "type": "object",
7
+ "required": ["id", "name", "version", "type", "section"],
8
+ "additionalProperties": false,
9
+
10
+ "definitions": {
11
+ "handlerRef": {
12
+ "type": "string",
13
+ "pattern": "^[A-Z][a-zA-Z0-9]*\\.[a-z][a-zA-Z0-9]*$",
14
+ "description": "ClassName.methodName — e.g. ActivityLogger.getAll"
15
+ },
16
+ "capabilityType": {
17
+ "type": "string",
18
+ "enum": ["view", "action", "metric", "widget", "page", "theme", "component-override"]
19
+ },
20
+ "uiWidget": {
21
+ "type": "string",
22
+ "enum": ["text", "textarea", "number", "slider", "toggle", "select", "multiselect", "code", "color", "date", "tags"]
23
+ },
24
+ "settingType": {
25
+ "type": "string",
26
+ "enum": ["integer", "number", "string", "boolean", "select", "multiselect"]
27
+ }
28
+ },
29
+
30
+ "properties": {
31
+ "id": {
32
+ "type": "string",
33
+ "pattern": "^[a-z][a-z0-9-]*$",
34
+ "minLength": 2,
35
+ "maxLength": 64,
36
+ "description": "Unique module identifier. kebab-case. Used in URLs, API paths, and settings namespace."
37
+ },
38
+
39
+ "name": {
40
+ "type": "string",
41
+ "minLength": 2,
42
+ "maxLength": 100,
43
+ "description": "Human-readable module name. Shown in Admin Panel navigation."
44
+ },
45
+
46
+ "version": {
47
+ "type": "string",
48
+ "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$",
49
+ "description": "Semantic version: major.minor.patch"
50
+ },
51
+
52
+ "type": {
53
+ "type": "string",
54
+ "enum": ["core", "functional", "integration", "ui"],
55
+ "description": "core: system-level. functional: business logic. integration: external services. ui: themes and components."
56
+ },
57
+
58
+ "section": {
59
+ "type": "string",
60
+ "pattern": "^[a-z][a-z0-9-]*$",
61
+ "description": "Admin Panel navigation group. Modules with the same section are grouped together."
62
+ },
63
+
64
+ "description": {
65
+ "type": "string",
66
+ "maxLength": 500,
67
+ "description": "Optional. Human-readable description of the module."
68
+ },
69
+
70
+ "icon": {
71
+ "type": "string",
72
+ "description": "Optional. Icon name from the project's icon library."
73
+ },
74
+
75
+ "dependencies": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "string",
79
+ "pattern": "^[a-z][a-z0-9-]*$"
80
+ },
81
+ "description": "Module IDs that must be booted before this module."
82
+ },
83
+
84
+ "hooks": {
85
+ "type": "object",
86
+ "patternProperties": {
87
+ "^[a-z][a-z0-9]*(\\.[a-z][a-z0-9]*)*$": {
88
+ "$ref": "#/definitions/handlerRef"
89
+ }
90
+ },
91
+ "additionalProperties": false,
92
+ "description": "Events this module listens to. Format: { 'event.name': 'ClassName.method' }"
93
+ },
94
+
95
+ "settings": {
96
+ "type": "object",
97
+ "patternProperties": {
98
+ "^[a-z][a-z0-9_]*$": {
99
+ "type": "object",
100
+ "required": ["type", "label", "default"],
101
+ "additionalProperties": false,
102
+ "properties": {
103
+ "type": { "$ref": "#/definitions/settingType" },
104
+ "label": { "type": "string" },
105
+ "description": { "type": "string" },
106
+ "default": {},
107
+ "ui": { "$ref": "#/definitions/uiWidget" },
108
+ "options": {
109
+ "type": "array",
110
+ "items": {
111
+ "oneOf": [
112
+ { "type": "string" },
113
+ {
114
+ "type": "object",
115
+ "required": ["label", "value"],
116
+ "properties": {
117
+ "label": { "type": "string" },
118
+ "value": {}
119
+ }
120
+ }
121
+ ]
122
+ },
123
+ "description": "Required when type is select or multiselect."
124
+ },
125
+ "min": { "type": "number" },
126
+ "max": { "type": "number" },
127
+ "required": { "type": "boolean", "default": false }
128
+ }
129
+ }
130
+ },
131
+ "additionalProperties": false,
132
+ "description": "Configuration fields. Auto-rendered as forms in Admin Panel. Read via context.settings.get('key'). Never hardcode."
133
+ },
134
+
135
+ "capabilities": {
136
+ "type": "array",
137
+ "minItems": 0,
138
+ "items": {
139
+ "type": "object",
140
+ "required": ["type", "label"],
141
+ "additionalProperties": false,
142
+ "properties": {
143
+ "type": { "$ref": "#/definitions/capabilityType" },
144
+ "label": { "type": "string" },
145
+ "description": { "type": "string" },
146
+ "icon": { "type": "string" },
147
+ "data": {
148
+ "$ref": "#/definitions/handlerRef",
149
+ "description": "Handler that returns data. Required for: view, metric, widget."
150
+ },
151
+ "handler": {
152
+ "$ref": "#/definitions/handlerRef",
153
+ "description": "Handler that executes an operation. Required for: action."
154
+ },
155
+ "component": {
156
+ "type": "string",
157
+ "pattern": "^[A-Z][a-zA-Z0-9]*$",
158
+ "description": "Custom UI component name. For widget and page types."
159
+ },
160
+ "route": {
161
+ "type": "string",
162
+ "pattern": "^/[a-z0-9-/]*$",
163
+ "description": "URL route for page capabilities. Relative to /admin/{module-id}/"
164
+ },
165
+ "dangerous": {
166
+ "type": "boolean",
167
+ "default": false,
168
+ "description": "If true, Admin Panel shows a confirmation dialog before executing."
169
+ },
170
+ "permissions": {
171
+ "type": "array",
172
+ "items": {
173
+ "type": "string",
174
+ "pattern": "^[a-z][a-z0-9-]*:[a-z][a-z0-9-]*$"
175
+ },
176
+ "description": "Required permissions. Format: 'scope:action' e.g. 'users:delete'"
177
+ },
178
+ "target": {
179
+ "type": "string",
180
+ "description": "For component-override: the component name to replace."
181
+ },
182
+ "variables": {
183
+ "type": "object",
184
+ "description": "For theme capabilities: CSS custom property values."
185
+ }
186
+ }
187
+ },
188
+ "description": "What this module exposes. Each capability becomes a UI element in Admin Panel or dashboard."
189
+ }
190
+ }
191
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@exchanet/enet",
3
- "version": "1.0.7",
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"
@@ -25,13 +25,17 @@
25
25
  },
26
26
  "keywords": [
27
27
  "exchanet",
28
- "method-reflex",
28
+ "method-modular-design",
29
+ "method-enterprise-builder",
30
+ "method-iris",
29
31
  "method-pdca-t",
32
+ "military-grade",
30
33
  "modular-architecture",
31
34
  "vibe-coding",
32
35
  "cursor",
33
36
  "windsurf",
34
37
  "copilot",
38
+ "claudecode",
35
39
  "ai-coding"
36
40
  ],
37
41
  "author": "Francisco J Bernades (@exchanet)",
package/registry.json CHANGED
@@ -25,8 +25,8 @@
25
25
  "id": "pdca-t",
26
26
  "name": "Method PDCA-T",
27
27
  "description": "≥99% test coverage, zero vulnerabilities, systematic quality validation cycles. Use alongside Modular Design for pro-grade results.",
28
- "repo": "exchanet/method_pdca-t_coding_Cursor",
29
- "version": "1.0.0",
28
+ "repo": "exchanet/method_pdca-t_coding",
29
+ "version": "3.0.0",
30
30
  "tags": ["quality", "testing", "security", "coverage", "validation"],
31
31
  "adapters": {
32
32
  "cursor": ".cursor/rules/METHOD-PDCA-T.md",
@@ -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
  }