@ancleto/spec 0.6.23 → 0.6.24

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
@@ -228,6 +228,8 @@ Cada entrada tiene un `scope`: `project` (default, entra en `<ProjectMemoryRules
228
228
 
229
229
  > El mismo motor se puede consultar a mano desde la terminal: `ancleto memory context --scope project`. El archivo `.ancleto/working-context.md` que lee el orquestador se regenera solo en `init`, `install --project` y `upgrade`.
230
230
 
231
+ **Dos memorias, sin mezcla.** La memoria **del repositorio** (esta, `.ancleto/memory.db`) guarda reglas y decisiones del proyecto y se comparte con el equipo. Si el runtime expone además una memoria **del agente** (por ejemplo engram, habilitable con `--with-engram`), esa guarda notas de sesión, no del repo. **Una entrada vive en una sola**: si un futuro agente del equipo debería encontrarla, va al repo.
232
+
231
233
  ---
232
234
 
233
235
  ## Comandos del ciclo SDD en tu IDE
@@ -20,6 +20,8 @@ You are the single owner of the repository's memory (`.ancleto/memory.db`, SQLit
20
20
 
21
21
  The memory is shared across sessions: what you write, someone else recalls months later, in another change, without today's context. A wrong or noisy entry is worse than no entry, because it is retrieved as precedent.
22
22
 
23
+ **Boundary:** the repository memory (yours) is distinct from any *agent/session* memory service the runtime may expose (e.g. engram). Repo rules and decisions live here, never duplicated there; transient, personal session notes belong to that other service, not here. When in doubt, it goes here — this is the one that outlives the repo and is shared with the team. The frontier is documented for projects in `AGENTS.md`.
24
+
23
25
  ## Rule vs Decision
24
26
 
25
27
  Classify every candidate BEFORE choosing the tool:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ancleto/spec",
3
- "version": "0.6.23",
3
+ "version": "0.6.24",
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
@@ -352,15 +352,40 @@ function mergeLocked(source, local, filename) {
352
352
  let result = local
353
353
  for (const [name, sourceBlock] of blocks) {
354
354
  const replaced = replaceLockedBlock(result, name, sourceBlock)
355
- if (replaced === null) {
355
+ if (replaced !== null) {
356
+ result = replaced
357
+ continue
358
+ }
359
+ // Si el nombre del bloque aparece igual en el archivo (p. ej. tag de cierre faltante),
360
+ // esta mal formado: se avisa y NO se toca, para no duplicar contenido.
361
+ const nameSeen = new RegExp(`<!--\\s*LOCKED:\\s*${escapeRe(name)}\\s*-->`).test(result)
362
+ if (nameSeen) {
356
363
  console.warn(`ancleto: no se pudo actualizar el bloque LOCKED "${name}" en ${filename} (tags ausentes o mal formados)`)
364
+ continue
365
+ }
366
+ // El bloque es nuevo (feature agregada al template): se inserta sin tocar el resto.
367
+ const anchor = findInsertAnchor(result, source, name)
368
+ if (anchor === null) {
369
+ console.warn(`ancleto: no se pudo insertar el bloque LOCKED "${name}" en ${filename}`)
357
370
  } else {
358
- result = replaced
371
+ result = result.slice(0, anchor) + sourceBlock + '\n\n' + result.slice(anchor)
359
372
  }
360
373
  }
361
374
  return result
362
375
  }
363
376
 
377
+ // Punto de insercion para un bloque LOCKED nuevo: justo antes del bloque LOCKED
378
+ // que lo sigue en el template, o al final del archivo si es el ultimo.
379
+ function findInsertAnchor(local, source, name) {
380
+ const order = [...extractLockedBlocks(source).keys()]
381
+ const nextInSource = order[order.indexOf(name) + 1]
382
+ if (nextInSource && extractLockedBlocks(local).has(nextInSource)) {
383
+ const at = local.search(new RegExp(`<!--\\s*LOCKED:\\s*${escapeRe(nextInSource)}\\s*-->`))
384
+ if (at >= 0) return at
385
+ }
386
+ return local.length
387
+ }
388
+
364
389
  async function copyTemplates(projectDir) {
365
390
  const dest = join(projectDir, '.opencode')
366
391
  await copyAssets(dest)
@@ -50,6 +50,22 @@ conflicto, prevalece el diseño vigente.
50
50
  - Si la memoria contradice el estado actual del código o los guardrails de este archivo,
51
51
  reportá la discrepancia en lugar de aplicarla a ciegas.
52
52
 
53
+ <!-- LOCKED: memory-boundary -->
54
+ ### Frontera: memoria del repo vs memoria del agente
55
+
56
+ Hay **dos memorias distintas** y no se mezclan:
57
+
58
+ | Memoria | Qué guarda | Dónde vive | Herramientas |
59
+ |---|---|---|---|
60
+ | **Del repositorio** | Reglas y decisiones **del proyecto**: convenciones, contratos, por qué se eligió algo | `.ancleto/memory.db` (versionable con el equipo) | `searchMemory`, `recordRule`, `recordDecision` |
61
+ | **Del agente** (si está habilitada) | Notas **de sesión/agente**: observaciones transitorias, contexto entre compactaciones | Servicio externo del agente | Las de ese servicio (p. ej. engram) |
62
+
63
+ Regla: **una entrada vive en una sola memoria, nunca en ambas.** Si es una decisión o regla
64
+ que un futuro agente del equipo debería encontrar → memoria del repo. Si es una nota personal
65
+ de la sesión → memoria del agente. Ante la duda, va al repo: es la que sobrevive al repo y se
66
+ comparte.
67
+ <!-- /LOCKED: memory-boundary -->
68
+
53
69
  ## Flujo Spec-Driven (aspec)
54
70
 
55
71
  - **Cambios con scope incierto / arquitectura**: Crear artifacts en `aspec/changes/<name>/`.