@ancleto/spec 0.4.6 → 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 +1 -1
- package/src/cli/index.js +38 -10
- package/src/core/discovery.js +62 -0
- package/src/core/repomix-tier.js +38 -0
package/package.json
CHANGED
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, '..', '..')
|
|
@@ -26,6 +28,7 @@ Uso:
|
|
|
26
28
|
ancleto install --tier <nivel> normal | minimo | gratis (pregunta en la 1ra config)
|
|
27
29
|
ancleto install --agent <nombre> opencode | vscode | antigravity | cursor | roo (pregunta si no esta guardado)
|
|
28
30
|
ancleto update [--project <dir>] Alias de install (re-instala sobre lo existente)
|
|
31
|
+
ancleto upgrade [--agent <nombre>] Re-aplica templates (LOCKED) y skills sobre el proyecto actual
|
|
29
32
|
ancleto init [--with-azure] [--agent <nombre>]
|
|
30
33
|
Crea .ancletorc en el repositorio actual
|
|
31
34
|
(Azure desactivado por defecto, agente: opencode)
|
|
@@ -417,6 +420,31 @@ async function install(args) {
|
|
|
417
420
|
if (azureMcp) console.log(AZURE_MCP_NOTICE)
|
|
418
421
|
}
|
|
419
422
|
|
|
423
|
+
async function upgradeCmd(args) {
|
|
424
|
+
const projectDir = process.cwd()
|
|
425
|
+
const rc = await readAncletorc(projectDir)
|
|
426
|
+
if (!rc) {
|
|
427
|
+
console.error("Error: No se encontro .ancletorc. Ejecuta 'ancleto init' primero.")
|
|
428
|
+
process.exit(1)
|
|
429
|
+
}
|
|
430
|
+
const agent = await resolveAgent(args, rc.agent)
|
|
431
|
+
await copyTemplates(projectDir)
|
|
432
|
+
const agentSkillsDir = await installAgentSkills(projectDir, agent)
|
|
433
|
+
const manifest = await writeManifest(projectDir, {
|
|
434
|
+
agent,
|
|
435
|
+
installedPaths: {
|
|
436
|
+
templates: ['AGENTS.md', 'PRODUCT.md'],
|
|
437
|
+
agents: ['.opencode/agents'],
|
|
438
|
+
commands: ['.opencode/commands'],
|
|
439
|
+
skills: [agentSkillsDir]
|
|
440
|
+
}
|
|
441
|
+
})
|
|
442
|
+
console.log(`ancleto: upgrade completo (v${manifest.version}, agente: ${agent})`)
|
|
443
|
+
console.log(' ✔ templates re-aplicados (bloques LOCKED actualizados, EXTENSIBLE intacto)')
|
|
444
|
+
console.log(` ✔ skills actualizadas en ${agentSkillsDir} (${OPENSPEC_SKILLS.length} skills)`)
|
|
445
|
+
console.log(' ✔ manifiesto .ancletorc actualizado')
|
|
446
|
+
}
|
|
447
|
+
|
|
420
448
|
async function initProject(args) {
|
|
421
449
|
const withAzure = args.includes('--with-azure')
|
|
422
450
|
const projectDir = process.cwd()
|
|
@@ -571,16 +599,11 @@ async function checkDiscovery() {
|
|
|
571
599
|
|
|
572
600
|
async function runRepomix(flags, tmpFile) {
|
|
573
601
|
const local = await resolveBin('repomix')
|
|
602
|
+
const tier = readProjectTier(process.cwd())
|
|
603
|
+
const { exclude } = await loadDiscoveryConfig()
|
|
574
604
|
const args = []
|
|
575
605
|
if (!local) args.push('-y', 'repomix@1.18.0')
|
|
576
|
-
args.push('--output', tmpFile)
|
|
577
|
-
const inc = flagValue(flags, '--include')
|
|
578
|
-
if (inc) args.push('--include', inc)
|
|
579
|
-
const extraIgnore = flagValue(flags, '--ignore')
|
|
580
|
-
const { exclude } = await loadDiscoveryConfig()
|
|
581
|
-
const ignore = [...exclude, ...(extraIgnore ? extraIgnore.split(',') : [])].filter(Boolean)
|
|
582
|
-
if (ignore.length) args.push('--ignore', ignore.join(','))
|
|
583
|
-
if (flags.includes('--compress')) args.push('--compress')
|
|
606
|
+
args.push('--output', tmpFile, ...buildRepomixArgs(flags, tier, exclude))
|
|
584
607
|
const cmd = local || 'npx'
|
|
585
608
|
const r = spawnSync(cmd, args, { encoding: 'utf8', cwd: process.cwd(), shell: true })
|
|
586
609
|
if (r.error) {
|
|
@@ -602,8 +625,9 @@ async function packDiscovery(flags) {
|
|
|
602
625
|
let content = ''
|
|
603
626
|
try { content = await readFile(tmpFile, 'utf8') } catch {}
|
|
604
627
|
const tokens = Math.round(content.length / 4)
|
|
605
|
-
const
|
|
606
|
-
|
|
628
|
+
const explicit = flagValue(flags, '--token-budget')
|
|
629
|
+
const budget = explicit ? Number(explicit) : tierTokenBudget(readProjectTier(process.cwd()))
|
|
630
|
+
if (budget && tokens > budget) {
|
|
607
631
|
console.error(`ancleto: el pack supera el token-budget (${tokens} > ${budget})`)
|
|
608
632
|
process.exit(1)
|
|
609
633
|
}
|
|
@@ -623,6 +647,7 @@ async function packDiscovery(flags) {
|
|
|
623
647
|
}
|
|
624
648
|
|
|
625
649
|
async function discovery(flags) {
|
|
650
|
+
try { writeDiscoveryMap(process.cwd()) } catch {}
|
|
626
651
|
if (flags.includes('--check')) {
|
|
627
652
|
await checkDiscovery()
|
|
628
653
|
return
|
|
@@ -788,6 +813,9 @@ switch (cmd) {
|
|
|
788
813
|
case 'update':
|
|
789
814
|
await install(rest)
|
|
790
815
|
break
|
|
816
|
+
case 'upgrade':
|
|
817
|
+
await upgradeCmd(rest)
|
|
818
|
+
break
|
|
791
819
|
case 'init':
|
|
792
820
|
await initProject(rest)
|
|
793
821
|
break
|
|
@@ -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
|
+
}
|