refacil-sdd-ai 4.0.4 → 4.0.6
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/bin/cli.js +21 -7
- package/lib/hooks.js +206 -130
- package/package.json +1 -1
- package/skills/update/SKILL.md +9 -1
package/bin/cli.js
CHANGED
|
@@ -53,14 +53,28 @@ 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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
const fromLabel = info.from ? `v${info.from}` : 'version anterior';
|
|
58
|
+
|
|
59
|
+
if (!info.shown) {
|
|
60
|
+
// Primera vez: bloquear al usuario para que lo vea claramente
|
|
61
|
+
try { fs.writeFileSync(flagPath, JSON.stringify({ ...info, shown: true })); } catch (_) {}
|
|
62
|
+
const msg =
|
|
63
|
+
`[refacil-sdd-ai] Actualizacion de metodologia detectada (${fromLabel} → v${info.to}).\n` +
|
|
64
|
+
`Tu mensaje fue pausado. ¿Deseas aplicar las migraciones pendientes antes de continuar?\n` +
|
|
65
|
+
` • Si: ejecuta /refacil:update\n` +
|
|
66
|
+
` • No: reenvía tu mensaje anterior para continuar sin actualizar.`;
|
|
67
|
+
process.stdout.write(msg + '\n');
|
|
68
|
+
process.stderr.write(msg + '\n');
|
|
69
|
+
process.exit(2);
|
|
70
|
+
} else {
|
|
71
|
+
// Segunda vez: dejar pasar pero avisar al LLM para que lo gestione
|
|
72
|
+
try { fs.unlinkSync(flagPath); } catch (_) {}
|
|
73
|
+
console.log(
|
|
74
|
+
`[refacil-sdd-ai] IMPORTANTE: hay una actualizacion de la metodologia SDD-AI pendiente (${fromLabel} → v${info.to}). ` +
|
|
75
|
+
`Antes de responder al usuario, preguntale si desea ejecutar /refacil:update ahora.`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
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
|
|