@ancleto/spec 0.5.0 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancleto/spec",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Orquestador SDD liviano con subagentes optimizados para costo/tokens",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.js CHANGED
@@ -8,6 +8,8 @@ import { fileURLToPath } from 'node:url'
8
8
  import { homedir, tmpdir } from 'node:os'
9
9
  import { createMemoryEngine, defaultMemoryDbPath } from '../core/memory/engine.js'
10
10
  import { memoryDoctor } from '../core/memory/doctor.js'
11
+ import { writeDiscoveryMap } from '../core/discovery.js'
12
+ import { readProjectTier, buildRepomixArgs, tierTokenBudget } from '../core/repomix-tier.js'
11
13
 
12
14
  const __dirname = dirname(fileURLToPath(import.meta.url))
13
15
  const ROOT = join(__dirname, '..', '..')
@@ -597,16 +599,11 @@ async function checkDiscovery() {
597
599
 
598
600
  async function runRepomix(flags, tmpFile) {
599
601
  const local = await resolveBin('repomix')
602
+ const tier = readProjectTier(process.cwd())
603
+ const { exclude } = await loadDiscoveryConfig()
600
604
  const args = []
601
605
  if (!local) args.push('-y', 'repomix@1.18.0')
602
- args.push('--output', tmpFile)
603
- const inc = flagValue(flags, '--include')
604
- if (inc) args.push('--include', inc)
605
- const extraIgnore = flagValue(flags, '--ignore')
606
- const { exclude } = await loadDiscoveryConfig()
607
- const ignore = [...exclude, ...(extraIgnore ? extraIgnore.split(',') : [])].filter(Boolean)
608
- if (ignore.length) args.push('--ignore', ignore.join(','))
609
- if (flags.includes('--compress')) args.push('--compress')
606
+ args.push('--output', tmpFile, ...buildRepomixArgs(flags, tier, exclude))
610
607
  const cmd = local || 'npx'
611
608
  const r = spawnSync(cmd, args, { encoding: 'utf8', cwd: process.cwd(), shell: true })
612
609
  if (r.error) {
@@ -628,8 +625,9 @@ async function packDiscovery(flags) {
628
625
  let content = ''
629
626
  try { content = await readFile(tmpFile, 'utf8') } catch {}
630
627
  const tokens = Math.round(content.length / 4)
631
- const budget = flagValue(flags, '--token-budget')
632
- if (budget && tokens > Number(budget)) {
628
+ const explicit = flagValue(flags, '--token-budget')
629
+ const budget = explicit ? Number(explicit) : tierTokenBudget(readProjectTier(process.cwd()))
630
+ if (budget && tokens > budget) {
633
631
  console.error(`ancleto: el pack supera el token-budget (${tokens} > ${budget})`)
634
632
  process.exit(1)
635
633
  }
@@ -649,6 +647,7 @@ async function packDiscovery(flags) {
649
647
  }
650
648
 
651
649
  async function discovery(flags) {
650
+ try { writeDiscoveryMap(process.cwd()) } catch {}
652
651
  if (flags.includes('--check')) {
653
652
  await checkDiscovery()
654
653
  return
@@ -0,0 +1,62 @@
1
+ import { readdirSync, writeFileSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ export const DISCOVERY_MAP_FILE = '.discovery-map.json'
5
+ export const TOPOLOGY_IGNORED_DIRS = ['node_modules', '.git', '.ancleto', 'dist', 'build', 'coverage']
6
+
7
+ function countFilesRecursive(dir, ignored) {
8
+ let n = 0
9
+ let entries
10
+ try {
11
+ entries = readdirSync(dir, { withFileTypes: true })
12
+ } catch {
13
+ return 0
14
+ }
15
+ for (const e of entries) {
16
+ const p = join(dir, e.name)
17
+ if (e.isDirectory()) {
18
+ if (ignored.has(e.name)) continue
19
+ n += countFilesRecursive(p, ignored)
20
+ } else if (e.isFile()) {
21
+ n += 1
22
+ }
23
+ }
24
+ return n
25
+ }
26
+
27
+ export function buildTopologyMap(rootDir) {
28
+ const ignored = new Set(TOPOLOGY_IGNORED_DIRS)
29
+ const tree_summary = {}
30
+ const root_files = []
31
+ let total = 0
32
+ let entries
33
+ try {
34
+ entries = readdirSync(rootDir, { withFileTypes: true })
35
+ } catch {
36
+ entries = []
37
+ }
38
+ for (const e of entries) {
39
+ if (ignored.has(e.name)) continue
40
+ if (e.isDirectory()) {
41
+ const n = countFilesRecursive(join(rootDir, e.name), ignored)
42
+ tree_summary[e.name] = n
43
+ total += n
44
+ } else if (e.isFile()) {
45
+ root_files.push(e.name)
46
+ total += 1
47
+ }
48
+ }
49
+ root_files.sort()
50
+ return {
51
+ last_updated: new Date().toISOString(),
52
+ total_files: total,
53
+ tree_summary,
54
+ root_files
55
+ }
56
+ }
57
+
58
+ export function writeDiscoveryMap(rootDir) {
59
+ const map = buildTopologyMap(rootDir)
60
+ writeFileSync(join(rootDir, DISCOVERY_MAP_FILE), JSON.stringify(map, null, 2) + '\n')
61
+ return map
62
+ }
@@ -0,0 +1,38 @@
1
+ import { readFileSync, existsSync } from 'node:fs'
2
+ import { join } from 'node:path'
3
+
4
+ export const TIER_PACK_CONFIG = {
5
+ normal: { extraIgnore: [], compress: false, tokenBudget: null },
6
+ minimo: { extraIgnore: ['test/**', 'docs/**', '**/*.md'], compress: true, tokenBudget: null },
7
+ gratis: { extraIgnore: ['test/**', 'docs/**', '**/*.md'], compress: true, tokenBudget: 50000 }
8
+ }
9
+
10
+ export function readProjectTier(cwd) {
11
+ for (const p of [join(cwd, '.ancleto-tier'), join(cwd, '.opencode', '.ancleto-tier')]) {
12
+ if (!existsSync(p)) continue
13
+ const v = readFileSync(p, 'utf8').trim()
14
+ if (v) return TIER_PACK_CONFIG[v] ? v : 'gratis'
15
+ }
16
+ return 'gratis'
17
+ }
18
+
19
+ export function tierTokenBudget(tier) {
20
+ return (TIER_PACK_CONFIG[tier] || TIER_PACK_CONFIG.gratis).tokenBudget
21
+ }
22
+
23
+ export function buildRepomixArgs(flags, tier, exclude = []) {
24
+ const cfg = TIER_PACK_CONFIG[tier] || TIER_PACK_CONFIG.gratis
25
+ const args = []
26
+ const inc = flagValue(flags, '--include')
27
+ if (inc) args.push('--include', inc)
28
+ const extraIgnore = flagValue(flags, '--ignore')
29
+ const ignore = [...exclude, ...cfg.extraIgnore, ...(extraIgnore ? extraIgnore.split(',') : [])].filter(Boolean)
30
+ if (ignore.length) args.push('--ignore', ignore.join(','))
31
+ if (cfg.compress || flags.includes('--compress')) args.push('--compress')
32
+ return args
33
+ }
34
+
35
+ function flagValue(args, flag) {
36
+ const i = args.indexOf(flag)
37
+ return i >= 0 && args[i + 1] ? args[i + 1] : null
38
+ }