ll-skills 1.0.0 → 2.0.2
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/CHANGELOG.md +90 -2
- package/README.md +134 -69
- package/agents/ll-executor.md +96 -0
- package/agents/ll-reviewer.md +76 -0
- package/agents/ll-scout.md +83 -0
- package/agents/ll-verifier.md +127 -0
- package/assets/preamble.md +70 -0
- package/assets/settings.suggested.json +19 -0
- package/bin/install.js +311 -54
- package/hooks/ll-precompact.js +49 -0
- package/hooks/ll-skills-check-update.js +1 -1
- package/hooks/ll-state.js +158 -0
- package/package.json +4 -2
- package/scripts/fixtures/empty/.gitkeep +0 -0
- package/scripts/fixtures/git-history.sh +49 -0
- package/scripts/fixtures/project/BACKLOG.md +11 -0
- package/scripts/fixtures/project/PLAN.md +66 -0
- package/scripts/fixtures/project/PROGRESS.md +57 -0
- package/scripts/fixtures/project/ROADMAP.md +28 -0
- package/scripts/fixtures/project/VERIFICATION.md +17 -0
- package/scripts/fixtures/project/decisions/DEC-0041-cents.md +16 -0
- package/scripts/fixtures/project/phases/07/PLAN.md +145 -0
- package/scripts/fixtures/project/src/a.ts +3 -0
- package/scripts/fixtures/project/src/pay.ts +5 -0
- package/scripts/fixtures/project/test/a.test.ts +5 -0
- package/scripts/ll-tools.js +664 -0
- package/scripts/smoke-test.sh +340 -0
- package/skills/ll-brainstorm/SKILL.md +180 -0
- package/skills/ll-brainstorm/references/decision-policy.md +118 -0
- package/skills/ll-brainstorm/references/techniques.md +75 -0
- package/skills/ll-close/SKILL.md +69 -0
- package/skills/ll-close/references/delivery.md +75 -0
- package/skills/ll-close/references/retrospective.md +48 -0
- package/skills/ll-decide/SKILL.md +135 -0
- package/skills/ll-decide/references/decision-policy.md +118 -0
- package/skills/ll-decide/references/decision-room.md +62 -0
- package/skills/ll-decide/references/disarm.md +79 -0
- package/skills/ll-decide/references/feedback-ingestion.md +90 -0
- package/skills/ll-decide/references/interview.md +102 -0
- package/skills/ll-decide/references/plan-skeleton.md +150 -0
- package/skills/ll-decide/references/premise-gate.md +83 -0
- package/skills/ll-decide/references/premortem.md +90 -0
- package/skills/ll-decide/references/review-spec.md +91 -0
- package/skills/ll-goal/SKILL.md +64 -0
- package/skills/ll-goal/references/goal-template.md +90 -0
- package/skills/ll-implement/SKILL.md +118 -0
- package/skills/ll-implement/references/briefs.md +123 -0
- package/skills/ll-implement/references/decision-policy.md +118 -0
- package/skills/ll-implement/references/phase-conversation.md +76 -0
- package/skills/ll-implement/references/phase-plan.md +111 -0
- package/skills/ll-oncall/SKILL.md +123 -0
- package/skills/ll-oncall/references/deploy-preflight.md +57 -0
- package/skills/ll-oncall/references/federation.md +110 -0
- package/skills/ll-oncall/references/watch-brief.md +39 -0
- package/skills/ll-refine/SKILL.md +116 -0
- package/skills/ll-refine/references/production-access.md +48 -0
- package/skills/ll-refine/references/visual-gate.md +100 -0
- package/skills/ll-research/SKILL.md +87 -0
- package/skills/ll-research/references/citation-check.md +38 -0
- package/skills/ll-research/references/front-brief.md +41 -0
- package/skills/ll-research/references/market-mode.md +83 -0
- package/skills/ll-resume/SKILL.md +75 -0
- package/skills/ll-update/SKILL.md +75 -0
- package/skills/ll-verify/SKILL.md +75 -0
- package/skills/ll-verify/references/verifier-briefs.md +116 -0
- package/agents/ll-implementador.md +0 -23
- package/skills/ll-atualizar/SKILL.md +0 -68
- package/skills/ll-decidir-antes/SKILL.md +0 -81
- package/skills/ll-decidir-antes/referencias/protocolo-entrevista.md +0 -112
- package/skills/ll-decidir-antes/referencias/template-spec.md +0 -238
- package/skills/ll-desarmar/SKILL.md +0 -254
- package/skills/ll-desarmar/referencias/execucao-adversarial.md +0 -217
- package/skills/ll-desarmar/referencias/humanos-e-substitutos.md +0 -116
- package/skills/ll-desarmar/referencias/placar-e-realimentacao.md +0 -140
- package/skills/ll-orquestrar/SKILL.md +0 -100
- package/skills/ll-pesquisar/SKILL.md +0 -159
- package/skills/ll-pesquisar/referencias/frente-de-pesquisa.md +0 -147
- package/skills/ll-pesquisar/referencias/sintese-e-fontes.md +0 -148
- package/skills/ll-pesquisar-mercado/SKILL.md +0 -112
- package/skills/ll-pesquisar-mercado/referencias/dossie.md +0 -375
- package/skills/ll-pesquisar-mercado/referencias/indice-e-fechamento.md +0 -122
- package/skills/ll-pesquisar-mercado/referencias/padroes-de-pesquisa.md +0 -149
- package/skills/ll-verificar-entrega/SKILL.md +0 -73
- package/skills/ll-verificar-entrega/referencias/briefs-auditoria.md +0 -291
- package/skills/ll-voltar-do-futuro/SKILL.md +0 -239
- package/skills/ll-voltar-do-futuro/referencias/anti-padroes-e-fundamentos.md +0 -201
- package/skills/ll-voltar-do-futuro/referencias/vetores-e-testes.md +0 -228
package/bin/install.js
CHANGED
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
|
|
4
4
|
// Instalador do ll-skills.
|
|
5
5
|
//
|
|
6
|
-
// npx ll-skills@latest
|
|
7
|
-
// npx ll-skills@latest --local
|
|
6
|
+
// npx ll-skills@latest instala/atualiza em ~/.claude (ou $CLAUDE_CONFIG_DIR)
|
|
7
|
+
// npx ll-skills@latest --local instala em ./.claude do diretório atual
|
|
8
|
+
// npx ll-skills@latest --no-settings só imprime os hooks, não toca em settings.json
|
|
9
|
+
// npx ll-skills@latest --no-preamble não escreve o bloco do preâmbulo no CLAUDE.md
|
|
10
|
+
// npx ll-skills@latest --yes aprova o preâmbulo sem perguntar
|
|
8
11
|
// npx ll-skills@latest --uninstall
|
|
9
12
|
//
|
|
10
|
-
// Copia skills/ll-*, agents/ll
|
|
11
|
-
// de configuração do Claude Code, registra
|
|
13
|
+
// Copia skills/ll-*, agents/ll-*, os 3 hooks e o helper ll-tools.js para o diretório
|
|
14
|
+
// de configuração do Claude Code, registra os hooks em settings.json (SessionStart +
|
|
15
|
+
// PreCompact), escreve o bloco do preâmbulo no CLAUDE.md entre marcadores, grava
|
|
12
16
|
// VERSION + manifesto com sha256 por arquivo e limpa o que ficou de instalações
|
|
13
|
-
// anteriores (plugin legado por marketplace, arquivos órfãos, cache antigo).
|
|
17
|
+
// anteriores (plugin legado por marketplace, skills 1.x, arquivos órfãos, cache antigo).
|
|
14
18
|
//
|
|
15
|
-
// Sem dependências
|
|
16
|
-
// nunca ao cwd.
|
|
19
|
+
// Sem dependências. Tudo relativo ao próprio pacote (__dirname), nunca ao cwd.
|
|
17
20
|
|
|
18
21
|
const fs = require('fs');
|
|
19
22
|
const path = require('path');
|
|
@@ -27,9 +30,42 @@ const PKG_NAME = PKG.name;
|
|
|
27
30
|
const REPO_URL = 'https://github.com/allangdy/ll-skills.git';
|
|
28
31
|
const LEGACY_PLUGIN_ID = 'll-skills@ll-skills';
|
|
29
32
|
const LEGACY_MARKETPLACE = 'll-skills';
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
+
|
|
34
|
+
// hooks registrados em settings.json
|
|
35
|
+
const SESSION_HOOKS = [
|
|
36
|
+
{ file: 'll-skills-check-update.js', timeout: 5 },
|
|
37
|
+
{ file: 'll-state.js', timeout: 3 },
|
|
38
|
+
];
|
|
39
|
+
const PRECOMPACT_HOOK = { file: 'll-precompact.js', timeout: 3 };
|
|
40
|
+
const SESSION_MATCHER = 'startup|resume|compact';
|
|
41
|
+
const HOOK_EVENTS = ['SessionStart', 'PreCompact'];
|
|
42
|
+
const OWN_HOOK_FILES = SESSION_HOOKS.map((h) => h.file).concat([PRECOMPACT_HOOK.file]);
|
|
43
|
+
|
|
44
|
+
// helper copiado para dentro das skills que o usam (caminho relativo com --local quebraria require)
|
|
45
|
+
const HELPER_SKILLS = ['ll-implement', 'll-verify', 'll-close'];
|
|
46
|
+
const HELPER_SRC = path.join(PKG_ROOT, 'scripts', 'll-tools.js');
|
|
47
|
+
|
|
48
|
+
// preâmbulo
|
|
49
|
+
const PREAMBLE_SRC = path.join(PKG_ROOT, 'assets', 'preamble.md');
|
|
50
|
+
const PREAMBLE_OPEN = '<!-- ll-skills:preamble v1 -->';
|
|
51
|
+
const PREAMBLE_CLOSE = '<!-- /ll-skills:preamble -->';
|
|
52
|
+
const POLICY_SRC = path.join(PKG_ROOT, 'assets', 'settings.suggested.json');
|
|
53
|
+
|
|
54
|
+
const PREFIXOS_PROPRIOS = ['skills/ll-', 'agents/ll-', 'hooks/ll-', 'll-skills/'];
|
|
55
|
+
|
|
56
|
+
// removidos mesmo sem manifesto (instalações 1.x e cópias manuais)
|
|
57
|
+
const KNOWN_LEGACY = [
|
|
58
|
+
'skills/ll-atualizar',
|
|
59
|
+
'skills/ll-decidir-antes',
|
|
60
|
+
'skills/ll-desarmar',
|
|
61
|
+
'skills/ll-orquestrar',
|
|
62
|
+
'skills/ll-pesquisar',
|
|
63
|
+
'skills/ll-pesquisar-mercado',
|
|
64
|
+
'skills/ll-verificar-entrega',
|
|
65
|
+
'skills/ll-voltar-do-futuro',
|
|
66
|
+
'agents/ll-implementador.md',
|
|
67
|
+
];
|
|
68
|
+
const LEGACY_BASENAMES = new Set(KNOWN_LEGACY.map((r) => r.split('/')[1]));
|
|
33
69
|
|
|
34
70
|
const CACHE_DIR = path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'), 'll-skills');
|
|
35
71
|
|
|
@@ -47,10 +83,13 @@ function fail(msg, code = 1) {
|
|
|
47
83
|
}
|
|
48
84
|
|
|
49
85
|
function parseArgs(argv) {
|
|
50
|
-
const args = { uninstall: false, local: false, help: false };
|
|
86
|
+
const args = { uninstall: false, local: false, help: false, settings: true, preamble: true, yes: false };
|
|
51
87
|
for (const a of argv) {
|
|
52
88
|
if (a === '--uninstall') args.uninstall = true;
|
|
53
89
|
else if (a === '--local') args.local = true;
|
|
90
|
+
else if (a === '--no-settings') args.settings = false;
|
|
91
|
+
else if (a === '--no-preamble') args.preamble = false;
|
|
92
|
+
else if (a === '--yes' || a === '-y') args.yes = true;
|
|
54
93
|
else if (a === '--help' || a === '-h') args.help = true;
|
|
55
94
|
else fail(`argumento desconhecido: ${a} (use --help)`);
|
|
56
95
|
}
|
|
@@ -61,9 +100,12 @@ function usage() {
|
|
|
61
100
|
log(`ll-skills ${PKG.version}
|
|
62
101
|
|
|
63
102
|
Uso:
|
|
64
|
-
npx ll-skills@latest
|
|
65
|
-
npx ll-skills@latest --local
|
|
66
|
-
npx ll-skills@latest --
|
|
103
|
+
npx ll-skills@latest instala ou atualiza em $CLAUDE_CONFIG_DIR ou ~/.claude
|
|
104
|
+
npx ll-skills@latest --local instala em ./.claude (só este projeto)
|
|
105
|
+
npx ll-skills@latest --no-settings não toca em settings.json; imprime os hooks
|
|
106
|
+
npx ll-skills@latest --no-preamble não escreve o bloco do preâmbulo no CLAUDE.md
|
|
107
|
+
npx ll-skills@latest --yes aprova o preâmbulo sem perguntar (-y)
|
|
108
|
+
npx ll-skills@latest --uninstall remove tudo que este pacote instalou
|
|
67
109
|
npx ll-skills@latest --help`);
|
|
68
110
|
}
|
|
69
111
|
|
|
@@ -93,6 +135,14 @@ function readJson(file, fallback) {
|
|
|
93
135
|
}
|
|
94
136
|
}
|
|
95
137
|
|
|
138
|
+
function readText(file) {
|
|
139
|
+
try {
|
|
140
|
+
return fs.readFileSync(file, 'utf8');
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
96
146
|
function writeJsonAtomic(file, obj) {
|
|
97
147
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
98
148
|
const tmp = `${file}.tmp.${process.pid}`;
|
|
@@ -100,6 +150,13 @@ function writeJsonAtomic(file, obj) {
|
|
|
100
150
|
fs.renameSync(tmp, file);
|
|
101
151
|
}
|
|
102
152
|
|
|
153
|
+
function writeTextAtomic(file, text) {
|
|
154
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
155
|
+
const tmp = `${file}.tmp.${process.pid}`;
|
|
156
|
+
fs.writeFileSync(tmp, text);
|
|
157
|
+
fs.renameSync(tmp, file);
|
|
158
|
+
}
|
|
159
|
+
|
|
103
160
|
function insideDir(base, target) {
|
|
104
161
|
const rel = path.relative(base, target);
|
|
105
162
|
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
@@ -118,12 +175,12 @@ function walk(dir, base = dir, out = []) {
|
|
|
118
175
|
return out;
|
|
119
176
|
}
|
|
120
177
|
|
|
121
|
-
// Lista de arquivos do pacote a instalar: [{ src, rel }], rel relativo ao configDir.
|
|
178
|
+
// Lista de arquivos do pacote a instalar: [{ src, rel, mode }], rel relativo ao configDir.
|
|
122
179
|
function planFiles(pkgRoot) {
|
|
123
180
|
const plan = [];
|
|
124
181
|
const skillsDir = path.join(pkgRoot, 'skills');
|
|
125
182
|
for (const name of fs.readdirSync(skillsDir)) {
|
|
126
|
-
if (!name.startsWith('ll-')) continue;
|
|
183
|
+
if (!name.startsWith('ll-') || LEGACY_BASENAMES.has(name)) continue;
|
|
127
184
|
const dir = path.join(skillsDir, name);
|
|
128
185
|
if (!fs.statSync(dir).isDirectory()) continue;
|
|
129
186
|
for (const rel of walk(dir)) {
|
|
@@ -133,7 +190,7 @@ function planFiles(pkgRoot) {
|
|
|
133
190
|
const agentsDir = path.join(pkgRoot, 'agents');
|
|
134
191
|
if (fs.existsSync(agentsDir)) {
|
|
135
192
|
for (const name of fs.readdirSync(agentsDir)) {
|
|
136
|
-
if (name.startsWith('ll-') && name.endsWith('.md')) {
|
|
193
|
+
if (name.startsWith('ll-') && name.endsWith('.md') && !LEGACY_BASENAMES.has(name)) {
|
|
137
194
|
plan.push({ src: path.join(agentsDir, name), rel: `agents/${name}` });
|
|
138
195
|
}
|
|
139
196
|
}
|
|
@@ -141,11 +198,20 @@ function planFiles(pkgRoot) {
|
|
|
141
198
|
const hooksDir = path.join(pkgRoot, 'hooks');
|
|
142
199
|
if (fs.existsSync(hooksDir)) {
|
|
143
200
|
for (const name of fs.readdirSync(hooksDir)) {
|
|
144
|
-
if (name.startsWith('ll-
|
|
201
|
+
if (name.startsWith('ll-') && name.endsWith('.js')) {
|
|
145
202
|
plan.push({ src: path.join(hooksDir, name), rel: `hooks/${name}`, mode: 0o755 });
|
|
146
203
|
}
|
|
147
204
|
}
|
|
148
205
|
}
|
|
206
|
+
if (fs.existsSync(HELPER_SRC)) {
|
|
207
|
+
const installed = new Set(plan.map((p) => p.rel.split('/')[1]));
|
|
208
|
+
for (const name of HELPER_SKILLS) {
|
|
209
|
+
if (!installed.has(name)) continue;
|
|
210
|
+
plan.push({ src: HELPER_SRC, rel: `skills/${name}/scripts/ll-tools.js`, mode: 0o755 });
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
log(`• AVISO: ${HELPER_SRC} não existe no pacote; as skills ficam sem o helper ll-tools.js`);
|
|
214
|
+
}
|
|
149
215
|
return plan;
|
|
150
216
|
}
|
|
151
217
|
|
|
@@ -260,6 +326,21 @@ function removeManagedFile(configDir, rel) {
|
|
|
260
326
|
return true;
|
|
261
327
|
}
|
|
262
328
|
|
|
329
|
+
// Remove um caminho gerenciado que pode ser diretório (skills 1.x) ou arquivo.
|
|
330
|
+
function removeManagedPath(configDir, rel) {
|
|
331
|
+
if (!isOwnRel(rel)) return false;
|
|
332
|
+
const full = path.resolve(configDir, rel);
|
|
333
|
+
if (!insideDir(configDir, full)) return false;
|
|
334
|
+
if (!fs.existsSync(full)) return false;
|
|
335
|
+
try {
|
|
336
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
removeEmptyDirsUpTo(path.dirname(full), configDir);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
263
344
|
function pruneStale(configDir, oldManifest, newFiles) {
|
|
264
345
|
const removed = [];
|
|
265
346
|
const old = (oldManifest && oldManifest.files) || {};
|
|
@@ -267,6 +348,10 @@ function pruneStale(configDir, oldManifest, newFiles) {
|
|
|
267
348
|
if (rel in newFiles) continue;
|
|
268
349
|
if (removeManagedFile(configDir, rel)) removed.push(rel);
|
|
269
350
|
}
|
|
351
|
+
for (const rel of KNOWN_LEGACY) {
|
|
352
|
+
if (rel in newFiles) continue;
|
|
353
|
+
if (removeManagedPath(configDir, rel)) removed.push(rel);
|
|
354
|
+
}
|
|
270
355
|
return removed;
|
|
271
356
|
}
|
|
272
357
|
|
|
@@ -303,29 +388,37 @@ function clearOldCache() {
|
|
|
303
388
|
// settings.json
|
|
304
389
|
// ---------------------------------------------------------------------------
|
|
305
390
|
|
|
306
|
-
function hookCommand(configDir) {
|
|
307
|
-
const hookPath = path.join(configDir, 'hooks',
|
|
391
|
+
function hookCommand(configDir, file) {
|
|
392
|
+
const hookPath = path.join(configDir, 'hooks', file);
|
|
308
393
|
// `node` bare: com nvm, um caminho absoluto para o binário quebra no próximo `nvm install`.
|
|
309
394
|
return `command -v node >/dev/null 2>&1 && node "${hookPath}" || true`;
|
|
310
395
|
}
|
|
311
396
|
|
|
312
|
-
function
|
|
397
|
+
function buildHookEntries(configDir) {
|
|
313
398
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
399
|
+
SessionStart: {
|
|
400
|
+
matcher: SESSION_MATCHER,
|
|
401
|
+
hooks: SESSION_HOOKS.map((h) => ({ type: 'command', command: hookCommand(configDir, h.file), timeout: h.timeout })),
|
|
402
|
+
},
|
|
403
|
+
PreCompact: {
|
|
404
|
+
hooks: [{ type: 'command', command: hookCommand(configDir, PRECOMPACT_HOOK.file), timeout: PRECOMPACT_HOOK.timeout }],
|
|
405
|
+
},
|
|
316
406
|
};
|
|
317
407
|
}
|
|
318
408
|
|
|
319
409
|
function isOwnHook(h) {
|
|
320
|
-
return Boolean(h && typeof h.command === 'string' && h.command.includes(
|
|
410
|
+
return Boolean(h && typeof h.command === 'string' && OWN_HOOK_FILES.some((f) => h.command.includes(f)));
|
|
321
411
|
}
|
|
322
412
|
|
|
323
413
|
function stripOwnHooks(settings) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
.
|
|
414
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') return;
|
|
415
|
+
for (const ev of HOOK_EVENTS) {
|
|
416
|
+
const list = settings.hooks[ev];
|
|
417
|
+
if (!Array.isArray(list)) continue;
|
|
418
|
+
settings.hooks[ev] = list
|
|
419
|
+
.map((e) => (e && Array.isArray(e.hooks) ? { ...e, hooks: e.hooks.filter((h) => !isOwnHook(h)) } : e))
|
|
420
|
+
.filter((e) => !(e && Array.isArray(e.hooks) && e.hooks.length === 0));
|
|
421
|
+
}
|
|
329
422
|
}
|
|
330
423
|
|
|
331
424
|
function stripLegacyPluginKeys(settings) {
|
|
@@ -340,10 +433,11 @@ function stripLegacyPluginKeys(settings) {
|
|
|
340
433
|
}
|
|
341
434
|
|
|
342
435
|
function pruneEmptyHooks(settings) {
|
|
343
|
-
if (settings.hooks
|
|
344
|
-
|
|
436
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') return;
|
|
437
|
+
for (const ev of HOOK_EVENTS) {
|
|
438
|
+
if (Array.isArray(settings.hooks[ev]) && settings.hooks[ev].length === 0) delete settings.hooks[ev];
|
|
345
439
|
}
|
|
346
|
-
if (
|
|
440
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
347
441
|
}
|
|
348
442
|
|
|
349
443
|
// Lê, aplica `mutate(settings)`, grava só se mudou. Retorna 'unchanged' | 'written' | 'invalid'.
|
|
@@ -359,32 +453,170 @@ function updateSettings(configDir, mutate) {
|
|
|
359
453
|
return 'written';
|
|
360
454
|
}
|
|
361
455
|
|
|
362
|
-
|
|
456
|
+
// Registra os 3 hooks (SessionStart + PreCompact) numa única mutação.
|
|
457
|
+
function registerHooks(configDir) {
|
|
458
|
+
const entries = buildHookEntries(configDir);
|
|
363
459
|
return updateSettings(configDir, (s) => {
|
|
364
460
|
if (!s.hooks || typeof s.hooks !== 'object') s.hooks = {};
|
|
365
|
-
if (!Array.isArray(s.hooks.SessionStart)) s.hooks.SessionStart = [];
|
|
366
461
|
stripOwnHooks(s);
|
|
367
|
-
|
|
462
|
+
for (const ev of HOOK_EVENTS) {
|
|
463
|
+
if (!Array.isArray(s.hooks[ev])) s.hooks[ev] = [];
|
|
464
|
+
s.hooks[ev].push(entries[ev]);
|
|
465
|
+
}
|
|
368
466
|
stripLegacyPluginKeys(s);
|
|
369
467
|
});
|
|
370
468
|
}
|
|
371
469
|
|
|
372
|
-
function
|
|
470
|
+
function unregisterHooks(configDir) {
|
|
373
471
|
return updateSettings(configDir, (s) => {
|
|
374
472
|
stripOwnHooks(s);
|
|
375
473
|
pruneEmptyHooks(s);
|
|
376
474
|
});
|
|
377
475
|
}
|
|
378
476
|
|
|
379
|
-
function
|
|
477
|
+
function printHookSnippet(configDir) {
|
|
478
|
+
const entries = buildHookEntries(configDir);
|
|
479
|
+
log('');
|
|
480
|
+
log('Adicione manualmente em "hooks" de ' + path.join(configDir, 'settings.json') + ':');
|
|
481
|
+
log('');
|
|
482
|
+
log(JSON.stringify({ hooks: { SessionStart: [entries.SessionStart], PreCompact: [entries.PreCompact] } }, null, 2));
|
|
380
483
|
log('');
|
|
381
|
-
|
|
382
|
-
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Política sugerida da máquina: sempre impressa, nunca escrita.
|
|
487
|
+
function printPolicy() {
|
|
488
|
+
const raw = readText(POLICY_SRC);
|
|
489
|
+
if (raw === null) return;
|
|
383
490
|
log('');
|
|
384
|
-
log(
|
|
491
|
+
log('Política sugerida (NÃO foi escrita; aplique você mesmo em settings.json):');
|
|
492
|
+
log(raw.trimEnd());
|
|
385
493
|
log('');
|
|
386
494
|
}
|
|
387
495
|
|
|
496
|
+
// Diagnóstico de limpeza da máquina — só imprime, nunca executa.
|
|
497
|
+
function diagnoseCleanup(configDir) {
|
|
498
|
+
const items = [];
|
|
499
|
+
const cache = path.join(os.homedir(), '.claude', 'plugins', 'cache', 'll-skills');
|
|
500
|
+
if (fs.existsSync(cache)) items.push(`rm -rf ${cache} # cache do plugin legado`);
|
|
501
|
+
|
|
502
|
+
if (!items.length) return;
|
|
503
|
+
log('');
|
|
504
|
+
log('Limpeza sugerida (diagnóstico; nada foi executado):');
|
|
505
|
+
for (const i of items) log(` • ${i}`);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// ---------------------------------------------------------------------------
|
|
509
|
+
// preâmbulo no CLAUDE.md
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
|
|
512
|
+
function preambleFile(configDir) {
|
|
513
|
+
return path.join(configDir, 'CLAUDE.md');
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// Texto canônico do bloco, com marcadores, sem newline final.
|
|
517
|
+
function preambleText() {
|
|
518
|
+
const raw = readText(PREAMBLE_SRC);
|
|
519
|
+
if (raw === null) return null;
|
|
520
|
+
const lines = raw.replace(/\n+$/, '').split('\n');
|
|
521
|
+
if (lines[0].trim() !== PREAMBLE_OPEN || lines[lines.length - 1].trim() !== PREAMBLE_CLOSE) return null;
|
|
522
|
+
return lines.join('\n');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Localiza o bloco: null (ausente), {start,end} ou {half:true}.
|
|
526
|
+
function readPreambleBlock(text) {
|
|
527
|
+
const all = text.split('\n');
|
|
528
|
+
let start = -1, end = -1;
|
|
529
|
+
for (let i = 0; i < all.length; i++) {
|
|
530
|
+
if (start < 0 && all[i].trim() === PREAMBLE_OPEN) start = i;
|
|
531
|
+
else if (start >= 0 && all[i].trim() === PREAMBLE_CLOSE) { end = i; break; }
|
|
532
|
+
}
|
|
533
|
+
if (start < 0 && end < 0) return null;
|
|
534
|
+
if (start < 0 || end < 0) return { half: true };
|
|
535
|
+
return { start, end, all };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
function diffLines(oldText, newText) {
|
|
539
|
+
const a = oldText === null ? [] : oldText.split('\n');
|
|
540
|
+
const b = newText.split('\n');
|
|
541
|
+
const setB = new Set(b), setA = new Set(a);
|
|
542
|
+
const out = [];
|
|
543
|
+
for (const l of a) if (!setB.has(l)) out.push('- ' + l);
|
|
544
|
+
for (const l of b) if (!setA.has(l)) out.push('+ ' + l);
|
|
545
|
+
return out;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function askTty(question) {
|
|
549
|
+
let fd = null;
|
|
550
|
+
try {
|
|
551
|
+
fd = fs.openSync('/dev/tty', 'r+');
|
|
552
|
+
fs.writeSync(fd, question);
|
|
553
|
+
const buf = Buffer.alloc(64);
|
|
554
|
+
const n = fs.readSync(fd, buf, 0, buf.length, null);
|
|
555
|
+
return buf.slice(0, n).toString('utf8').trim().toLowerCase();
|
|
556
|
+
} catch {
|
|
557
|
+
return null;
|
|
558
|
+
} finally {
|
|
559
|
+
if (fd !== null) try { fs.closeSync(fd); } catch { /* nada */ }
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 'skipped' | 'unchanged' | 'written' | 'declined' | 'invalid'
|
|
564
|
+
function writePreamble(configDir, opts) {
|
|
565
|
+
if (opts.skip) return 'skipped';
|
|
566
|
+
const block = preambleText();
|
|
567
|
+
if (block === null) return 'invalid';
|
|
568
|
+
|
|
569
|
+
const file = preambleFile(configDir);
|
|
570
|
+
const cur = readText(file);
|
|
571
|
+
let next;
|
|
572
|
+
if (cur === null) {
|
|
573
|
+
next = block + '\n';
|
|
574
|
+
} else {
|
|
575
|
+
const loc = readPreambleBlock(cur);
|
|
576
|
+
if (loc && loc.half) return 'invalid';
|
|
577
|
+
if (loc) {
|
|
578
|
+
const all = loc.all.slice();
|
|
579
|
+
all.splice(loc.start, loc.end - loc.start + 1, ...block.split('\n'));
|
|
580
|
+
next = all.join('\n');
|
|
581
|
+
} else {
|
|
582
|
+
next = cur.replace(/\n*$/, '\n\n') + block + '\n';
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (cur !== null && next === cur) return 'unchanged';
|
|
586
|
+
|
|
587
|
+
if (!opts.yes) {
|
|
588
|
+
const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
589
|
+
const answer = interactive ? askTty(`\nEscrever o bloco do preâmbulo em ${file}? [s/N] `) : null;
|
|
590
|
+
if (!answer || !/^(s|sim|y|yes)$/.test(answer)) {
|
|
591
|
+
log('');
|
|
592
|
+
log(`Preâmbulo NÃO escrito em ${file}. Diff proposto:`);
|
|
593
|
+
for (const l of diffLines(cur, next).slice(0, 80)) log(' ' + l);
|
|
594
|
+
log(' (rode de novo com --yes para aplicar, ou --no-preamble para nunca perguntar)');
|
|
595
|
+
return 'declined';
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (cur !== null) fs.copyFileSync(file, `${file}.ll-skills.bak`);
|
|
599
|
+
writeTextAtomic(file, next);
|
|
600
|
+
return 'written';
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// 'missing' | 'unchanged' | 'written' | 'invalid'
|
|
604
|
+
function stripPreamble(configDir) {
|
|
605
|
+
const file = preambleFile(configDir);
|
|
606
|
+
const cur = readText(file);
|
|
607
|
+
if (cur === null) return 'missing';
|
|
608
|
+
const loc = readPreambleBlock(cur);
|
|
609
|
+
if (!loc) return 'unchanged';
|
|
610
|
+
if (loc.half) return 'invalid';
|
|
611
|
+
const all = loc.all.slice();
|
|
612
|
+
all.splice(loc.start, loc.end - loc.start + 1);
|
|
613
|
+
while (all.length && all[0].trim() === '') all.shift();
|
|
614
|
+
const next = all.join('\n').replace(/\n{3,}/g, '\n\n');
|
|
615
|
+
fs.copyFileSync(file, `${file}.ll-skills.bak`);
|
|
616
|
+
writeTextAtomic(file, next);
|
|
617
|
+
return 'written';
|
|
618
|
+
}
|
|
619
|
+
|
|
388
620
|
// ---------------------------------------------------------------------------
|
|
389
621
|
// fluxos
|
|
390
622
|
// ---------------------------------------------------------------------------
|
|
@@ -407,25 +639,44 @@ function install(args) {
|
|
|
407
639
|
const plan = planFiles(PKG_ROOT);
|
|
408
640
|
const files = copyFiles(configDir, plan);
|
|
409
641
|
const skillNames = [...new Set(Object.keys(files).filter((r) => r.startsWith('skills/')).map((r) => r.split('/')[1]))].sort();
|
|
410
|
-
const agentNames = Object.keys(files).filter((r) => r.startsWith('agents/')).map((r) => path.basename(r, '.md'));
|
|
642
|
+
const agentNames = Object.keys(files).filter((r) => r.startsWith('agents/')).map((r) => path.basename(r, '.md')).sort();
|
|
643
|
+
const hookNames = Object.keys(files).filter((r) => r.startsWith('hooks/')).map((r) => path.basename(r));
|
|
411
644
|
log(`• ${skillNames.length} skills: ${skillNames.join(', ')}`);
|
|
412
|
-
if (agentNames.length) log(`• ${agentNames.length}
|
|
645
|
+
if (agentNames.length) log(`• ${agentNames.length} agentes: ${agentNames.join(', ')}`);
|
|
646
|
+
if (hookNames.length) log(`• ${hookNames.length} hooks: ${hookNames.join(', ')}`);
|
|
413
647
|
|
|
414
|
-
// 4. poda
|
|
648
|
+
// 4. poda (manifesto antigo + skills 1.x conhecidas)
|
|
415
649
|
const pruned = pruneStale(configDir, oldManifest, files);
|
|
416
|
-
if (pruned.length) log(`• ${pruned.length}
|
|
650
|
+
if (pruned.length) log(`• ${pruned.length} caminho(s) de instalação anterior removido(s)`);
|
|
417
651
|
|
|
418
652
|
// 5. estado
|
|
419
653
|
writeState(configDir, files, detectInstallSource(PKG_ROOT));
|
|
420
654
|
|
|
421
|
-
// 6.
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
|
|
655
|
+
// 6. hooks
|
|
656
|
+
let hookResult = 'skipped';
|
|
657
|
+
if (!args.settings) {
|
|
658
|
+
log('• --no-settings: settings.json não foi alterado');
|
|
659
|
+
printHookSnippet(configDir);
|
|
660
|
+
} else {
|
|
661
|
+
hookResult = registerHooks(configDir);
|
|
662
|
+
if (hookResult === 'written') log('• 3 hooks registrados em settings.json (SessionStart, PreCompact)');
|
|
663
|
+
else if (hookResult === 'unchanged') log('• hooks já registrados em settings.json');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// 7. política sugerida (impressa, nunca escrita) + diagnóstico de limpeza
|
|
667
|
+
printPolicy();
|
|
668
|
+
diagnoseCleanup(configDir);
|
|
425
669
|
|
|
426
|
-
//
|
|
670
|
+
// 8. cache antigo
|
|
427
671
|
clearOldCache();
|
|
428
672
|
|
|
673
|
+
// 9. preâmbulo
|
|
674
|
+
const pre = writePreamble(configDir, { yes: args.yes, skip: !args.preamble });
|
|
675
|
+
if (pre === 'written') log(`• preâmbulo escrito em ${preambleFile(configDir)}`);
|
|
676
|
+
else if (pre === 'unchanged') log('• preâmbulo já atualizado');
|
|
677
|
+
else if (pre === 'skipped') log('• --no-preamble: CLAUDE.md não foi tocado');
|
|
678
|
+
else if (pre === 'invalid') log(`• AVISO: bloco do preâmbulo pela metade em ${preambleFile(configDir)}; corrija os marcadores à mão`);
|
|
679
|
+
|
|
429
680
|
log('');
|
|
430
681
|
if (legacy.manual.length) {
|
|
431
682
|
log('Para concluir a remoção do plugin legado, rode dentro do Claude Code ou no terminal:');
|
|
@@ -433,11 +684,12 @@ function install(args) {
|
|
|
433
684
|
log('');
|
|
434
685
|
}
|
|
435
686
|
if (hookResult === 'invalid') {
|
|
436
|
-
|
|
687
|
+
log(`AVISO: ${path.join(configDir, 'settings.json')} não é um JSON válido; não foi alterado.`);
|
|
688
|
+
printHookSnippet(configDir);
|
|
437
689
|
log('Reinicie o Claude Code para que as skills ll-* apareçam.');
|
|
438
690
|
process.exit(1);
|
|
439
691
|
}
|
|
440
|
-
log('Pronto. Reinicie o Claude Code para que as skills ll-* apareçam (ex.: /ll-
|
|
692
|
+
log('Pronto. Reinicie o Claude Code para que as skills ll-* apareçam (ex.: /ll-implement).');
|
|
441
693
|
}
|
|
442
694
|
|
|
443
695
|
function uninstall(args) {
|
|
@@ -448,6 +700,7 @@ function uninstall(args) {
|
|
|
448
700
|
const rels = manifest && manifest.files ? Object.keys(manifest.files) : planFiles(PKG_ROOT).map((p) => p.rel);
|
|
449
701
|
let n = 0;
|
|
450
702
|
for (const rel of rels) if (removeManagedFile(configDir, rel)) n++;
|
|
703
|
+
for (const rel of KNOWN_LEGACY) if (removeManagedPath(configDir, rel)) n++;
|
|
451
704
|
log(`• ${n} arquivo(s) removido(s)`);
|
|
452
705
|
|
|
453
706
|
try {
|
|
@@ -456,9 +709,13 @@ function uninstall(args) {
|
|
|
456
709
|
/* melhor esforço */
|
|
457
710
|
}
|
|
458
711
|
|
|
459
|
-
const hookResult =
|
|
460
|
-
if (hookResult === 'written') log('•
|
|
461
|
-
else if (hookResult === 'invalid') log(
|
|
712
|
+
const hookResult = unregisterHooks(configDir);
|
|
713
|
+
if (hookResult === 'written') log('• hooks removidos de settings.json');
|
|
714
|
+
else if (hookResult === 'invalid') log('• AVISO: settings.json inválido; remova os hooks ll-* manualmente');
|
|
715
|
+
|
|
716
|
+
const pre = stripPreamble(configDir);
|
|
717
|
+
if (pre === 'written') log('• bloco do preâmbulo removido do CLAUDE.md');
|
|
718
|
+
else if (pre === 'invalid') log('• AVISO: bloco do preâmbulo pela metade no CLAUDE.md; remova à mão');
|
|
462
719
|
|
|
463
720
|
clearOldCache();
|
|
464
721
|
log('');
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// ll-precompact.js — ll-skills PreCompact hook. Stamps PROGRESS.md so the
|
|
4
|
+
// post-compaction session knows the disk is the truth. Reads stdin {cwd, trigger}.
|
|
5
|
+
// Silent without PROGRESS.md; any failure ends in silence with exit 0.
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execFileSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
function main() {
|
|
12
|
+
let input = {};
|
|
13
|
+
try { input = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { input = {}; }
|
|
14
|
+
const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();
|
|
15
|
+
const trigger = typeof input.trigger === 'string' && input.trigger ? input.trigger : 'auto';
|
|
16
|
+
|
|
17
|
+
const file = path.join(cwd, 'PROGRESS.md');
|
|
18
|
+
let body;
|
|
19
|
+
try { body = fs.readFileSync(file, 'utf8'); } catch { return; }
|
|
20
|
+
|
|
21
|
+
let phase = 'NN';
|
|
22
|
+
const m = /^<!--\s*ll-state\s*-->\s*$[\s\S]*?^phase:\s*(\S+)\s*$/m.exec(body);
|
|
23
|
+
if (m) phase = m[1].replace(/^["']|["']$/g, '');
|
|
24
|
+
|
|
25
|
+
let head = '';
|
|
26
|
+
try {
|
|
27
|
+
head = execFileSync('git', ['rev-parse', '--short=7', 'HEAD'],
|
|
28
|
+
{ cwd, timeout: 3000, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
29
|
+
} catch { head = ''; }
|
|
30
|
+
|
|
31
|
+
const stamp = new Date().toISOString().replace(/\.\d+Z$/, 'Z');
|
|
32
|
+
const line = '- [compaction ' + stamp + ' · ' + trigger + ' · HEAD ' + (head || 'unknown') + '] '
|
|
33
|
+
+ 're-read phases/' + phase + '/PLAN.md and the milestone board before continuing.';
|
|
34
|
+
|
|
35
|
+
const out = body.split('\n');
|
|
36
|
+
const at = out.findIndex((l) => /^## Epilogue/.test(l));
|
|
37
|
+
if (at >= 0) out.splice(at, 0, line, '');
|
|
38
|
+
else {
|
|
39
|
+
while (out.length && out[out.length - 1].trim() === '') out.pop();
|
|
40
|
+
out.push(line, '');
|
|
41
|
+
}
|
|
42
|
+
const tmp = file + '.ll-tmp-' + process.pid;
|
|
43
|
+
fs.writeFileSync(tmp, out.join('\n'));
|
|
44
|
+
fs.renameSync(tmp, file);
|
|
45
|
+
process.stdout.write(JSON.stringify({ systemMessage: '[ll-skills] compaction marked in PROGRESS.md' }) + '\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try { main(); } catch { /* silence */ }
|
|
49
|
+
process.exit(0);
|
|
@@ -129,7 +129,7 @@ function reader(installed) {
|
|
|
129
129
|
if (cache && cache.update_available && cache.installed === installed && cache.latest) {
|
|
130
130
|
process.stdout.write(
|
|
131
131
|
JSON.stringify({
|
|
132
|
-
systemMessage: `ll-skills desatualizado (instalado ${installed}, disponível ${cache.latest}) — rode /ll-
|
|
132
|
+
systemMessage: `ll-skills desatualizado (instalado ${installed}, disponível ${cache.latest}) — rode /ll-update`,
|
|
133
133
|
}) + '\n'
|
|
134
134
|
);
|
|
135
135
|
}
|