refacil-sdd-ai 4.2.4 → 4.3.0

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.
Files changed (47) hide show
  1. package/README.md +239 -214
  2. package/agents/auditor.md +182 -184
  3. package/agents/debugger.md +201 -204
  4. package/agents/implementer.md +150 -149
  5. package/agents/investigator.md +80 -89
  6. package/agents/proposer.md +219 -124
  7. package/agents/tester.md +140 -144
  8. package/agents/validator.md +153 -145
  9. package/bin/cli.js +158 -116
  10. package/lib/bus/askFulfillment.js +17 -17
  11. package/lib/bus/broker.js +599 -599
  12. package/lib/bus/ui/app.js +318 -318
  13. package/lib/commands/sdd.js +433 -0
  14. package/lib/hooks.js +236 -236
  15. package/lib/installer.js +55 -1
  16. package/lib/methodology-migration-pending.js +101 -136
  17. package/package.json +4 -6
  18. package/skills/apply/SKILL.md +122 -120
  19. package/skills/archive/SKILL.md +101 -107
  20. package/skills/ask/SKILL.md +78 -78
  21. package/skills/attend/SKILL.md +70 -70
  22. package/skills/bug/SKILL.md +121 -128
  23. package/skills/explore/SKILL.md +61 -63
  24. package/skills/guide/SKILL.md +79 -79
  25. package/skills/inbox/SKILL.md +43 -43
  26. package/skills/join/SKILL.md +82 -82
  27. package/skills/prereqs/BUS-CROSS-REPO.md +55 -55
  28. package/skills/prereqs/METHODOLOGY-CONTRACT.md +122 -115
  29. package/skills/prereqs/SKILL.md +30 -37
  30. package/skills/propose/SKILL.md +91 -102
  31. package/skills/reply/SKILL.md +44 -44
  32. package/skills/review/SKILL.md +135 -126
  33. package/skills/review/checklist-back.md +92 -92
  34. package/skills/review/checklist-front.md +72 -72
  35. package/skills/review/checklist.md +114 -114
  36. package/skills/say/SKILL.md +38 -38
  37. package/skills/setup/SKILL.md +85 -141
  38. package/skills/setup/troubleshooting.md +38 -35
  39. package/skills/test/SKILL.md +86 -94
  40. package/skills/test/testing-patterns.md +63 -63
  41. package/skills/up-code/SKILL.md +108 -108
  42. package/skills/update/SKILL.md +109 -132
  43. package/skills/verify/SKILL.md +128 -132
  44. package/templates/compact-guidance.md +45 -45
  45. package/templates/methodology-guide.md +46 -42
  46. package/config/openspec-config.yaml +0 -8
  47. package/skills/prereqs/OPENSPEC-DELTAS.md +0 -51
package/lib/hooks.js CHANGED
@@ -1,236 +1,236 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- // ── Cursor: todos los hooks en .cursor/hooks.json ────────────────────────────
7
-
8
- function readCursorHooksJson(projectRoot) {
9
- const p = path.join(projectRoot, '.cursor', 'hooks.json');
10
- if (!fs.existsSync(p)) return { version: 1, hooks: {} };
11
- try {
12
- const c = JSON.parse(fs.readFileSync(p, 'utf8'));
13
- if (!c.hooks) c.hooks = {};
14
- return c;
15
- } catch (_) {
16
- return { version: 1, hooks: {} };
17
- }
18
- }
19
-
20
- function writeCursorHooksJson(projectRoot, config) {
21
- const dir = path.join(projectRoot, '.cursor');
22
- fs.mkdirSync(dir, { recursive: true });
23
- fs.writeFileSync(path.join(dir, 'hooks.json'), JSON.stringify(config, null, 2) + '\n');
24
- }
25
-
26
- function installCursorHooks(projectRoot) {
27
- cleanLegacySettingsHooks(projectRoot);
28
- const config = readCursorHooksJson(projectRoot);
29
- let changed = false;
30
-
31
- function ensure(event, marker, entry) {
32
- if (!config.hooks[event]) config.hooks[event] = [];
33
- if (!config.hooks[event].some((h) => h[marker] === true)) {
34
- config.hooks[event].push(entry);
35
- changed = true;
36
- }
37
- }
38
-
39
- ensure('sessionStart', '_sdd', {
40
- _sdd: true,
41
- command: 'refacil-sdd-ai check-update',
42
- });
43
-
44
- // compact-bash debe ir ANTES de check-review
45
- if (!config.hooks.preToolUse) config.hooks.preToolUse = [];
46
- if (!config.hooks.preToolUse.some((h) => h._sdd_compact === true)) {
47
- config.hooks.preToolUse.unshift({ _sdd_compact: true, command: 'refacil-sdd-ai compact-bash', matcher: 'Bash' });
48
- changed = true;
49
- }
50
- if (!config.hooks.preToolUse.some((h) => h._sdd_review === true)) {
51
- config.hooks.preToolUse.push({ _sdd_review: true, command: 'refacil-sdd-ai check-review', matcher: 'Bash' });
52
- changed = true;
53
- }
54
-
55
- ensure('beforeSubmitPrompt', '_sdd_notify', {
56
- _sdd_notify: true,
57
- command: 'refacil-sdd-ai notify-update --cursor',
58
- });
59
-
60
- if (!changed) {
61
- console.log(' Hooks SDD-AI ya configurados en .cursor/hooks.json.');
62
- return false;
63
- }
64
-
65
- writeCursorHooksJson(projectRoot, config);
66
- return true;
67
- }
68
-
69
- function uninstallCursorHooks(projectRoot) {
70
- const hooksJsonPath = path.join(projectRoot, '.cursor', 'hooks.json');
71
- let changed = false;
72
-
73
- if (fs.existsSync(hooksJsonPath)) {
74
- let config;
75
- try { config = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8')); } catch (_) { config = null; }
76
-
77
- if (config && config.hooks) {
78
- const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
79
- for (const event of Object.keys(config.hooks)) {
80
- if (!Array.isArray(config.hooks[event])) continue;
81
- const before = config.hooks[event].length;
82
- config.hooks[event] = config.hooks[event].filter(
83
- (h) => !sddMarkers.some((m) => h[m] === true),
84
- );
85
- if (config.hooks[event].length !== before) changed = true;
86
- if (config.hooks[event].length === 0) delete config.hooks[event];
87
- }
88
- if (changed) writeCursorHooksJson(projectRoot, config);
89
- }
90
- }
91
-
92
- // Limpiar vestigios en settings.json de instalaciones previas
93
- const settingsPath = path.join(projectRoot, '.cursor', 'settings.json');
94
- if (fs.existsSync(settingsPath)) {
95
- let settings;
96
- try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { settings = null; }
97
- if (settings && settings.hooks) {
98
- const evts = ['SessionStart', 'PreToolUse', 'UserPromptSubmit', 'beforeSubmitPrompt', 'Stop', 'afterAgentResponse'];
99
- const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
100
- for (const evt of evts) {
101
- if (!Array.isArray(settings.hooks[evt])) continue;
102
- const before = settings.hooks[evt].length;
103
- settings.hooks[evt] = settings.hooks[evt].filter((h) => !sddMarkers.some((m) => h[m] === true));
104
- if (settings.hooks[evt].length !== before) changed = true;
105
- if (settings.hooks[evt].length === 0) delete settings.hooks[evt];
106
- }
107
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
108
- if (changed) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
109
- }
110
- }
111
-
112
- return changed;
113
- }
114
-
115
- // ── Claude Code: todos los hooks en .claude/settings.json ───────────────────
116
-
117
- function installClaudeHooks(projectRoot) {
118
- const settingsDir = path.join(projectRoot, '.claude');
119
- const settingsPath = path.join(settingsDir, 'settings.json');
120
- let settings = {};
121
-
122
- if (fs.existsSync(settingsPath)) {
123
- try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { settings = {}; }
124
- }
125
-
126
- if (!settings.hooks) settings.hooks = {};
127
- let changed = false;
128
-
129
- if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
130
- if (!settings.hooks.SessionStart.some((h) => h._sdd === true)) {
131
- settings.hooks.SessionStart.push({ _sdd: true, matcher: '', hooks: [{ type: 'command', command: 'refacil-sdd-ai check-update' }] });
132
- changed = true;
133
- }
134
-
135
- if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
136
- if (!settings.hooks.UserPromptSubmit.some((h) => h._sdd_notify === true)) {
137
- settings.hooks.UserPromptSubmit.push({ _sdd_notify: true, matcher: '', hooks: [{ type: 'command', command: 'refacil-sdd-ai notify-update' }] });
138
- changed = true;
139
- }
140
-
141
- if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
142
- if (!settings.hooks.PreToolUse.some((h) => h._sdd_compact === true)) {
143
- settings.hooks.PreToolUse.unshift({ _sdd_compact: true, matcher: 'Bash', hooks: [{ type: 'command', command: 'refacil-sdd-ai compact-bash' }] });
144
- changed = true;
145
- }
146
- if (!settings.hooks.PreToolUse.some((h) => h._sdd_review === true)) {
147
- settings.hooks.PreToolUse.push({ _sdd_review: true, matcher: 'Bash', hooks: [{ type: 'command', command: 'refacil-sdd-ai check-review' }] });
148
- changed = true;
149
- }
150
-
151
- if (!changed) {
152
- console.log(' Hooks SDD-AI ya configurados en .claude/settings.json.');
153
- return false;
154
- }
155
-
156
- fs.mkdirSync(settingsDir, { recursive: true });
157
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
158
- return true;
159
- }
160
-
161
- function uninstallClaudeHooks(projectRoot) {
162
- const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
163
- if (!fs.existsSync(settingsPath)) return false;
164
-
165
- let settings;
166
- try {
167
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
168
- } catch (_) {
169
- console.log(' No se pudieron remover hooks: .claude/settings.json invalido.');
170
- return false;
171
- }
172
-
173
- if (!settings.hooks) return false;
174
-
175
- let changed = false;
176
- const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
177
-
178
- for (const event of Object.keys(settings.hooks)) {
179
- if (!Array.isArray(settings.hooks[event])) continue;
180
- const before = settings.hooks[event].length;
181
- settings.hooks[event] = settings.hooks[event].filter((h) => !sddMarkers.some((m) => h[m] === true));
182
- if (settings.hooks[event].length !== before) changed = true;
183
- if (settings.hooks[event].length === 0) delete settings.hooks[event];
184
- }
185
-
186
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
187
- if (!changed) return false;
188
-
189
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
190
- return true;
191
- }
192
-
193
- // ── Limpieza de hooks SDD en settings.json (migracion legacy) ───────────────
194
-
195
- function cleanLegacySettingsHooks(projectRoot) {
196
- const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
197
- const evts = ['SessionStart', 'PreToolUse', 'UserPromptSubmit', 'beforeSubmitPrompt', 'Stop', 'afterAgentResponse'];
198
-
199
- for (const ideDir of ['.cursor']) {
200
- const settingsPath = path.join(projectRoot, ideDir, 'settings.json');
201
- if (!fs.existsSync(settingsPath)) continue;
202
-
203
- let settings;
204
- try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { continue; }
205
- if (!settings || !settings.hooks) continue;
206
-
207
- let changed = false;
208
- for (const evt of evts) {
209
- if (!Array.isArray(settings.hooks[evt])) continue;
210
- const before = settings.hooks[evt].length;
211
- settings.hooks[evt] = settings.hooks[evt].filter((h) => !sddMarkers.some((m) => h[m] === true));
212
- if (settings.hooks[evt].length !== before) changed = true;
213
- if (settings.hooks[evt].length === 0) delete settings.hooks[evt];
214
- }
215
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
216
- if (changed) {
217
- try { fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n'); } catch (_) {}
218
- }
219
- }
220
- }
221
-
222
- // ── Fachada pública ──────────────────────────────────────────────────────────
223
-
224
- function installHooks(ideDir, projectRoot) {
225
- return ideDir === '.cursor'
226
- ? installCursorHooks(projectRoot)
227
- : installClaudeHooks(projectRoot);
228
- }
229
-
230
- function uninstallHooks(ideDir, projectRoot) {
231
- return ideDir === '.cursor'
232
- ? uninstallCursorHooks(projectRoot)
233
- : uninstallClaudeHooks(projectRoot);
234
- }
235
-
236
- module.exports = { installHooks, uninstallHooks, cleanLegacySettingsHooks };
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // ── Cursor: todos los hooks en .cursor/hooks.json ────────────────────────────
7
+
8
+ function readCursorHooksJson(projectRoot) {
9
+ const p = path.join(projectRoot, '.cursor', 'hooks.json');
10
+ if (!fs.existsSync(p)) return { version: 1, hooks: {} };
11
+ try {
12
+ const c = JSON.parse(fs.readFileSync(p, 'utf8'));
13
+ if (!c.hooks) c.hooks = {};
14
+ return c;
15
+ } catch (_) {
16
+ return { version: 1, hooks: {} };
17
+ }
18
+ }
19
+
20
+ function writeCursorHooksJson(projectRoot, config) {
21
+ const dir = path.join(projectRoot, '.cursor');
22
+ fs.mkdirSync(dir, { recursive: true });
23
+ fs.writeFileSync(path.join(dir, 'hooks.json'), JSON.stringify(config, null, 2) + '\n');
24
+ }
25
+
26
+ function installCursorHooks(projectRoot) {
27
+ cleanLegacySettingsHooks(projectRoot);
28
+ const config = readCursorHooksJson(projectRoot);
29
+ let changed = false;
30
+
31
+ function ensure(event, marker, entry) {
32
+ if (!config.hooks[event]) config.hooks[event] = [];
33
+ if (!config.hooks[event].some((h) => h[marker] === true)) {
34
+ config.hooks[event].push(entry);
35
+ changed = true;
36
+ }
37
+ }
38
+
39
+ ensure('sessionStart', '_sdd', {
40
+ _sdd: true,
41
+ command: 'refacil-sdd-ai check-update',
42
+ });
43
+
44
+ // compact-bash debe ir ANTES de check-review
45
+ if (!config.hooks.preToolUse) config.hooks.preToolUse = [];
46
+ if (!config.hooks.preToolUse.some((h) => h._sdd_compact === true)) {
47
+ config.hooks.preToolUse.unshift({ _sdd_compact: true, command: 'refacil-sdd-ai compact-bash', matcher: 'Bash' });
48
+ changed = true;
49
+ }
50
+ if (!config.hooks.preToolUse.some((h) => h._sdd_review === true)) {
51
+ config.hooks.preToolUse.push({ _sdd_review: true, command: 'refacil-sdd-ai check-review', matcher: 'Bash' });
52
+ changed = true;
53
+ }
54
+
55
+ ensure('beforeSubmitPrompt', '_sdd_notify', {
56
+ _sdd_notify: true,
57
+ command: 'refacil-sdd-ai notify-update --cursor',
58
+ });
59
+
60
+ if (!changed) {
61
+ console.log(' Hooks SDD-AI ya configurados en .cursor/hooks.json.');
62
+ return false;
63
+ }
64
+
65
+ writeCursorHooksJson(projectRoot, config);
66
+ return true;
67
+ }
68
+
69
+ function uninstallCursorHooks(projectRoot) {
70
+ const hooksJsonPath = path.join(projectRoot, '.cursor', 'hooks.json');
71
+ let changed = false;
72
+
73
+ if (fs.existsSync(hooksJsonPath)) {
74
+ let config;
75
+ try { config = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8')); } catch (_) { config = null; }
76
+
77
+ if (config && config.hooks) {
78
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
79
+ for (const event of Object.keys(config.hooks)) {
80
+ if (!Array.isArray(config.hooks[event])) continue;
81
+ const before = config.hooks[event].length;
82
+ config.hooks[event] = config.hooks[event].filter(
83
+ (h) => !sddMarkers.some((m) => h[m] === true),
84
+ );
85
+ if (config.hooks[event].length !== before) changed = true;
86
+ if (config.hooks[event].length === 0) delete config.hooks[event];
87
+ }
88
+ if (changed) writeCursorHooksJson(projectRoot, config);
89
+ }
90
+ }
91
+
92
+ // Limpiar vestigios en settings.json de instalaciones previas
93
+ const settingsPath = path.join(projectRoot, '.cursor', 'settings.json');
94
+ if (fs.existsSync(settingsPath)) {
95
+ let settings;
96
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { settings = null; }
97
+ if (settings && settings.hooks) {
98
+ const evts = ['SessionStart', 'PreToolUse', 'UserPromptSubmit', 'beforeSubmitPrompt', 'Stop', 'afterAgentResponse'];
99
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
100
+ for (const evt of evts) {
101
+ if (!Array.isArray(settings.hooks[evt])) continue;
102
+ const before = settings.hooks[evt].length;
103
+ settings.hooks[evt] = settings.hooks[evt].filter((h) => !sddMarkers.some((m) => h[m] === true));
104
+ if (settings.hooks[evt].length !== before) changed = true;
105
+ if (settings.hooks[evt].length === 0) delete settings.hooks[evt];
106
+ }
107
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
108
+ if (changed) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
109
+ }
110
+ }
111
+
112
+ return changed;
113
+ }
114
+
115
+ // ── Claude Code: todos los hooks en .claude/settings.json ───────────────────
116
+
117
+ function installClaudeHooks(projectRoot) {
118
+ const settingsDir = path.join(projectRoot, '.claude');
119
+ const settingsPath = path.join(settingsDir, 'settings.json');
120
+ let settings = {};
121
+
122
+ if (fs.existsSync(settingsPath)) {
123
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { settings = {}; }
124
+ }
125
+
126
+ if (!settings.hooks) settings.hooks = {};
127
+ let changed = false;
128
+
129
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
130
+ if (!settings.hooks.SessionStart.some((h) => h._sdd === true)) {
131
+ settings.hooks.SessionStart.push({ _sdd: true, matcher: '', hooks: [{ type: 'command', command: 'refacil-sdd-ai check-update' }] });
132
+ changed = true;
133
+ }
134
+
135
+ if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
136
+ if (!settings.hooks.UserPromptSubmit.some((h) => h._sdd_notify === true)) {
137
+ settings.hooks.UserPromptSubmit.push({ _sdd_notify: true, matcher: '', hooks: [{ type: 'command', command: 'refacil-sdd-ai notify-update' }] });
138
+ changed = true;
139
+ }
140
+
141
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
142
+ if (!settings.hooks.PreToolUse.some((h) => h._sdd_compact === true)) {
143
+ settings.hooks.PreToolUse.unshift({ _sdd_compact: true, matcher: 'Bash', hooks: [{ type: 'command', command: 'refacil-sdd-ai compact-bash' }] });
144
+ changed = true;
145
+ }
146
+ if (!settings.hooks.PreToolUse.some((h) => h._sdd_review === true)) {
147
+ settings.hooks.PreToolUse.push({ _sdd_review: true, matcher: 'Bash', hooks: [{ type: 'command', command: 'refacil-sdd-ai check-review' }] });
148
+ changed = true;
149
+ }
150
+
151
+ if (!changed) {
152
+ console.log(' Hooks SDD-AI ya configurados en .claude/settings.json.');
153
+ return false;
154
+ }
155
+
156
+ fs.mkdirSync(settingsDir, { recursive: true });
157
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
158
+ return true;
159
+ }
160
+
161
+ function uninstallClaudeHooks(projectRoot) {
162
+ const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
163
+ if (!fs.existsSync(settingsPath)) return false;
164
+
165
+ let settings;
166
+ try {
167
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
168
+ } catch (_) {
169
+ console.log(' No se pudieron remover hooks: .claude/settings.json invalido.');
170
+ return false;
171
+ }
172
+
173
+ if (!settings.hooks) return false;
174
+
175
+ let changed = false;
176
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
177
+
178
+ for (const event of Object.keys(settings.hooks)) {
179
+ if (!Array.isArray(settings.hooks[event])) continue;
180
+ const before = settings.hooks[event].length;
181
+ settings.hooks[event] = settings.hooks[event].filter((h) => !sddMarkers.some((m) => h[m] === true));
182
+ if (settings.hooks[event].length !== before) changed = true;
183
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
184
+ }
185
+
186
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
187
+ if (!changed) return false;
188
+
189
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
190
+ return true;
191
+ }
192
+
193
+ // ── Limpieza de hooks SDD en settings.json (migracion legacy) ───────────────
194
+
195
+ function cleanLegacySettingsHooks(projectRoot) {
196
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
197
+ const evts = ['SessionStart', 'PreToolUse', 'UserPromptSubmit', 'beforeSubmitPrompt', 'Stop', 'afterAgentResponse'];
198
+
199
+ for (const ideDir of ['.cursor']) {
200
+ const settingsPath = path.join(projectRoot, ideDir, 'settings.json');
201
+ if (!fs.existsSync(settingsPath)) continue;
202
+
203
+ let settings;
204
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { continue; }
205
+ if (!settings || !settings.hooks) continue;
206
+
207
+ let changed = false;
208
+ for (const evt of evts) {
209
+ if (!Array.isArray(settings.hooks[evt])) continue;
210
+ const before = settings.hooks[evt].length;
211
+ settings.hooks[evt] = settings.hooks[evt].filter((h) => !sddMarkers.some((m) => h[m] === true));
212
+ if (settings.hooks[evt].length !== before) changed = true;
213
+ if (settings.hooks[evt].length === 0) delete settings.hooks[evt];
214
+ }
215
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
216
+ if (changed) {
217
+ try { fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n'); } catch (_) {}
218
+ }
219
+ }
220
+ }
221
+
222
+ // ── Fachada pública ──────────────────────────────────────────────────────────
223
+
224
+ function installHooks(ideDir, projectRoot) {
225
+ return ideDir === '.cursor'
226
+ ? installCursorHooks(projectRoot)
227
+ : installClaudeHooks(projectRoot);
228
+ }
229
+
230
+ function uninstallHooks(ideDir, projectRoot) {
231
+ return ideDir === '.cursor'
232
+ ? uninstallCursorHooks(projectRoot)
233
+ : uninstallClaudeHooks(projectRoot);
234
+ }
235
+
236
+ module.exports = { installHooks, uninstallHooks, cleanLegacySettingsHooks };
package/lib/installer.js CHANGED
@@ -214,6 +214,59 @@ function removeSkills(projectRoot) {
214
214
  return removed;
215
215
  }
216
216
 
217
+ function removeOpenspecLegacyAssets(projectRoot) {
218
+ let removed = 0;
219
+
220
+ // Remove .claude/skills/openspec-*/ and .cursor/skills/openspec-*/
221
+ for (const ideDir of ['.claude', '.cursor']) {
222
+ const skillsDir = path.join(projectRoot, ideDir, 'skills');
223
+ if (!fs.existsSync(skillsDir)) continue;
224
+ let entries;
225
+ try {
226
+ entries = fs.readdirSync(skillsDir, { withFileTypes: true });
227
+ } catch (_) {
228
+ continue;
229
+ }
230
+ for (const entry of entries) {
231
+ if (entry.isDirectory() && entry.name.startsWith('openspec-')) {
232
+ fs.rmSync(path.join(skillsDir, entry.name), { recursive: true });
233
+ removed++;
234
+ }
235
+ }
236
+ }
237
+
238
+ // Remove .claude/commands/opsx/ directory
239
+ const claudeOpsx = path.join(projectRoot, '.claude', 'commands', 'opsx');
240
+ if (fs.existsSync(claudeOpsx)) {
241
+ fs.rmSync(claudeOpsx, { recursive: true });
242
+ removed++;
243
+ }
244
+
245
+ // Remove .cursor/commands/opsx/ directory and opsx-*.md loose files
246
+ const cursorCommandsDir = path.join(projectRoot, '.cursor', 'commands');
247
+ const cursorOpsx = path.join(cursorCommandsDir, 'opsx');
248
+ if (fs.existsSync(cursorOpsx)) {
249
+ fs.rmSync(cursorOpsx, { recursive: true });
250
+ removed++;
251
+ }
252
+ if (fs.existsSync(cursorCommandsDir)) {
253
+ let entries;
254
+ try {
255
+ entries = fs.readdirSync(cursorCommandsDir, { withFileTypes: true });
256
+ } catch (_) {
257
+ entries = [];
258
+ }
259
+ for (const entry of entries) {
260
+ if (entry.isFile() && entry.name.startsWith('opsx-') && entry.name.endsWith('.md')) {
261
+ fs.unlinkSync(path.join(cursorCommandsDir, entry.name));
262
+ removed++;
263
+ }
264
+ }
265
+ }
266
+
267
+ return removed;
268
+ }
269
+
217
270
  function checkClaudeCodeVersion() {
218
271
  const { execSync } = require('child_process');
219
272
  try {
@@ -244,7 +297,7 @@ function checkNodeVersion() {
244
297
 
245
298
  if (major < 20 || (major === 20 && minor < 19)) {
246
299
  console.log(`\n ADVERTENCIA: Node.js ${version} detectado.`);
247
- console.log(' OpenSpec requiere Node.js >= 20.19.0.');
300
+ console.log(' refacil-sdd-ai requiere Node.js >= 20.19.0.');
248
301
  console.log(' Las skills se instalaran pero /refacil:setup podria fallar al instalar OpenSpec.\n');
249
302
  return false;
250
303
  }
@@ -264,6 +317,7 @@ module.exports = {
264
317
  writeRepoVersion,
265
318
  getPackageVersion,
266
319
  removeSkills,
320
+ removeOpenspecLegacyAssets,
267
321
  checkClaudeCodeVersion,
268
322
  checkNodeVersion,
269
323
  };