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 +3 -3
- package/bin/cli.js +9 -6
- package/lib/hooks.js +206 -130
- package/package.json +1 -1
- package/skills/update/SKILL.md +9 -1
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` | (`
|
|
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` | `
|
|
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 `
|
|
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
|
|
59
|
-
|
|
60
|
-
`[refacil-sdd-ai]
|
|
61
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
package/skills/update/SKILL.md
CHANGED
|
@@ -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:
|
|
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
|
|