refacil-sdd-ai 2.6.0 → 2.7.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.
package/README.md CHANGED
@@ -56,7 +56,7 @@ refacil-sdd-ai update # Actualizar skills y hooks a la ultima version
56
56
  refacil-sdd-ai check-update # Verifica si hay una version mas reciente en npm (usado por hook)
57
57
  refacil-sdd-ai check-review # Verifica que el review se haya completado (usado por hook)
58
58
  refacil-sdd-ai compact-bash # Hook de rewrite de comandos Bash bare (usado por hook, no invocar manual)
59
- refacil-sdd-ai compact stats # Tabla de rewrites + tokens estimados + USD ahorrado
59
+ refacil-sdd-ai compact stats # Estadisticas completas (hook + ya-compacto) + tokens estimados + USD
60
60
  refacil-sdd-ai compact disable # Desactiva el hook compact-bash sin desinstalarlo
61
61
  refacil-sdd-ai compact enable # Reactiva el hook compact-bash
62
62
  refacil-sdd-ai compact clear-log # Limpia ~/.refacil-sdd-ai/compact.log
@@ -431,13 +431,13 @@ Fase 2A — linters, type checkers, build, sistema:
431
431
  **Subcomandos de control**:
432
432
 
433
433
  ```bash
434
- refacil-sdd-ai compact stats # tabla de rewrites + tokens estimados + USD
434
+ refacil-sdd-ai compact stats # estadisticas completas (hook + ya-compacto) + tokens estimados + USD
435
435
  refacil-sdd-ai compact disable # desactiva el hook sin desinstalar
436
436
  refacil-sdd-ai compact enable # reactiva el hook
437
437
  refacil-sdd-ai compact clear-log # limpia ~/.refacil-sdd-ai/compact.log
438
438
  ```
439
439
 
440
- **Telemetria**: cada rewrite genera una linea JSON en `~/.refacil-sdd-ai/compact.log` con timestamp, ruleId y estimacion de tokens ahorrados. El log se usa para el comando `compact stats` que agrega por regla y calcula costo estimado (a razon de $3/MTok input de Sonnet, conservador). La telemetria es local; nada se envia a la nube.
440
+ **Telemetria**: cada evento genera una linea JSON en `~/.refacil-sdd-ai/compact.log` con timestamp, ruleId y estimacion de tokens. Hay dos tipos de evento: `hook_rewrite` (el hook reescribe) y `already_compact` (el comando ya llega compacto, asumido por skill/agente). `compact stats` muestra ambos por defecto y calcula costo estimado (a razon de $3/MTok input de Sonnet, conservador). La telemetria es local; nada se envia a la nube.
441
441
 
442
442
  **Flujo del hook**:
443
443
 
package/bin/cli.js CHANGED
@@ -535,29 +535,65 @@ function handleCompactSubcommand(sub) {
535
535
 
536
536
  function showCompactStats() {
537
537
  const s = compactTelemetry.stats();
538
- if (s.totalRewrites === 0) {
539
- console.log('\n No hay rewrites registrados todavia. El hook compact-bash aun no se ha disparado o esta deshabilitado.\n');
538
+ if (s.totalEvents === 0) {
539
+ console.log('\n No hay eventos registrados todavia. Ejecuta comandos Bash para generar telemetria de compactacion.\n');
540
540
  return;
541
541
  }
542
542
 
543
- const sorted = Object.entries(s.byRule).sort((a, b) => b[1].saved - a[1].saved);
543
+ const sortedRewrites = Object.entries(s.byRule)
544
+ .filter(([, data]) => data.rewriteCount > 0)
545
+ .sort((a, b) => b[1].rewriteSaved - a[1].rewriteSaved);
546
+ const sortedAlreadyCompact = Object.entries(s.byRule)
547
+ .filter(([, data]) => data.alreadyCompactCount > 0)
548
+ .sort((a, b) => b[1].alreadyCompactPotential - a[1].alreadyCompactPotential);
544
549
 
545
- console.log(`\n compact-bash stats (${s.totalRewrites} rewrites en total)\n`);
546
- for (const [id, data] of sorted) {
547
- const kTokens = (data.saved / 1000).toFixed(1);
550
+ console.log(`\n compact-bash stats\n`);
551
+ console.log(` Rewrites por hook: ${s.totalRewrites}`);
552
+ console.log(` Comandos ya compactos detectados (skill/agente): ${s.totalAlreadyCompact}\n`);
553
+
554
+ if (sortedRewrites.length > 0) {
555
+ console.log(' Ahorro aplicado por hook (rewrite):');
556
+ for (const [id, data] of sortedRewrites) {
557
+ const kTokens = (data.rewriteSaved / 1000).toFixed(1);
558
+ console.log(
559
+ ` ${id.padEnd(18)} ${String(data.rewriteCount).padStart(6)} rewrites ~${kTokens.padStart(7)}k tokens`,
560
+ );
561
+ }
562
+ const totalHookK = (s.totalSaved / 1000).toFixed(1);
563
+ const hookUsd = ((s.totalSaved / 1_000_000) * 3).toFixed(2);
564
+ console.log(` ${'-'.repeat(62)}`);
565
+ console.log(
566
+ ` ${'Total hook'.padEnd(18)} ${String(s.totalRewrites).padStart(6)} rewrites ~${totalHookK.padStart(7)}k tokens (~$${hookUsd} USD)`,
567
+ );
568
+ console.log('');
569
+ }
570
+
571
+ if (sortedAlreadyCompact.length > 0) {
572
+ console.log(' Ahorro potencial ya capturado por skill/agente (sin rewrite):');
573
+ for (const [id, data] of sortedAlreadyCompact) {
574
+ const kTokens = (data.alreadyCompactPotential / 1000).toFixed(1);
575
+ console.log(
576
+ ` ${id.padEnd(18)} ${String(data.alreadyCompactCount).padStart(6)} eventos ~${kTokens.padStart(7)}k tokens potenciales`,
577
+ );
578
+ }
579
+ const totalAgentK = (s.totalAlreadyCompactPotential / 1000).toFixed(1);
580
+ const agentUsd = ((s.totalAlreadyCompactPotential / 1_000_000) * 3).toFixed(2);
581
+ console.log(` ${'-'.repeat(62)}`);
548
582
  console.log(
549
- ` ${id.padEnd(18)} ${String(data.count).padStart(6)} rewrites ~${kTokens.padStart(7)}k tokens estimados`,
583
+ ` ${'Total skill'.padEnd(18)} ${String(s.totalAlreadyCompact).padStart(6)} eventos ~${totalAgentK.padStart(7)}k tokens (~$${agentUsd} USD)`,
550
584
  );
585
+ console.log('');
551
586
  }
552
- const totalK = (s.totalSaved / 1000).toFixed(1);
553
- const costUsd = ((s.totalSaved / 1_000_000) * 3).toFixed(2);
587
+
588
+ const totalK = (s.totalObservedPotential / 1000).toFixed(1);
589
+ const totalUsd = ((s.totalObservedPotential / 1_000_000) * 3).toFixed(2);
554
590
  console.log(` ${'-'.repeat(62)}`);
555
591
  console.log(
556
- ` ${'Total'.padEnd(18)} ${String(s.totalRewrites).padStart(6)} rewrites ~${totalK.padStart(7)}k tokens (~$${costUsd} USD, Sonnet input)`,
592
+ ` ${'Total observado'.padEnd(18)} ${String(s.totalEvents).padStart(6)} eventos ~${totalK.padStart(7)}k tokens (~$${totalUsd} USD, Sonnet input)`,
557
593
  );
558
594
  console.log(`\n Log: ${compactTelemetry.LOG_PATH}`);
559
595
  if (compactTelemetry.isDisabled()) {
560
- console.log(' Estado: DESHABILITADO (no se registran nuevos rewrites)');
596
+ console.log(' Estado: DESHABILITADO (no se registran nuevos eventos)');
561
597
  }
562
598
  console.log('');
563
599
  }
@@ -573,7 +609,7 @@ function help() {
573
609
  check-review Verifica que el review se haya completado (usado por hook PreToolUse)
574
610
  compact-bash Reescribe comandos Bash bare para reducir tokens (usado por hook PreToolUse)
575
611
  compact Subcomandos del hook compact-bash:
576
- compact stats - Estadisticas de rewrites y ahorro estimado
612
+ compact stats - Estadisticas completas (hook + ya-compacto) y ahorro estimado
577
613
  compact disable - Desactiva el rewrite temporalmente
578
614
  compact enable - Re-activa el rewrite
579
615
  compact clear-log - Borra el log historico
@@ -1,5 +1,5 @@
1
1
  const fs = require('fs');
2
- const { findRule } = require('./rules');
2
+ const { findRule, findAlreadyCompactRule } = require('./rules');
3
3
  const telemetry = require('./telemetry');
4
4
 
5
5
  function run() {
@@ -19,7 +19,13 @@ function run() {
19
19
  if (typeof origCommand !== 'string') return;
20
20
 
21
21
  const rule = findRule(origCommand);
22
- if (!rule) return;
22
+ if (!rule) {
23
+ const compactRule = findAlreadyCompactRule(origCommand);
24
+ if (compactRule) {
25
+ telemetry.logAlreadyCompact(compactRule.id, compactRule.savedTokensEst);
26
+ }
27
+ return;
28
+ }
23
29
 
24
30
  let newCommand;
25
31
  try {
@@ -13,6 +13,9 @@ const RULES = [
13
13
  id: 'git-log',
14
14
  match: (cmd) =>
15
15
  /^\s*git\s+log(\s|$)/.test(cmd) && !hasFlagAfterBase(cmd, 2),
16
+ compactMatch: (cmd) =>
17
+ /^\s*git\s+log(\s|$)/.test(cmd) &&
18
+ (/--oneline\b/.test(cmd) || /(^|\s)-\d+\b/.test(cmd)),
16
19
  rewrite: (cmd) => cmd.replace(/^(\s*git\s+log)/, '$1 --oneline -20'),
17
20
  reason: 'git log → --oneline -20',
18
21
  savedTokensEst: 850,
@@ -21,6 +24,9 @@ const RULES = [
21
24
  id: 'git-status',
22
25
  match: (cmd) =>
23
26
  /^\s*git\s+status(\s|$)/.test(cmd) && !hasFlagAfterBase(cmd, 2),
27
+ compactMatch: (cmd) =>
28
+ /^\s*git\s+status(\s|$)/.test(cmd) &&
29
+ (/\s-s(\s|$)/.test(cmd) || /--short\b/.test(cmd)),
24
30
  rewrite: (cmd) => cmd.replace(/^(\s*git\s+status)/, '$1 -s'),
25
31
  reason: 'git status → -s',
26
32
  savedTokensEst: 120,
@@ -28,6 +34,7 @@ const RULES = [
28
34
  {
29
35
  id: 'git-diff',
30
36
  match: (cmd) => /^\s*git\s+diff\s*$/.test(cmd),
37
+ compactMatch: (cmd) => /^\s*git\s+diff(\s|$)/.test(cmd) && /--stat\b/.test(cmd),
31
38
  rewrite: (cmd) => cmd.replace(/^(\s*git\s+diff)\s*$/, '$1 --stat'),
32
39
  reason: 'git diff → --stat',
33
40
  savedTokensEst: 400,
@@ -36,6 +43,7 @@ const RULES = [
36
43
  id: 'git-show',
37
44
  match: (cmd) =>
38
45
  /^\s*git\s+show(\s|$)/.test(cmd) && !hasFlagAfterBase(cmd, 2),
46
+ compactMatch: (cmd) => /^\s*git\s+show(\s|$)/.test(cmd) && /--stat\b/.test(cmd),
39
47
  rewrite: (cmd) => cmd.replace(/^(\s*git\s+show)/, '$1 --stat'),
40
48
  reason: 'git show → --stat',
41
49
  savedTokensEst: 200,
@@ -49,6 +57,9 @@ const RULES = [
49
57
  if (/\s--since\b/.test(cmd)) return false;
50
58
  return true;
51
59
  },
60
+ compactMatch: (cmd) =>
61
+ /^\s*docker\s+logs(\s|$)/.test(cmd) &&
62
+ (/\s--tail\b/.test(cmd) || /\s-n\s+\d/.test(cmd) || /\s--since\b/.test(cmd)),
52
63
  rewrite: (cmd) => cmd.replace(/^(\s*docker\s+logs)/, '$1 --tail 100'),
53
64
  reason: 'docker logs → --tail 100',
54
65
  savedTokensEst: 1500,
@@ -56,6 +67,9 @@ const RULES = [
56
67
  {
57
68
  id: 'pkg-test',
58
69
  match: (cmd) => /^\s*(npm|yarn|pnpm)\s+(test|t)\s*$/.test(cmd),
70
+ compactMatch: (cmd) =>
71
+ /^\s*(npm|yarn|pnpm)\s+(test|t)\b/.test(cmd) &&
72
+ (hasPipeOrRedirect(cmd) || /--silent\b/.test(cmd) || /\b-q\b/.test(cmd)),
59
73
  rewrite: (cmd) => `${cmd.trim()} 2>&1 | tail -80`,
60
74
  reason: 'test bare → tail -80',
61
75
  savedTokensEst: 2400,
@@ -63,6 +77,9 @@ const RULES = [
63
77
  {
64
78
  id: 'jest-bare',
65
79
  match: (cmd) => /^\s*(npx\s+)?jest\s*$/.test(cmd),
80
+ compactMatch: (cmd) =>
81
+ /^\s*(npx\s+)?jest(\s|$)/.test(cmd) &&
82
+ (/--silent\b/.test(cmd) || /--reporters=summary\b/.test(cmd)),
66
83
  rewrite: (cmd) =>
67
84
  cmd.replace(/^(\s*(?:npx\s+)?jest)\s*$/, '$1 --silent --reporters=summary'),
68
85
  reason: 'jest → silent summary',
@@ -71,6 +88,7 @@ const RULES = [
71
88
  {
72
89
  id: 'pytest-bare',
73
90
  match: (cmd) => /^\s*pytest\s*$/.test(cmd),
91
+ compactMatch: (cmd) => /^\s*pytest(\s|$)/.test(cmd) && /(^|\s)-q(\s|$)/.test(cmd),
74
92
  rewrite: (cmd) => cmd.replace(/^(\s*pytest)\s*$/, '$1 -q'),
75
93
  reason: 'pytest → -q',
76
94
  savedTokensEst: 600,
@@ -79,6 +97,9 @@ const RULES = [
79
97
  {
80
98
  id: 'eslint',
81
99
  match: (cmd) => /^\s*eslint(\s+[^-]\S*)*\s*$/.test(cmd),
100
+ compactMatch: (cmd) =>
101
+ /^\s*eslint(\s|$)/.test(cmd) &&
102
+ (/--format\s+compact\b/.test(cmd) || /--quiet\b/.test(cmd)),
82
103
  rewrite: (cmd) => {
83
104
  const tokens = cmd.trim().split(/\s+/);
84
105
  if (tokens.length === 1) {
@@ -93,6 +114,8 @@ const RULES = [
93
114
  id: 'biome-check',
94
115
  match: (cmd) =>
95
116
  /^\s*biome\s+check(\s|$)/.test(cmd) && !/--reporter\b/.test(cmd),
117
+ compactMatch: (cmd) =>
118
+ /^\s*biome\s+check(\s|$)/.test(cmd) && /--reporter=summary\b/.test(cmd),
96
119
  rewrite: (cmd) =>
97
120
  cmd.replace(/^(\s*biome\s+check)/, '$1 --reporter=summary'),
98
121
  reason: 'biome check → --reporter=summary',
@@ -106,6 +129,8 @@ const RULES = [
106
129
  if (hasPipeOrRedirect(cmd)) return false;
107
130
  return true;
108
131
  },
132
+ compactMatch: (cmd) =>
133
+ /^\s*(npx\s+)?tsc(\s|$)/.test(cmd) && hasPipeOrRedirect(cmd),
109
134
  rewrite: (cmd) => `${cmd.trim()} 2>&1 | head -80`,
110
135
  reason: 'tsc → head -80',
111
136
  savedTokensEst: 1200,
@@ -114,6 +139,8 @@ const RULES = [
114
139
  id: 'prettier-check',
115
140
  match: (cmd) =>
116
141
  /^\s*prettier\s+--check\b/.test(cmd) && !/--loglevel\b/.test(cmd),
142
+ compactMatch: (cmd) =>
143
+ /^\s*prettier\s+--check\b/.test(cmd) && /--loglevel\b/.test(cmd),
117
144
  rewrite: (cmd) =>
118
145
  cmd.replace(/^(\s*prettier\s+--check)/, '$1 --loglevel warn'),
119
146
  reason: 'prettier --check → --loglevel warn',
@@ -122,6 +149,7 @@ const RULES = [
122
149
  {
123
150
  id: 'npm-audit',
124
151
  match: (cmd) => /^\s*npm\s+audit\s*$/.test(cmd),
152
+ compactMatch: (cmd) => /^\s*npm\s+audit(\s|$)/.test(cmd) && hasPipeOrRedirect(cmd),
125
153
  rewrite: (cmd) => `${cmd.trim()} 2>&1 | tail -10`,
126
154
  reason: 'npm audit → tail -10',
127
155
  savedTokensEst: 900,
@@ -129,6 +157,7 @@ const RULES = [
129
157
  {
130
158
  id: 'npm-ls',
131
159
  match: (cmd) => /^\s*npm\s+ls\s*$/.test(cmd),
160
+ compactMatch: (cmd) => /^\s*npm\s+ls(\s|$)/.test(cmd) && /--depth=0\b/.test(cmd),
132
161
  rewrite: (cmd) => cmd.replace(/^(\s*npm\s+ls)/, '$1 --depth=0'),
133
162
  reason: 'npm ls → --depth=0',
134
163
  savedTokensEst: 700,
@@ -136,6 +165,8 @@ const RULES = [
136
165
  {
137
166
  id: 'cargo-bare',
138
167
  match: (cmd) => /^\s*cargo\s+(build|test|check)\s*$/.test(cmd),
168
+ compactMatch: (cmd) =>
169
+ /^\s*cargo\s+(build|test|check)(\s|$)/.test(cmd) && /--quiet\b/.test(cmd),
139
170
  rewrite: (cmd) => `${cmd.trim()} --quiet`,
140
171
  reason: 'cargo → --quiet',
141
172
  savedTokensEst: 400,
@@ -149,6 +180,7 @@ const RULES = [
149
180
  if (hasPipeOrRedirect(cmd)) return false;
150
181
  return true;
151
182
  },
183
+ compactMatch: (cmd) => /^\s*go\s+test(\s|$)/.test(cmd) && hasPipeOrRedirect(cmd),
152
184
  rewrite: (cmd) => `${cmd.trim()} 2>&1 | tail -80`,
153
185
  reason: 'go test → tail -80',
154
186
  savedTokensEst: 1500,
@@ -156,6 +188,7 @@ const RULES = [
156
188
  {
157
189
  id: 'mvn-test',
158
190
  match: (cmd) => /^\s*mvn\s+test\s*$/.test(cmd),
191
+ compactMatch: (cmd) => /^\s*mvn\s+test(\s|$)/.test(cmd) && /(^|\s)-q(\s|$)/.test(cmd),
159
192
  rewrite: (cmd) => `${cmd.trim()} -q`,
160
193
  reason: 'mvn test → -q',
161
194
  savedTokensEst: 1800,
@@ -163,6 +196,9 @@ const RULES = [
163
196
  {
164
197
  id: 'gradle-test',
165
198
  match: (cmd) => /^\s*(\.\/gradlew|gradle)\s+test\s*$/.test(cmd),
199
+ compactMatch: (cmd) =>
200
+ /^\s*(\.\/gradlew|gradle)\s+test(\s|$)/.test(cmd) &&
201
+ /(^|\s)-q(\s|$)/.test(cmd),
166
202
  rewrite: (cmd) => `${cmd.trim()} -q`,
167
203
  reason: 'gradle test → -q',
168
204
  savedTokensEst: 1500,
@@ -187,4 +223,14 @@ function findRule(cmd) {
187
223
  return null;
188
224
  }
189
225
 
190
- module.exports = { RULES, findRule };
226
+ function findAlreadyCompactRule(cmd) {
227
+ if (typeof cmd !== 'string' || !cmd.trim()) return null;
228
+ for (const rule of RULES) {
229
+ if (typeof rule.compactMatch === 'function' && rule.compactMatch(cmd)) {
230
+ return rule;
231
+ }
232
+ }
233
+ return null;
234
+ }
235
+
236
+ module.exports = { RULES, findRule, findAlreadyCompactRule };
@@ -16,14 +16,16 @@ function isDisabled() {
16
16
  return fs.existsSync(DISABLED_PATH);
17
17
  }
18
18
 
19
- function logRewrite(ruleId, savedTokensEst) {
19
+ function logEvent(eventType, ruleId, savedTokensEst, meta = {}) {
20
20
  try {
21
21
  ensureDir();
22
22
  const line =
23
23
  JSON.stringify({
24
24
  ts: new Date().toISOString(),
25
+ eventType: eventType || 'hook_rewrite',
25
26
  ruleId,
26
27
  savedTokensEst: savedTokensEst || 0,
28
+ ...meta,
27
29
  }) + '\n';
28
30
  fs.appendFileSync(LOG_PATH, line);
29
31
  } catch (_) {
@@ -31,6 +33,17 @@ function logRewrite(ruleId, savedTokensEst) {
31
33
  }
32
34
  }
33
35
 
36
+ function logRewrite(ruleId, savedTokensEst) {
37
+ logEvent('hook_rewrite', ruleId, savedTokensEst);
38
+ }
39
+
40
+ function logAlreadyCompact(ruleId, savedTokensEst) {
41
+ // Assumption: command arrived compact due to skills/agent discipline.
42
+ logEvent('already_compact', ruleId, savedTokensEst, {
43
+ source: 'skill_assumed',
44
+ });
45
+ }
46
+
34
47
  function readLog() {
35
48
  try {
36
49
  const content = fs.readFileSync(LOG_PATH, 'utf8');
@@ -54,13 +67,43 @@ function stats() {
54
67
  const entries = readLog();
55
68
  const byRule = {};
56
69
  let totalSaved = 0;
70
+ let totalAlreadyCompactPotential = 0;
71
+ let totalAlreadyCompact = 0;
72
+ let totalRewrites = 0;
73
+
57
74
  for (const e of entries) {
58
- if (!byRule[e.ruleId]) byRule[e.ruleId] = { count: 0, saved: 0 };
59
- byRule[e.ruleId].count++;
60
- byRule[e.ruleId].saved += e.savedTokensEst || 0;
61
- totalSaved += e.savedTokensEst || 0;
75
+ const eventType = e.eventType || 'hook_rewrite';
76
+ if (!byRule[e.ruleId]) {
77
+ byRule[e.ruleId] = {
78
+ rewriteCount: 0,
79
+ rewriteSaved: 0,
80
+ alreadyCompactCount: 0,
81
+ alreadyCompactPotential: 0,
82
+ };
83
+ }
84
+
85
+ if (eventType === 'already_compact') {
86
+ byRule[e.ruleId].alreadyCompactCount++;
87
+ byRule[e.ruleId].alreadyCompactPotential += e.savedTokensEst || 0;
88
+ totalAlreadyCompact++;
89
+ totalAlreadyCompactPotential += e.savedTokensEst || 0;
90
+ } else {
91
+ byRule[e.ruleId].rewriteCount++;
92
+ byRule[e.ruleId].rewriteSaved += e.savedTokensEst || 0;
93
+ totalRewrites++;
94
+ totalSaved += e.savedTokensEst || 0;
95
+ }
62
96
  }
63
- return { byRule, totalSaved, totalRewrites: entries.length };
97
+
98
+ return {
99
+ byRule,
100
+ totalSaved,
101
+ totalRewrites,
102
+ totalAlreadyCompact,
103
+ totalAlreadyCompactPotential,
104
+ totalObservedPotential: totalSaved + totalAlreadyCompactPotential,
105
+ totalEvents: entries.length,
106
+ };
64
107
  }
65
108
 
66
109
  function disable() {
@@ -86,6 +129,7 @@ module.exports = {
86
129
  DISABLED_PATH,
87
130
  isDisabled,
88
131
  logRewrite,
132
+ logAlreadyCompact,
89
133
  stats,
90
134
  disable,
91
135
  enable,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "refacil-sdd-ai",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
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"