@ancleto/spec 0.6.6 → 0.6.7

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
@@ -106,7 +106,7 @@ ancleto init --agent opencode --tier normal
106
106
 
107
107
  * `normal`: Modelos balanceados sin restricciones agresivas de contexto (default).
108
108
  * `minimo`: Enfoque en el modelo pago más económico viable, con alta compresión de contexto y exclusión de tests/docs en el discovery.
109
- * `gratis`: Bloqueado a modelos gratuitos (ej. `opencode/big-pickle`), empaquetado ultra-agresivo y un límite estricto (budget) de tokens enviado al LLM.
109
+ * `gratis`: Bloqueado a modelos gratuitos, empaquetado ultra-agresivo y un límite estricto (budget) de tokens enviado al LLM. Si tenés acceso a **Muse Spark 1.3 Free** (`opencode/muse-spark-1.3-contributor-free`, no abierto a todos), se usa ese; si no, se usa `opencode/big-pickle`. En modo interactivo el instalador te lo pregunta; la elección queda guardada en `.ancletorc` (`gratisModel`).
110
110
 
111
111
  La configuración se preserva en `.ancletorc` (raíz del proyecto) y `.ancleto-tier` (junto a la configuración instalada).
112
112
 
@@ -211,3 +211,28 @@ ancleto init --with-azure
211
211
  ```
212
212
 
213
213
  Esto escribe `azure.enabled: true` en tu `.ancletorc`. Solo deberás completar la sección de Azure en el archivo `PRODUCT.md` e instalar su extensión (`az extension add --name azure-devops`).
214
+
215
+ ---
216
+
217
+ ## 🤖 Referencia: Agentes y Modelos por Tier
218
+
219
+ Los 10 agentes instalados y el modelo que usa cada uno según tu tier. Al instalar, el nivel elegido se escribe en la línea `model:` de cada agente (y se re-aplica en cada `update`).
220
+
221
+ | Agente | `normal` (default) | `minimo` | `gratis` |
222
+ |---|---|---|---|
223
+ | orchestrator | `opencode-go/qwen3.7-plus` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
224
+ | coder | `opencode-go/minimax-m3` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
225
+ | tester | `opencode-go/deepseek-v4.1-flash` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
226
+ | spec-writer | `opencode-go/qwen3.7-plus` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
227
+ | reviewer | `opencode-go/qwen3.6-plus` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
228
+ | technical-discovery | `opencode-go/deepseek-v4.1-flash` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
229
+ | technical-seed-writer | `opencode-go/minimax-m3` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
230
+ | memory-keeper | `opencode-go/deepseek-v4.1-flash` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
231
+ | context-resolver | `opencode-go/deepseek-v4.1-flash` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
232
+ | documenter | `opencode-go/deepseek-v4.1-flash` | `opencode-go/deepseek-v4.1-flash` | ver nota ↓ |
233
+
234
+ El tier `minimo` usa un único modelo (`deepseek-v4.1-flash`) para los 10 agentes: es el más económico del catálogo para coding (0.15/0.60 por millón de tokens) con 1M de contexto.
235
+
236
+ **Tier `gratis`:** los 10 agentes usan **Muse Spark 1.3 Free** (`opencode/muse-spark-1.3-contributor-free`) **si está disponible** en tu cuenta (no está abierto a todos). En una instalación interactiva el wizard te lo pregunta; la respuesta se guarda en `.ancletorc` (`gratisModel`) y se reutiliza en los siguientes `install`/`update`. Sin TTY (CI) se detecta con `opencode models` — con timeout — y si no se puede confirmar se usa `opencode/big-pickle`. El instalador siempre informa cuál aplicó (`ancleto: modelo gratis: ...`).
237
+
238
+ > Variable avanzada: `ANCLETO_MUSE_SPARK=1` fuerza Muse Spark, `ANCLETO_MUSE_SPARK=0` fuerza `big-pickle` (útil para tests o para fijar el comportamiento; tiene prioridad sobre la elección guardada).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Resolves intake grounding context (Work Item / card) before triage, so the orchestrator classifies and delegates with the real card content
3
3
  mode: subagent
4
- model: opencode-go/deepseek-v4-flash
4
+ model: opencode-go/deepseek-v4.1-flash
5
5
  temperature: 0.1
6
6
  color: '#8b5cf6'
7
7
  tools:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Finalizes and archives completed aspec changes in alignment with the aspec lifecycle
3
3
  mode: subagent
4
- model: opencode-go/deepseek-v4-flash
4
+ model: opencode-go/deepseek-v4.1-flash
5
5
  temperature: 0.1
6
6
  color: '#10b981'
7
7
  tools:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Owns the repository memory (.ancleto/memory.db) — recalls prior lessons at intake and records rules and decisions at close, so a finding survives the session that produced it
3
3
  mode: subagent
4
- model: opencode-go/deepseek-v4-flash
4
+ model: opencode-go/deepseek-v4.1-flash
5
5
  temperature: 0.1
6
6
  color: '#f59e0b'
7
7
  tools:
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Internal repository-context specialist, invoked only by the orchestrator to avoid repository sweeps
3
3
  mode: subagent
4
- model: opencode-go/deepseek-v4-flash
4
+ model: opencode-go/deepseek-v4.1-flash
5
5
  temperature: 0.1
6
6
  color: '#0ea5e9'
7
7
  tools:
package/agents/tester.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  description: Writes and updates tests, and verifies implementation against approved change inputs
3
3
  mode: subagent
4
- model: opencode-go/deepseek-v4-flash
4
+ model: opencode-go/deepseek-v4.1-flash
5
5
  temperature: 0.1
6
6
  color: '#3b82f6'
7
7
  tools:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancleto/spec",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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
@@ -12,6 +12,7 @@ import { memoryDoctor } from '../core/memory/doctor.js'
12
12
  import { writeDiscoveryMap } from '../core/discovery.js'
13
13
  import { readProjectTier, buildRepomixArgs, tierTokenBudget } from '../core/repomix-tier.js'
14
14
  import { showBanner, selectOption, stopBanner } from './ui.js'
15
+ import { tierModels, gratisModel, envGratisModel, isKnownGratisModel, MUSE_SPARK_MODEL, GRATIS_FALLBACK_MODEL } from '../core/tier-models.js'
15
16
 
16
17
  const __dirname = dirname(fileURLToPath(import.meta.url))
17
18
  const ROOT = join(__dirname, '..', '..')
@@ -137,26 +138,26 @@ const TIERS = {
137
138
  normal: {
138
139
  orchestrator: 'opencode-go/qwen3.7-plus',
139
140
  coder: 'opencode-go/minimax-m3',
140
- tester: 'opencode-go/deepseek-v4-flash',
141
+ tester: 'opencode-go/deepseek-v4.1-flash',
141
142
  'spec-writer': 'opencode-go/qwen3.7-plus',
142
143
  reviewer: 'opencode-go/qwen3.6-plus',
143
- 'technical-discovery': 'opencode-go/deepseek-v4-flash',
144
+ 'technical-discovery': 'opencode-go/deepseek-v4.1-flash',
144
145
  'technical-seed-writer': 'opencode-go/minimax-m3',
145
- 'memory-keeper': 'opencode-go/deepseek-v4-flash',
146
- 'context-resolver': 'opencode-go/deepseek-v4-flash',
147
- documenter: 'opencode-go/deepseek-v4-flash'
146
+ 'memory-keeper': 'opencode-go/deepseek-v4.1-flash',
147
+ 'context-resolver': 'opencode-go/deepseek-v4.1-flash',
148
+ documenter: 'opencode-go/deepseek-v4.1-flash'
148
149
  },
149
150
  minimo: {
150
- orchestrator: 'opencode-go/deepseek-v4-flash',
151
- coder: 'opencode-go/minimax-m2.7',
152
- tester: 'opencode-go/deepseek-v4-flash',
153
- 'spec-writer': 'opencode-go/qwen3.6-plus',
154
- reviewer: 'opencode-go/deepseek-v4-flash',
155
- 'technical-discovery': 'opencode-go/deepseek-v4-flash',
156
- 'technical-seed-writer': 'opencode-go/minimax-m2.7',
157
- 'memory-keeper': 'opencode-go/deepseek-v4-flash',
158
- 'context-resolver': 'opencode-go/deepseek-v4-flash',
159
- documenter: 'opencode-go/deepseek-v4-flash'
151
+ orchestrator: 'opencode-go/deepseek-v4.1-flash',
152
+ coder: 'opencode-go/deepseek-v4.1-flash',
153
+ tester: 'opencode-go/deepseek-v4.1-flash',
154
+ 'spec-writer': 'opencode-go/deepseek-v4.1-flash',
155
+ reviewer: 'opencode-go/deepseek-v4.1-flash',
156
+ 'technical-discovery': 'opencode-go/deepseek-v4.1-flash',
157
+ 'technical-seed-writer': 'opencode-go/deepseek-v4.1-flash',
158
+ 'memory-keeper': 'opencode-go/deepseek-v4.1-flash',
159
+ 'context-resolver': 'opencode-go/deepseek-v4.1-flash',
160
+ documenter: 'opencode-go/deepseek-v4.1-flash'
160
161
  },
161
162
  gratis: {
162
163
  orchestrator: 'opencode/big-pickle',
@@ -176,8 +177,8 @@ function tierStatePath(targetDir) {
176
177
  return join(targetDir, '.ancleto-tier')
177
178
  }
178
179
 
179
- async function applyTier(agentsDir, tier) {
180
- const map = TIERS[tier]
180
+ async function applyTier(agentsDir, tier, models = null) {
181
+ const map = models || TIERS[tier]
181
182
  for (const [name, model] of Object.entries(map)) {
182
183
  const p = join(agentsDir, name + '.md')
183
184
  if (!(await exists(p))) continue
@@ -389,9 +390,11 @@ async function install(args) {
389
390
 
390
391
  let agent = agentFlag || (existingRc?.agent && SUPPORTED_AGENTS.includes(existingRc.agent) ? existingRc.agent : null)
391
392
 
393
+ let bannerShown = false
392
394
  const isInteractive = Boolean(process.stdout.isTTY) && ((projectDir && !agent) || !tier)
393
395
  if (isInteractive) {
394
396
  await showBanner()
397
+ bannerShown = true
395
398
  if (projectDir && !agent) {
396
399
  agent = await selectOption('Agente/IDE', SUPPORTED_AGENTS, Math.max(0, SUPPORTED_AGENTS.indexOf(DEFAULT_AGENT)))
397
400
  }
@@ -405,12 +408,29 @@ async function install(args) {
405
408
  if (projectDir && !agent) agent = DEFAULT_AGENT
406
409
  if (!tier) tier = TIERS[storedTier] ? storedTier : 'normal'
407
410
 
411
+ let gratisModelChoice = null
412
+ if (tier === 'gratis') {
413
+ const env = envGratisModel()
414
+ const persisted = isKnownGratisModel(existingRc?.gratisModel) ? existingRc.gratisModel : null
415
+ if (env || persisted) {
416
+ gratisModelChoice = env || persisted
417
+ } else if (process.stdout.isTTY) {
418
+ if (!bannerShown) await showBanner()
419
+ const answer = await selectOption('Muse Spark 1.3 Free disponible en tu cuenta?', ['No (usar big-pickle)', 'Si (Muse Spark)'], 0)
420
+ if (!bannerShown) stopBanner()
421
+ gratisModelChoice = answer.startsWith('Si') ? MUSE_SPARK_MODEL : GRATIS_FALLBACK_MODEL
422
+ } else {
423
+ gratisModelChoice = gratisModel()
424
+ }
425
+ }
426
+
408
427
  if (projectDir) {
409
428
  await copyTemplates(projectDir)
410
429
  await scaffoldAspec(projectDir)
411
430
  const agentSkillsDir = await installAgentSkills(projectDir, agent)
412
431
  await writeManifest(projectDir, {
413
432
  agent,
433
+ ...(gratisModelChoice ? { gratisModel: gratisModelChoice } : {}),
414
434
  installedPaths: {
415
435
  templates: ['AGENTS.md', 'PRODUCT.md'],
416
436
  agents: ['.opencode/agents'],
@@ -422,7 +442,8 @@ async function install(args) {
422
442
  await copyAssets(target)
423
443
  }
424
444
 
425
- await applyTier(join(target, 'agents'), tier)
445
+ const models = tierModels(tier, TIERS, gratisModelChoice)
446
+ await applyTier(join(target, 'agents'), tier, models)
426
447
  await writeFile(tierStatePath(target), tier + '\n')
427
448
 
428
449
  let azureMcp = false
@@ -440,6 +461,7 @@ async function install(args) {
440
461
  : `${target} (disponible en todos tus proyectos)`
441
462
  console.log(`ancleto: instalado en ${loc}`)
442
463
  console.log(`ancleto: tier de costo de agents: ${tier}`)
464
+ if (tier === 'gratis') console.log(`ancleto: modelo gratis: ${models.orchestrator}`)
443
465
  if (res.added.length) {
444
466
  console.log(`ancleto: MCP configurados: ${res.added.join(', ')} en ${res.file}`)
445
467
  }
@@ -0,0 +1,48 @@
1
+ import { spawnSync } from 'node:child_process'
2
+
3
+ export const MUSE_SPARK_MODEL = 'opencode/muse-spark-1.3-contributor-free'
4
+ export const GRATIS_FALLBACK_MODEL = 'opencode/big-pickle'
5
+
6
+ let probed = null
7
+
8
+ export function envGratisModel() {
9
+ if (process.env.ANCLETO_MUSE_SPARK === '1') return MUSE_SPARK_MODEL
10
+ if (process.env.ANCLETO_MUSE_SPARK === '0') return GRATIS_FALLBACK_MODEL
11
+ return null
12
+ }
13
+
14
+ export function isKnownGratisModel(model) {
15
+ return model === MUSE_SPARK_MODEL || model === GRATIS_FALLBACK_MODEL
16
+ }
17
+
18
+ export function detectMuseSpark() {
19
+ const env = envGratisModel()
20
+ if (env) return env === MUSE_SPARK_MODEL
21
+ if (probed !== null) return probed
22
+ try {
23
+ const r = spawnSync('opencode models opencode', {
24
+ encoding: 'utf8',
25
+ timeout: 5000,
26
+ shell: true,
27
+ stdio: ['ignore', 'pipe', 'pipe']
28
+ })
29
+ probed = !r.error && r.status === 0
30
+ && String(r.stdout || '').split('\n').some((l) => l.trim() === MUSE_SPARK_MODEL)
31
+ } catch {
32
+ probed = false
33
+ }
34
+ return probed
35
+ }
36
+
37
+ export function gratisModel(persisted = null) {
38
+ const env = envGratisModel()
39
+ if (env) return env
40
+ if (isKnownGratisModel(persisted)) return persisted
41
+ return detectMuseSpark() ? MUSE_SPARK_MODEL : GRATIS_FALLBACK_MODEL
42
+ }
43
+
44
+ export function tierModels(tier, tiers, gratisOverride = null) {
45
+ if (tier !== 'gratis') return tiers[tier]
46
+ const model = gratisOverride || gratisModel()
47
+ return Object.fromEntries(Object.keys(tiers.gratis).map((k) => [k, model]))
48
+ }