refacil-sdd-ai 4.0.3 → 4.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/README.md CHANGED
@@ -72,7 +72,7 @@ npm uninstall -g refacil-sdd-ai
72
72
  | Comando | Descripcion |
73
73
  |---|---|
74
74
  | `refacil-sdd-ai check-update` | (`SessionStart`) Verifica nueva version, sincroniza skills y `compact-guidance` en AGENTS.md |
75
- | `refacil-sdd-ai notify-update` | (`Stop` / `afterAgentResponse`) Notifica al LLM sobre migraciones pendientes al finalizar la primera respuesta; el agente ejecuta `/refacil:update` automaticamente |
75
+ | `refacil-sdd-ai notify-update` | (`UserPromptSubmit` / `beforeSubmitPrompt`) Pregunta al usuario si desea ejecutar `/refacil:update` cuando hay migraciones pendientes |
76
76
  | `refacil-sdd-ai check-review` | (`PreToolUse`) Bloquea `git push` si falta `.review-passed` en algun cambio activo |
77
77
  | `refacil-sdd-ai compact-bash` | (`PreToolUse`) Reescribe comandos Bash bare via `updatedInput` |
78
78
 
@@ -234,13 +234,13 @@ Se instalan en `.claude/settings.json` **y** `.cursor/settings.json` durante `in
234
234
  | Hook | Evento | Que hace |
235
235
  |---|---|---|
236
236
  | `check-update` | `SessionStart` | Chequea nueva version en npm, la instala, sincroniza skills y **sincroniza el bloque `compact-guidance`** en `AGENTS.md`. Si hubo actualizacion, escribe un flag de notificacion para el siguiente hook. |
237
- | `notify-update` | `Stop` (Claude Code) / `afterAgentResponse` (Cursor) | Al finalizar la primera respuesta del agente, lee el flag de actualizacion pendiente. Si existe, inyecta la instruccion de ejecutar `/refacil:update` — el agente la ejecuta automaticamente y el usuario ve el resultado al final. El flag se borra tras notificar. |
237
+ | `notify-update` | `UserPromptSubmit` (Claude Code) / `beforeSubmitPrompt` (Cursor) | Antes de procesar el siguiente mensaje del usuario, lee el flag de actualizacion pendiente. Si existe, inyecta la instruccion para que el agente pregunte al usuario si desea ejecutar `/refacil:update`. El flag se borra tras notificar. |
238
238
  | `compact-bash` | `PreToolUse` (Bash) | Reescribe silenciosamente comandos Bash bare via `updatedInput`. Sin turnos extra, sin que el IDE vea el cambio. Requiere Claude Code >= 2.1.89. |
239
239
  | `check-review` | `PreToolUse` (Bash) | Intercepta `git push` y bloquea si falta `.review-passed` en algun cambio activo. |
240
240
 
241
241
  Los cuatro hooks se instalan en `.claude/settings.json` (Claude Code) y `.cursor/settings.json` (Cursor) con la misma logica parametrica.
242
242
 
243
- > **Por que dos hooks para el update?** `SessionStart` hace el sync silencioso al abrir la sesion sin interaccion del usuario. `notify-update` en `Stop` se dispara al finalizar la primera respuesta del agente la instruccion queda al final visible para el usuario y Claude la ejecuta automaticamente en el siguiente turno.
243
+ > **Por que dos hooks para el update?** `SessionStart` hace el sync silencioso al abrir la sesion sin interaccion del usuario. `notify-update` en `UserPromptSubmit` / `beforeSubmitPrompt` inyecta la instruccion justo antes de que el agente procese el siguiente mensaje del usuario, garantizando que no se ignore.
244
244
 
245
245
  ### Gate de review en el push
246
246
 
package/bin/cli.js CHANGED
@@ -53,13 +53,16 @@ function notifyUpdate() {
53
53
 
54
54
  let info = {};
55
55
  try { info = JSON.parse(fs.readFileSync(flagPath, 'utf8')); } catch (_) {}
56
- try { fs.unlinkSync(flagPath); } catch (_) {}
57
56
 
58
- const fromLabel = info.from ? `v${info.from}` : 'version desconocida';
59
- console.log(
60
- `[refacil-sdd-ai] La metodologia fue actualizada (${fromLabel} → v${info.to}). ` +
61
- 'Ejecuta /refacil:update ahora para aplicar las migraciones pendientes e informa el resultado al usuario.',
62
- );
57
+ const fromLabel = info.from ? `v${info.from}` : 'version anterior';
58
+ const msg =
59
+ `[refacil-sdd-ai] Actualizacion de metodologia detectada (${fromLabel} → v${info.to}).\n` +
60
+ `Tu mensaje fue pausado. ¿Deseas aplicar las migraciones pendientes antes de continuar?\n` +
61
+ ` • Si: ejecuta /refacil:update\n` +
62
+ ` • No: reenvía tu mensaje anterior para continuar sin actualizar.`;
63
+ process.stdout.write(msg + '\n');
64
+ process.stderr.write(msg + '\n');
65
+ process.exit(2);
63
66
  }
64
67
 
65
68
  function repoIsInitialized() {
package/lib/hooks.js CHANGED
@@ -1,130 +1,206 @@
1
- 'use strict';
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
-
6
- function installHooks(ideDir, projectRoot) {
7
- const settingsDir = path.join(projectRoot, ideDir);
8
- const settingsPath = path.join(settingsDir, 'settings.json');
9
- let settings = {};
10
-
11
- if (fs.existsSync(settingsPath)) {
12
- try {
13
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
14
- } catch (_) {
15
- settings = {};
16
- }
17
- }
18
-
19
- if (!settings.hooks) settings.hooks = {};
20
- if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
21
-
22
- let changed = false;
23
-
24
- const hasUpdateHook = settings.hooks.SessionStart.some((h) => h._sdd === true);
25
- if (!hasUpdateHook) {
26
- settings.hooks.SessionStart.push({
27
- _sdd: true,
28
- matcher: '',
29
- hooks: [{ type: 'command', command: 'refacil-sdd-ai check-update' }],
30
- });
31
- changed = true;
32
- }
33
-
34
- // Claude Code usa "Stop"; Cursor usa "afterAgentResponse" (equivalente al fin de turno del agente)
35
- const notifyEvent = ideDir === '.cursor' ? 'afterAgentResponse' : 'Stop';
36
- if (!settings.hooks[notifyEvent]) settings.hooks[notifyEvent] = [];
37
-
38
- const hasNotifyHook = settings.hooks[notifyEvent].some((h) => h._sdd_notify === true);
39
- if (!hasNotifyHook) {
40
- settings.hooks[notifyEvent].push({
41
- _sdd_notify: true,
42
- matcher: '',
43
- hooks: [{ type: 'command', command: 'refacil-sdd-ai notify-update' }],
44
- });
45
- changed = true;
46
- }
47
-
48
- if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
49
-
50
- // compact-bash debe correr ANTES de check-review para que el rewrite sea visible
51
- const hasCompactHook = settings.hooks.PreToolUse.some((h) => h._sdd_compact === true);
52
- if (!hasCompactHook) {
53
- settings.hooks.PreToolUse.unshift({
54
- _sdd_compact: true,
55
- matcher: 'Bash',
56
- hooks: [{ type: 'command', command: 'refacil-sdd-ai compact-bash' }],
57
- });
58
- changed = true;
59
- }
60
-
61
- const hasReviewHook = settings.hooks.PreToolUse.some((h) => h._sdd_review === true);
62
- if (!hasReviewHook) {
63
- settings.hooks.PreToolUse.push({
64
- _sdd_review: true,
65
- matcher: 'Bash',
66
- hooks: [{ type: 'command', command: 'refacil-sdd-ai check-review' }],
67
- });
68
- changed = true;
69
- }
70
-
71
- if (!changed) {
72
- console.log(` Hooks SDD-AI ya configurados en ${ideDir}/settings.json.`);
73
- return false;
74
- }
75
-
76
- fs.mkdirSync(settingsDir, { recursive: true });
77
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
78
- return true;
79
- }
80
-
81
- function uninstallHooks(ideDir, projectRoot) {
82
- const settingsPath = path.join(projectRoot, ideDir, 'settings.json');
83
- if (!fs.existsSync(settingsPath)) return false;
84
-
85
- let settings;
86
- try {
87
- settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
88
- } catch (_) {
89
- console.log(` No se pudieron remover hooks: ${ideDir}/settings.json invalido.`);
90
- return false;
91
- }
92
-
93
- if (!settings.hooks) return false;
94
-
95
- let changed = false;
96
-
97
- if (Array.isArray(settings.hooks.SessionStart)) {
98
- const original = settings.hooks.SessionStart.length;
99
- settings.hooks.SessionStart = settings.hooks.SessionStart.filter((h) => h._sdd !== true);
100
- if (settings.hooks.SessionStart.length !== original) changed = true;
101
- if (settings.hooks.SessionStart.length === 0) delete settings.hooks.SessionStart;
102
- }
103
-
104
- for (const notifyEvent of ['Stop', 'afterAgentResponse']) {
105
- if (Array.isArray(settings.hooks[notifyEvent])) {
106
- const original = settings.hooks[notifyEvent].length;
107
- settings.hooks[notifyEvent] = settings.hooks[notifyEvent].filter((h) => h._sdd_notify !== true);
108
- if (settings.hooks[notifyEvent].length !== original) changed = true;
109
- if (settings.hooks[notifyEvent].length === 0) delete settings.hooks[notifyEvent];
110
- }
111
- }
112
-
113
- if (Array.isArray(settings.hooks.PreToolUse)) {
114
- const original = settings.hooks.PreToolUse.length;
115
- settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
116
- (h) => h._sdd_review !== true && h._sdd_compact !== true,
117
- );
118
- if (settings.hooks.PreToolUse.length !== original) changed = true;
119
- if (settings.hooks.PreToolUse.length === 0) delete settings.hooks.PreToolUse;
120
- }
121
-
122
- if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
123
-
124
- if (!changed) return false;
125
-
126
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
127
- return true;
128
- }
129
-
130
- module.exports = { installHooks, uninstallHooks };
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
+ const config = readCursorHooksJson(projectRoot);
28
+ let changed = false;
29
+
30
+ function ensure(event, marker, entry) {
31
+ if (!config.hooks[event]) config.hooks[event] = [];
32
+ if (!config.hooks[event].some((h) => h[marker] === true)) {
33
+ config.hooks[event].push(entry);
34
+ changed = true;
35
+ }
36
+ }
37
+
38
+ ensure('sessionStart', '_sdd', {
39
+ _sdd: true,
40
+ command: 'refacil-sdd-ai check-update',
41
+ });
42
+
43
+ // compact-bash debe ir ANTES de check-review
44
+ if (!config.hooks.preToolUse) config.hooks.preToolUse = [];
45
+ if (!config.hooks.preToolUse.some((h) => h._sdd_compact === true)) {
46
+ config.hooks.preToolUse.unshift({ _sdd_compact: true, command: 'refacil-sdd-ai compact-bash', matcher: 'Bash' });
47
+ changed = true;
48
+ }
49
+ if (!config.hooks.preToolUse.some((h) => h._sdd_review === true)) {
50
+ config.hooks.preToolUse.push({ _sdd_review: true, command: 'refacil-sdd-ai check-review', matcher: 'Bash' });
51
+ changed = true;
52
+ }
53
+
54
+ ensure('beforeSubmitPrompt', '_sdd_notify', {
55
+ _sdd_notify: true,
56
+ command: 'refacil-sdd-ai notify-update',
57
+ });
58
+
59
+ if (!changed) {
60
+ console.log(' Hooks SDD-AI ya configurados en .cursor/hooks.json.');
61
+ return false;
62
+ }
63
+
64
+ writeCursorHooksJson(projectRoot, config);
65
+ return true;
66
+ }
67
+
68
+ function uninstallCursorHooks(projectRoot) {
69
+ const hooksJsonPath = path.join(projectRoot, '.cursor', 'hooks.json');
70
+ let changed = false;
71
+
72
+ if (fs.existsSync(hooksJsonPath)) {
73
+ let config;
74
+ try { config = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8')); } catch (_) { config = null; }
75
+
76
+ if (config && config.hooks) {
77
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
78
+ for (const event of Object.keys(config.hooks)) {
79
+ if (!Array.isArray(config.hooks[event])) continue;
80
+ const before = config.hooks[event].length;
81
+ config.hooks[event] = config.hooks[event].filter(
82
+ (h) => !sddMarkers.some((m) => h[m] === true),
83
+ );
84
+ if (config.hooks[event].length !== before) changed = true;
85
+ if (config.hooks[event].length === 0) delete config.hooks[event];
86
+ }
87
+ if (changed) writeCursorHooksJson(projectRoot, config);
88
+ }
89
+ }
90
+
91
+ // Limpiar vestigios en settings.json de instalaciones previas
92
+ const settingsPath = path.join(projectRoot, '.cursor', 'settings.json');
93
+ if (fs.existsSync(settingsPath)) {
94
+ let settings;
95
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { settings = null; }
96
+ if (settings && settings.hooks) {
97
+ const evts = ['SessionStart', 'PreToolUse', 'UserPromptSubmit', 'beforeSubmitPrompt', 'Stop', 'afterAgentResponse'];
98
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
99
+ for (const evt of evts) {
100
+ if (!Array.isArray(settings.hooks[evt])) continue;
101
+ const before = settings.hooks[evt].length;
102
+ settings.hooks[evt] = settings.hooks[evt].filter((h) => !sddMarkers.some((m) => h[m] === true));
103
+ if (settings.hooks[evt].length !== before) changed = true;
104
+ if (settings.hooks[evt].length === 0) delete settings.hooks[evt];
105
+ }
106
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
107
+ if (changed) fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
108
+ }
109
+ }
110
+
111
+ return changed;
112
+ }
113
+
114
+ // ── Claude Code: todos los hooks en .claude/settings.json ───────────────────
115
+
116
+ function installClaudeHooks(projectRoot) {
117
+ const settingsDir = path.join(projectRoot, '.claude');
118
+ const settingsPath = path.join(settingsDir, 'settings.json');
119
+ let settings = {};
120
+
121
+ if (fs.existsSync(settingsPath)) {
122
+ try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (_) { settings = {}; }
123
+ }
124
+
125
+ if (!settings.hooks) settings.hooks = {};
126
+ let changed = false;
127
+
128
+ if (!settings.hooks.SessionStart) settings.hooks.SessionStart = [];
129
+ if (!settings.hooks.SessionStart.some((h) => h._sdd === true)) {
130
+ settings.hooks.SessionStart.push({ _sdd: true, matcher: '', hooks: [{ type: 'command', command: 'refacil-sdd-ai check-update' }] });
131
+ changed = true;
132
+ }
133
+
134
+ if (!settings.hooks.UserPromptSubmit) settings.hooks.UserPromptSubmit = [];
135
+ if (!settings.hooks.UserPromptSubmit.some((h) => h._sdd_notify === true)) {
136
+ settings.hooks.UserPromptSubmit.push({ _sdd_notify: true, matcher: '', hooks: [{ type: 'command', command: 'refacil-sdd-ai notify-update' }] });
137
+ changed = true;
138
+ }
139
+
140
+ if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
141
+ if (!settings.hooks.PreToolUse.some((h) => h._sdd_compact === true)) {
142
+ settings.hooks.PreToolUse.unshift({ _sdd_compact: true, matcher: 'Bash', hooks: [{ type: 'command', command: 'refacil-sdd-ai compact-bash' }] });
143
+ changed = true;
144
+ }
145
+ if (!settings.hooks.PreToolUse.some((h) => h._sdd_review === true)) {
146
+ settings.hooks.PreToolUse.push({ _sdd_review: true, matcher: 'Bash', hooks: [{ type: 'command', command: 'refacil-sdd-ai check-review' }] });
147
+ changed = true;
148
+ }
149
+
150
+ if (!changed) {
151
+ console.log(' Hooks SDD-AI ya configurados en .claude/settings.json.');
152
+ return false;
153
+ }
154
+
155
+ fs.mkdirSync(settingsDir, { recursive: true });
156
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
157
+ return true;
158
+ }
159
+
160
+ function uninstallClaudeHooks(projectRoot) {
161
+ const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
162
+ if (!fs.existsSync(settingsPath)) return false;
163
+
164
+ let settings;
165
+ try {
166
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
167
+ } catch (_) {
168
+ console.log(' No se pudieron remover hooks: .claude/settings.json invalido.');
169
+ return false;
170
+ }
171
+
172
+ if (!settings.hooks) return false;
173
+
174
+ let changed = false;
175
+ const sddMarkers = ['_sdd', '_sdd_compact', '_sdd_review', '_sdd_notify'];
176
+
177
+ for (const event of Object.keys(settings.hooks)) {
178
+ if (!Array.isArray(settings.hooks[event])) continue;
179
+ const before = settings.hooks[event].length;
180
+ settings.hooks[event] = settings.hooks[event].filter((h) => !sddMarkers.some((m) => h[m] === true));
181
+ if (settings.hooks[event].length !== before) changed = true;
182
+ if (settings.hooks[event].length === 0) delete settings.hooks[event];
183
+ }
184
+
185
+ if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
186
+ if (!changed) return false;
187
+
188
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
189
+ return true;
190
+ }
191
+
192
+ // ── Fachada pública ──────────────────────────────────────────────────────────
193
+
194
+ function installHooks(ideDir, projectRoot) {
195
+ return ideDir === '.cursor'
196
+ ? installCursorHooks(projectRoot)
197
+ : installClaudeHooks(projectRoot);
198
+ }
199
+
200
+ function uninstallHooks(ideDir, projectRoot) {
201
+ return ideDir === '.cursor'
202
+ ? uninstallCursorHooks(projectRoot)
203
+ : uninstallClaudeHooks(projectRoot);
204
+ }
205
+
206
+ module.exports = { installHooks, uninstallHooks };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refacil-sdd-ai",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "description": "SDD-AI: Specification-Driven Development with AI — metodologia de desarrollo con IA usando OpenSpec, Claude Code y Cursor",
5
5
  "bin": {
6
6
  "refacil-sdd-ai": "./bin/cli.js"
@@ -94,7 +94,15 @@ Conjunto requerido de commands: `.claude/commands/opsx/` → `apply.md`, `archiv
94
94
  4. Elimina skills y commands sobrantes.
95
95
  5. Ejecuta `openspec init --tools claude,cursor` para dejar el estado limpio.
96
96
 
97
- ## Paso 4: Resumen
97
+ ## Paso 4: Limpiar flag de actualizacion pendiente
98
+
99
+ Eliminar el archivo de flag si existe (puede estar en `.claude/` o `.cursor/`):
100
+
101
+ ```bash
102
+ rm -f .claude/.refacil-pending-update .cursor/.refacil-pending-update
103
+ ```
104
+
105
+ ## Paso 5: Resumen
98
106
 
99
107
  Informar que archivos se crearon o modificaron. Mencionar que el bloque `compact-guidance` se sincronizara automaticamente en el proximo SessionStart.
100
108