navori 0.2.3 → 0.2.5

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: commit-pr-pilot
3
3
  description: Redacta commit messages y abre PRs con título + body siguiendo el formato del repo. Corre pre-flight contra git/gh antes de tocar la red.
4
- tools: Read, Bash
4
+ tools: Read, Glob, Grep, Bash
5
5
  model: {{models.commitPrPilot}}
6
6
  ---
7
7
 
@@ -37,11 +37,7 @@ git diff origin/{{prTarget}}...HEAD --stat # scope REAL del PR (contr
37
37
  gh auth status # gh autenticado
38
38
  ```
39
39
 
40
- Si el harness está activo:
41
-
42
- ```bash
43
- grep -li 'APPROVED' .claude/progress/review_*.md 2>/dev/null
44
- ```
40
+ Si el harness está activo, verifica que exista un review aprobado con la tool nativa `Grep` (read-only, no pide permiso): `pattern: "APPROVED"`, `path: ".claude/progress"`, `glob: "review_*.md"`, `output_mode: "files_with_matches"`.
45
41
 
46
42
  Sin `APPROVED` y con harness activo → abort, dile al usuario que falta review.
47
43
 
@@ -22,8 +22,8 @@ Si la pregunta es puntual ("¿dónde está X?"), no eres tú — es `researcher`
22
22
 
23
23
  ## Protocolo
24
24
 
25
- 1. Lee `CLAUDE.md` y `.claude/AGENTS.md` para entender convenciones del repo.
26
- 2. Define el alcance: una carpeta, un módulo lógico, un patrón de archivos. Si el alcance no está claro, devuelve `blocked` y pide precisión.
25
+ 1. Lee `CLAUDE.md` para entender convenciones del repo.
26
+ 2. Define el alcance: una carpeta, un módulo lógico, un patrón de archivos. El orquestador debería pasártelo preciso; si llega ambiguo, devuelve `blocked` nombrando las opciones (carpeta X / módulo Y / patrón Z) para que reenvíe acotado — no adivines.
27
27
  3. Recorre desde los entry points (rutas, exports raíz del módulo, `index.ts`) hacia las hojas. Para cada nivel, lista archivos y su rol breve.
28
28
  4. Identifica dependencias inversas: ¿qué módulos externos consumen este módulo? Eso indica el "blast radius" de cambiar algo acá.
29
29
  5. Escribe `.claude/progress/explore_<area>.md`:
@@ -11,7 +11,7 @@ Ejecutas **una sola** tarea desde inicio hasta verificación. No orquestas, no l
11
11
 
12
12
  ## Protocolo
13
13
 
14
- 1. **Lee** `CLAUDE.md` y `.claude/AGENTS.md` (si existe). Identifica las convenciones del repo y las "Reglas del proyecto" del leader.
14
+ 1. **Lee** `CLAUDE.md`. Identifica las convenciones del repo y las "Reglas del proyecto" (la sección del orquestador en `CLAUDE.md`).
15
15
  2. **Anota** en `.claude/progress/current.md`:
16
16
  - `Tarea: <descripción breve>`
17
17
  - `Root cause: <archivo:línea + por qué>` (solo si la tarea es bugfix; no puedes tocar código sin esto).
@@ -12,7 +12,7 @@ Tu único trabajo es **descomponer y coordinar**, nunca implementar.
12
12
  ## Protocolo de arranque
13
13
 
14
14
  1. Lee `CLAUDE.md` (stack, convenciones, quality gate).
15
- 2. Lee `.claude/AGENTS.md` si existe (índice de agentes y skills).
15
+ 2. El catálogo de subagentes y skills está en `CLAUDE.md` (`## Agentes disponibles`, `## Skills disponibles`).
16
16
  3. Lee `.claude/progress/current.md` si existe — estado de la sesión anterior.
17
17
  4. Identifica el scope de la tarea contra las "Reglas del proyecto" abajo (legacy paths, áreas críticas, convenciones del repo).
18
18
  5. **¿Llega texto de un ticket (Jira/Linear/GitHub/Slack)?** Si matchea los triggers de tu agente `ticket-audit` (bug en feature crítica, migración estructural, feature que cruza >3 capas), invoca primero ese agente — produce `.claude/progress/audit_<ID>.md` que orienta toda la descomposición posterior. Para tickets triviales (typo, copy, color), sáltate el audit.
@@ -37,6 +37,27 @@ Cuando arranques una tarea compleja con audit previo, **pásale al implementer l
37
37
 
38
38
  Para investigación previa con preguntas acotadas, usa `researcher`. Para mapas exploratorios amplios (¿dónde vive X en el repo?), usa `explorer`. En Claude Code puedes referenciar `subagent_type: "Explore"` cuando exista; en otros engines, los reemplazos viven aquí.
39
39
 
40
+ ## Cómo lanzar en paralelo (mecánica, no opcional)
41
+
42
+ El paralelismo es una herramienta **analítica**, no solo de velocidad: el valor está en cómo partes el problema —en piezas genuinamente independientes, con criterio— y en cómo integras lo que vuelve. Lanzar agentes por lanzar no sirve; descomponer bien y sintetizar a fondo, sí. La velocidad es la consecuencia, no el objetivo.
43
+
44
+ La mecánica: cuando la tabla dice "en paralelo" (N `implementer`, 2–3 `researcher`/`explorer`), eso se logra emitiendo TODAS las llamadas a `Agent` en un MISMO turno — no una, esperar su `done -> archivo`, y luego la siguiente. Claude por defecto las lanza en serie; el paralelo hay que pedirlo explícito, en un solo mensaje.
45
+
46
+ - ✅ En un solo mensaje, invoca `Agent` 3 veces (`explorer` auth, `explorer` db, `explorer` api). Corren concurrentes y el tiempo total ≈ el del más lento.
47
+ - ❌ Invocar `Agent` para auth, esperar su resultado, luego db, luego api. Eso es serie y tira justo el tiempo que el paralelo ahorra.
48
+
49
+ Regla: sub-tareas **independientes** (no comparten estado ni una depende del output de otra) → MISMO turno. Serializa solo con dependencia real (`implementer` → `reviewer`: el review necesita el diff; un `explorer` cuyo scope sale de lo que descubrió otro).
50
+
51
+ **`implementer` en paralelo: solo con archivos disjuntos (que no se pisen).** Investigar y revisar es read-only, así que paralelizar `researcher`/`explorer`/`reviewer` nunca choca. Pero dos `implementer` a la vez SÍ se pisan si tocan el mismo archivo: uno sobrescribe el diff del otro. Lánzalos en paralelo SOLO cuando sus scopes de escritura no se solapan (1 bug por módulo aislado, archivos distintos). Antes de abrir el abanico de implementers, reparte el scope explícitamente —"tú tocas `a/`, tú `b/`"— y si dos sub-tareas tocarían el mismo archivo, van en SERIE. En la duda, serie.
52
+
53
+ ### Investigación en abanico → síntesis (el patrón que más agiliza)
54
+
55
+ Para una pregunta amplia, **descompónla en sub-preguntas independientes y lanza un `researcher`/`explorer` por cada una EN PARALELO** (mismo turno). Cada uno reúne evidencia de su área y la escribe en su archivo de progreso. Tú no investigas en serie ni te quedas con el primer hallazgo.
56
+
57
+ Cuando vuelven los `done -> archivo`, **recopila y analiza a fondo TÚ**: lee los N archivos juntos, cruza los hallazgos (contradicciones, gaps, qué se repite, qué falta), y recién entonces decides la descomposición de la implementación. El fan-out es para reunir evidencia rápido y en ancho; la síntesis profunda —con todo junto sobre la mesa— es trabajo tuyo, no se delega. Si la primera ronda deja huecos, lanza otra tanda de investigadores en paralelo sobre esos huecos.
58
+
59
+ Los investigadores son hojas (no tienen `Agent`): el abanico lo abres tú. Cada investigador, eso sí, paraleliza sus PROPIAS búsquedas internas (varios `Grep`/`Read` en un turno).
60
+
40
61
  ## Ejecución continua (no pausar entre tareas)
41
62
 
42
63
  Una vez aprobado el plan/scope, ejecuta TODAS las sub-tareas sin pausar para pedir confirmación al usuario. Razones válidas para parar:
@@ -22,10 +22,11 @@ Si la pregunta es amplia ("mapéame todo el módulo X"), no eres tú — es `exp
22
22
 
23
23
  ## Protocolo
24
24
 
25
- 1. Lee `CLAUDE.md` y `.claude/AGENTS.md` para entender el contexto del repo.
26
- 2. Acota la pregunta: si tiene >2 sub-preguntas, pide al leader que la divida o pártelamisma en sub-investigaciones serializadas.
25
+ 1. Lee `CLAUDE.md` para entender el contexto del repo.
26
+ 2. Trabaja UNA pregunta acotada (el orquestador ya te pasó el scope). Si descubres que en realidad son >2 preguntas independientes, devuélvelas listadas para que el orquestador las reparta en investigadores paralelos — no las encadenes tú en serie.
27
27
  3. Ejecuta la búsqueda:
28
- - `grep -rn`, `git grep`, `find`, `Glob` herramientas read-only.
28
+ - Método primario: las tools nativas `Grep` (contenido) y `Glob` (archivos por nombre/patrón). Son read-only, rápidas (ripgrep) y no piden permiso.
29
+ - Fallback solo para lo que las tools no cubren (historial git con `git grep`, metadata del FS con `find`): comandos por shell. Encadenados con pipes/redirects piden confirmación, así que reserva el shell para cuando `Grep`/`Glob` no alcancen.
29
30
  - Para preguntas semánticas (no solo string match), lee los archivos identificados completos.
30
31
  4. Valida cada hallazgo: abre el archivo, confirma que la coincidencia significa lo que parece (a veces un `grep` matchea comentarios o strings ajenos al concepto).
31
32
  5. Escribe `.claude/progress/research_<slug-de-la-pregunta>.md`:
@@ -13,7 +13,7 @@ Eres un revisor estricto. Tu única función es **aprobar o rechazar**. No edita
13
13
 
14
14
  ### Setup (común a las dos pasadas)
15
15
 
16
- 1. Lee `CLAUDE.md`, `.claude/AGENTS.md`, `.claude/progress/impl_<feature>.md`, `.claude/progress/audit_<ID>.md` (si existe).
16
+ 1. Lee `CLAUDE.md`, `.claude/progress/impl_<feature>.md`, `.claude/progress/audit_<ID>.md` (si existe).
17
17
  2. Identifica archivos modificados:
18
18
 
19
19
  ```bash
@@ -37,7 +37,7 @@ Si encuentras un audit reciente para el mismo ticket, léelo primero. No re-audi
37
37
 
38
38
  ## Flujo
39
39
 
40
- 1. **Lee**: `CLAUDE.md`, `.claude/AGENTS.md`, "Reglas del proyecto" del leader.
40
+ 1. **Lee**: `CLAUDE.md` (reglas del proyecto + el rol del orquestador).
41
41
  2. **Cura contexto del repo** para tu análisis:
42
42
  - Texto literal del ticket (no parafrasees).
43
43
  - Grep por keywords del ticket → archivos candidatos.
@@ -2,10 +2,10 @@
2
2
 
3
3
  Antes de tocar código, valida que el harness está sano (checkpoint de arranque):
4
4
 
5
- 1. **Contexto**: lee `CLAUDE.md`, `.claude/AGENTS.md` (si existe) y `progress/current.md` para retomar dónde quedó la sesión anterior. Si el repo usa memoria persistente, recupera contexto previo.
5
+ 1. **Contexto**: lee `CLAUDE.md` (incluye tu rol de orquestador y el catálogo `## Agentes disponibles`) y `progress/current.md` para retomar dónde quedó la sesión anterior. Si el repo usa memoria persistente, recupera contexto previo.
6
6
  2. **Config sana**: si `navori.config.json` o `.claude/` se ven inconsistentes, corre `navori doctor` antes de seguir.
7
7
  3. **Gates listos**: los quality gates que el repo declara corren de verdad (binarios en PATH, toolchains opt-in bootstrapeados). Un gate declarado que no ejecuta es deuda silenciosa — instálalo o anota la deuda en `progress/current.md`.
8
8
  4. **Branch de trabajo**: confirma que no estás sobre la branch base (`{{branchBase}}`).
9
- 5. **Tarea acotada**: ten claro el alcance de ESTA tarea antes de empezar. Una tarea a la vez; si el pedido trae varias, descompón primero.
9
+ 5. **Tarea acotada**: ten claro el alcance de ESTA tarea. Una tarea **de usuario** a la vez (no mezcles pedidos distintos); pero la descompones en sub-tareas y, si son independientes, las lanzas en paralelo — ver tu rol de orquestador.
10
10
 
11
11
  Este checkpoint es el espejo de **Cierre de sesión** (más abajo): arrancas sano, cierras limpio.
@@ -5,5 +5,6 @@ Read-only por default. Antes de mutar datos, esquema o infraestructura (DB, stor
5
5
  - **DB / queries**: por default solo lectura (`SELECT`, `EXPLAIN`, flags tipo `onlyRead`). `INSERT/UPDATE/DELETE/DROP/ALTER/TRUNCATE` requieren que el usuario lo pida de forma explícita.
6
6
  - **Comandos de shell**: inspeccionar es libre (`ls`, `cat`, `git status/diff/log`). Los destructivos (`rm -rf`, `git reset --hard`, force-push, `chmod -R`) los manda el harness a `ask`/`deny` y un hook los bloquea — no intentes evadir esa capa.
7
7
  - **Búsqueda de código**: usa las tools nativas `Glob` (archivos por nombre/patrón) y `Grep` (contenido). Son read-only, más rápidas (ripgrep por debajo) y ya saltan `node_modules`/`.git`, así que no piden permiso. Reserva `find`/`grep` por shell para lo que las tools no cubren — búsqueda por metadata del FS (`-size`, `-mtime`, permisos) — y úsalo solo cuando sea críticamente necesario. `find` no está pre-aprobado a propósito: con `-exec`/`-delete` no es read-only puro, así que pedir permiso ahí es la red de seguridad correcta, no un estorbo.
8
+ - **Operaciones independientes → en paralelo**: cuando hagas varias cosas que no dependen entre sí (varias lecturas, varios `Grep`/`Glob`, o lanzar varios subagentes), emítelas en un MISMO turno —varias tool calls juntas en un solo mensaje—, no una por una esperando cada resultado. Claude por defecto va en serie; el paralelo hay que pedirlo. Serializa solo cuando una operación necesita el resultado de la anterior.
8
9
  - **Si una mutación destructiva es legítima y necesaria**: explica qué hace y por qué, y deja que el usuario la confirme o la corra. Nunca la disfraces con variables, subshells o `--no-verify` para saltarte el gate.
9
10
  - **Datos sensibles**: no vuelques secretos, PII ni dumps completos a logs, chat o archivos del repo.
@@ -0,0 +1,15 @@
1
+ ## Rol: orquestador
2
+
3
+ Ante una tarea no trivial **actúas como el `leader`** (`.claude/agents/leader.md`): descompones y coordinas, no implementas el código tú directamente. La inteligencia de orquestación —tabla de escalado, paralelismo, síntesis— vive en ese archivo; encárnala. El catálogo de subagentes está en "## Agentes disponibles".
4
+
5
+ ### Cómo operas
6
+
7
+ - **Descompón** la tarea y, para cada pieza, **lanza el subagente apropiado** vía la tool `Agent`: investigación → `researcher`/`explorer`; implementación → `implementer`; validación → `reviewer`; cierre con PR → `commit-pr-pilot`.
8
+ - **Paraleliza lo independiente**: si necesitas varios investigadores (o varios `implementer` de scopes disjuntos), **emite todas las llamadas `Agent` en un mismo turno** — no una, esperar, otra. Es la palanca que más agiliza. El detalle (fan-out → síntesis, implementers que no se pisen) está en `leader.md`.
9
+ - **Sintetiza tú**: los subagentes escriben en `.claude/progress/<archivo>.md` y te devuelven solo la referencia. Recopila los N y analiza a fondo antes de decidir.
10
+
11
+ ### Cuándo NO orquestar (hazlo tú directo)
12
+
13
+ - Pregunta conceptual o lectura pura → responde sin subagentes.
14
+ - Cambios en `docs/`, `.claude/`, `CLAUDE.md`, `progress/` → edítalos tú.
15
+ - Una sola línea trivial en un archivo conocido → puede no valer el overhead.
package/dist/index.js CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { defineCommand as defineCommand16, runMain } from "citty";
5
- import { readFileSync as readFileSync21 } from "fs";
6
- import { dirname as dirname9, resolve as resolve24 } from "path";
5
+ import { readFileSync as readFileSync22 } from "fs";
6
+ import { dirname as dirname10, resolve as resolve24 } from "path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "url";
8
8
 
9
9
  // src/commands/init.ts
10
10
  import { defineCommand as defineCommand2 } from "citty";
11
11
  import * as p2 from "@clack/prompts";
12
- import { resolve as resolve11, join as join11, dirname as dirname6, relative as relative3 } from "path";
13
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, chmodSync as chmodSync2 } from "fs";
12
+ import { resolve as resolve11, join as join12, dirname as dirname7, relative as relative3 } from "path";
13
+ import { existsSync as existsSync16, mkdirSync as mkdirSync6, chmodSync as chmodSync2 } from "fs";
14
14
  import { spawnSync as spawnSync2 } from "child_process";
15
15
 
16
16
  // src/lib/config.ts
@@ -1374,7 +1374,7 @@ function writeWorkspace(workspace) {
1374
1374
  // src/commands/render.ts
1375
1375
  import { defineCommand } from "citty";
1376
1376
  import * as p from "@clack/prompts";
1377
- import { existsSync as existsSync12 } from "fs";
1377
+ import { existsSync as existsSync13 } from "fs";
1378
1378
  import { resolve as resolve9 } from "path";
1379
1379
 
1380
1380
  // src/engines/claude/index.ts
@@ -1677,6 +1677,7 @@ function placeholderFallback(path) {
1677
1677
  // src/lib/render-plan.ts
1678
1678
  var CORE_SOURCE_ID = "@navori/core";
1679
1679
  var CORE_MANAGED_ASSETS = [
1680
+ { id: "orquestacion", relPath: "core-assets/managed/orquestacion.md", availableLanguages: ["es"] },
1680
1681
  { id: "idioma-rol", relPath: "core-assets/managed/idioma-rol.md", availableLanguages: ["es"] },
1681
1682
  { id: "formato-respuesta", relPath: "core-assets/managed/formato-respuesta.md", availableLanguages: ["es"] },
1682
1683
  { id: "tipado-fuerte", relPath: "core-assets/managed/tipado-fuerte.md", availableLanguages: ["es"], condition: "project.typedLanguage" },
@@ -2339,6 +2340,34 @@ function buildSkillsIndexBody(config, localSkills, repoRoot) {
2339
2340
  ""
2340
2341
  ].join("\n");
2341
2342
  }
2343
+ var AGENTS_INDEX_ID = "agentes-disponibles";
2344
+ var AGENT_WHEN = {
2345
+ implementer: "Escribe c\xF3digo y tests de UNA tarea acotada con scope claro.",
2346
+ reviewer: "Valida un diff contra spec y calidad antes de cerrar (APPROVED / CHANGES_REQUESTED).",
2347
+ researcher: "Responde una pregunta concreta del repo (\xBFpasa Y? \xBFqu\xE9 consume X?) con evidencia citada.",
2348
+ explorer: "Mapea un \xE1rea o m\xF3dulo amplio: estructura, entry points, dependencias.",
2349
+ "ticket-audit": "Analiza a fondo un ticket complejo (bug cr\xEDtico, migraci\xF3n, feature multi-capa) antes de descomponer.",
2350
+ "commit-pr-pilot": "Redacta commits Conventional y abre el PR tras la aprobaci\xF3n del reviewer."
2351
+ };
2352
+ function buildAgentsIndexBody(config) {
2353
+ const rows = [];
2354
+ for (const agent of CORE_AGENTS) {
2355
+ if (agent.id === "leader") continue;
2356
+ if (!isAgentEnabled(config, agent.harnessKey)) continue;
2357
+ const when = AGENT_WHEN[agent.id];
2358
+ if (!when) continue;
2359
+ rows.push(`- \`${agent.id}\` \u2014 ${when}`);
2360
+ }
2361
+ if (rows.length === 0) return null;
2362
+ return [
2363
+ "## Agentes disponibles",
2364
+ "",
2365
+ 'Subagentes que puedes lanzar v\xEDa la tool `Agent` (t\xFA eres el orquestador; ver "## Rol: orquestador"). Investigaci\xF3n y review son read-only \u2192 paralel\xEDzalos sin miedo.',
2366
+ "",
2367
+ ...rows,
2368
+ ""
2369
+ ].join("\n");
2370
+ }
2342
2371
  var CONTEXTO_PROYECTO_ID = "contexto-proyecto";
2343
2372
  function buildContextoProyectoBody(config) {
2344
2373
  const proj = config.project ?? {};
@@ -2422,6 +2451,26 @@ function renderClaudeEngine(cwd, inputConfig, options = {}) {
2422
2451
  } else {
2423
2452
  claudeMdContent = removeManagedSection(claudeMdContent, SKILLS_INDEX_ID);
2424
2453
  }
2454
+ const agentsIndexBody = buildAgentsIndexBody(config);
2455
+ if (agentsIndexBody !== null) {
2456
+ const result = injectManagedSection(
2457
+ claudeMdContent,
2458
+ AGENTS_INDEX_ID,
2459
+ agentsIndexBody,
2460
+ CORE_META,
2461
+ "html",
2462
+ options.forceIds?.has(AGENTS_INDEX_ID) ?? false
2463
+ );
2464
+ claudeMdContent = result.output;
2465
+ claudeMdPlan.entries.push({
2466
+ asset: { id: AGENTS_INDEX_ID, relPath: "(computed)" },
2467
+ source: "core",
2468
+ status: result.status,
2469
+ newContent: null
2470
+ });
2471
+ } else {
2472
+ claudeMdContent = removeManagedSection(claudeMdContent, AGENTS_INDEX_ID);
2473
+ }
2425
2474
  const contextoBody = buildContextoProyectoBody(config);
2426
2475
  if (contextoBody !== null) {
2427
2476
  const result = injectManagedSection(
@@ -2846,6 +2895,96 @@ function planPluginScript(cwd, script, config) {
2846
2895
  };
2847
2896
  }
2848
2897
 
2898
+ // src/engines/agents-md/index.ts
2899
+ import { existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync12 } from "fs";
2900
+ import { basename as basename4, dirname as dirname6, join as join10 } from "path";
2901
+ var MANAGED_ID = "navori-agents";
2902
+ var CORE_META2 = { source: "@navori/core", version: readBundledCoreVersion() };
2903
+ var CORE_SKILLS2 = ["verify-before-done", "loop-back-debug", "review-diff"];
2904
+ var HEADER = "# AGENTS.md\n";
2905
+ var USER_SECTION = "\n<!-- navori:user-section -->\n## Reglas del repo (tuyas)\n\n<!-- Agrega ac\xE1 lo espec\xEDfico de tu repo; navori no toca esta secci\xF3n. -->\n";
2906
+ function buildSkillsSection(config, repoRoot) {
2907
+ const rows = [];
2908
+ const listed = /* @__PURE__ */ new Set();
2909
+ for (const id of CORE_SKILLS2) {
2910
+ rows.push(`- \`${id}\` \u2014 navori`);
2911
+ listed.add(id);
2912
+ }
2913
+ if (config.preset && config.preset !== "custom") {
2914
+ try {
2915
+ const loaded = loadPreset(config.preset, repoRoot);
2916
+ for (const e of loaded?.def.extras.skills ?? []) {
2917
+ if (e.condition) continue;
2918
+ const name = basename4(e.destRelPath).replace(/\.md$/, "");
2919
+ if (listed.has(name)) continue;
2920
+ rows.push(`- \`${name}\` \u2014 preset (\`${config.preset}\`)`);
2921
+ listed.add(name);
2922
+ }
2923
+ } catch {
2924
+ }
2925
+ }
2926
+ for (const id of config.project?.libraries ?? []) {
2927
+ if (listed.has(id) || !librarySkillById(id)) continue;
2928
+ rows.push(`- \`${id}\` \u2014 library (detected)`);
2929
+ listed.add(id);
2930
+ }
2931
+ if (rows.length === 0) return null;
2932
+ return ["## Skills disponibles", "", ...rows, ""].join("\n");
2933
+ }
2934
+ function buildWorkflowSection() {
2935
+ return [
2936
+ "## Flujo de trabajo",
2937
+ "",
2938
+ "- Para tareas no triviales: an\xE1lisis \u2192 plan \u2192 implementaci\xF3n. De una en una.",
2939
+ "- Antes de codear: \xBFes lo m\xE1s simple? \xBFlegible en 6 meses? \xBFmantiene el patr\xF3n existente?",
2940
+ "- Busca con herramientas read-only (no leas el repo entero); cita `archivo:l\xEDnea`.",
2941
+ "- Cierra con el quality gate del proyecto en verde antes de dar por terminado.",
2942
+ ""
2943
+ ].join("\n");
2944
+ }
2945
+ function buildManagedBody(config, repoRoot) {
2946
+ const plan = computeRenderPlan("", config, repoRoot);
2947
+ const ruleBlocks = plan.entries.filter(
2948
+ (e) => e.newContent != null && e.status !== "removed-condition-false" && e.asset.id !== "orquestacion" && (e.source === "core" || e.source === config.preset)
2949
+ ).map((e) => e.newContent.trim());
2950
+ const skills = buildSkillsSection(config, repoRoot);
2951
+ const sections = [
2952
+ "> Contexto del proyecto generado por navori. Lo leen Cursor, Codex, Gemini y Copilot.",
2953
+ ...ruleBlocks,
2954
+ ...skills ? [skills] : [],
2955
+ buildWorkflowSection()
2956
+ ];
2957
+ return sections.join("\n\n").trim() + "\n";
2958
+ }
2959
+ function renderAgentsMdEngine(cwd, inputConfig, options = {}) {
2960
+ const config = effectiveConfig(inputConfig);
2961
+ const repoRoot = options.repoRoot ?? cwd;
2962
+ const agentsMdPath = join10(cwd, "AGENTS.md");
2963
+ const firstRender = !existsSync12(agentsMdPath);
2964
+ const existing = firstRender ? HEADER : readFileSync12(agentsMdPath, "utf-8");
2965
+ const body = buildManagedBody(config, repoRoot);
2966
+ const result = injectManagedSection(existing, MANAGED_ID, body, CORE_META2, "html");
2967
+ const output = firstRender ? result.output + USER_SECTION : result.output;
2968
+ const written = [];
2969
+ const skipped = [];
2970
+ let backupPath = null;
2971
+ if (result.status === "user-modified-skipped") {
2972
+ skipped.push({ path: "AGENTS.md", reason: "managed block edited by hand" });
2973
+ } else if (result.status === "unchanged") {
2974
+ } else {
2975
+ written.push({ path: "AGENTS.md", status: result.status });
2976
+ if (!options.dryRun) {
2977
+ if (!firstRender) {
2978
+ const handle = createBackup(cwd, ["AGENTS.md"]);
2979
+ if (handle.files.length > 0) backupPath = handle.path;
2980
+ }
2981
+ mkdirSync5(dirname6(agentsMdPath), { recursive: true });
2982
+ writeFileAtomic(agentsMdPath, output);
2983
+ }
2984
+ }
2985
+ return { written, skipped, warnings: [], backupPath };
2986
+ }
2987
+
2849
2988
  // src/lib/style.ts
2850
2989
  import pc from "picocolors";
2851
2990
  var color = pc;
@@ -2923,6 +3062,25 @@ function effectiveConfigForWorkspace(root, workspace) {
2923
3062
  }
2924
3063
 
2925
3064
  // src/commands/render.ts
3065
+ function renderNonClaudeEngines(cwd, config, engines, dryRun) {
3066
+ const out = [];
3067
+ for (const eng of engines) {
3068
+ if (eng === "claude") continue;
3069
+ if (eng === "agents-md") {
3070
+ const r = renderAgentsMdEngine(cwd, config, { dryRun, repoRoot: cwd });
3071
+ out.push({ engine: eng, ...r });
3072
+ } else {
3073
+ out.push({
3074
+ engine: eng,
3075
+ written: [],
3076
+ skipped: [],
3077
+ warnings: [`El engine '${eng}' todav\xEDa no tiene adapter en navori; se omiti\xF3.`],
3078
+ backupPath: null
3079
+ });
3080
+ }
3081
+ }
3082
+ return out;
3083
+ }
2926
3084
  function runRender(cwd, dryRunOrOptions = false, force = false) {
2927
3085
  const opts = typeof dryRunOrOptions === "boolean" ? { dryRun: dryRunOrOptions, force } : dryRunOrOptions;
2928
3086
  const dryRun = Boolean(opts.dryRun);
@@ -2930,7 +3088,7 @@ function runRender(cwd, dryRunOrOptions = false, force = false) {
2930
3088
  const workspaceFilter = opts.workspaceFilter ?? null;
2931
3089
  const configPath = `${cwd}/navori.config.json`;
2932
3090
  const claudeMdPath = `${cwd}/CLAUDE.md`;
2933
- if (!existsSync12(configPath)) {
3091
+ if (!existsSync13(configPath)) {
2934
3092
  return {
2935
3093
  ok: false,
2936
3094
  reason: `No navori.config.json at ${configPath}`,
@@ -3006,38 +3164,44 @@ function runRender(cwd, dryRunOrOptions = false, force = false) {
3006
3164
  ]
3007
3165
  };
3008
3166
  }
3009
- const engineResult = renderClaudeEngine(cwd, config, { dryRun, force: forceFlag });
3167
+ const engines = config.engines ?? ["claude"];
3168
+ const renderClaude = engines.includes("claude");
3169
+ const engineResult = renderClaude ? renderClaudeEngine(cwd, config, { dryRun, force: forceFlag }) : void 0;
3010
3170
  const workspaces = [];
3011
- for (const ws of config.monorepo?.workspaces ?? []) {
3012
- const wsCwd = resolve9(cwd, ws.path);
3013
- const wsConfig = effectiveConfigForWorkspace(config, ws);
3014
- const wsResult = renderClaudeEngine(wsCwd, wsConfig, {
3015
- dryRun,
3016
- force: forceFlag,
3017
- repoRoot: cwd
3018
- });
3019
- workspaces.push({
3020
- workspacePath: ws.path,
3021
- workspaceName: ws.name,
3022
- filePath: `${wsCwd}/CLAUDE.md`,
3023
- entries: wsResult.claudeMdEntries,
3024
- written: wsResult.written.length > 0,
3025
- languageFallbacks: wsResult.languageFallbacks,
3026
- updatesAvailable: wsResult.updatesAvailable,
3027
- backupPath: wsResult.backupPath,
3028
- engineResult: wsResult
3029
- });
3171
+ if (renderClaude) {
3172
+ for (const ws of config.monorepo?.workspaces ?? []) {
3173
+ const wsCwd = resolve9(cwd, ws.path);
3174
+ const wsConfig = effectiveConfigForWorkspace(config, ws);
3175
+ const wsResult = renderClaudeEngine(wsCwd, wsConfig, {
3176
+ dryRun,
3177
+ force: forceFlag,
3178
+ repoRoot: cwd
3179
+ });
3180
+ workspaces.push({
3181
+ workspacePath: ws.path,
3182
+ workspaceName: ws.name,
3183
+ filePath: `${wsCwd}/CLAUDE.md`,
3184
+ entries: wsResult.claudeMdEntries,
3185
+ written: wsResult.written.length > 0,
3186
+ languageFallbacks: wsResult.languageFallbacks,
3187
+ updatesAvailable: wsResult.updatesAvailable,
3188
+ backupPath: wsResult.backupPath,
3189
+ engineResult: wsResult
3190
+ });
3191
+ }
3030
3192
  }
3193
+ const extraEngines = renderNonClaudeEngines(cwd, config, engines, dryRun);
3031
3194
  return {
3032
3195
  ok: true,
3033
3196
  filePath: claudeMdPath,
3034
- entries: engineResult.claudeMdEntries,
3035
- written: engineResult.written.length > 0,
3036
- languageFallbacks: engineResult.languageFallbacks,
3037
- updatesAvailable: engineResult.updatesAvailable,
3038
- backupPath: engineResult.backupPath,
3197
+ entries: engineResult?.claudeMdEntries ?? [],
3198
+ written: (engineResult?.written.length ?? 0) > 0,
3199
+ languageFallbacks: engineResult?.languageFallbacks ?? [],
3200
+ updatesAvailable: engineResult?.updatesAvailable ?? [],
3201
+ backupPath: engineResult?.backupPath ?? null,
3039
3202
  engineResult,
3040
- workspaces
3203
+ workspaces,
3204
+ extraEngines
3041
3205
  };
3042
3206
  }
3043
3207
  var renderCommand = defineCommand({
@@ -3068,7 +3232,7 @@ var renderCommand = defineCommand({
3068
3232
  benchStart();
3069
3233
  const cwd = resolve9(args.cwd ?? process.cwd());
3070
3234
  p.intro(brand("render"));
3071
- if (!existsSync12(cwd)) {
3235
+ if (!existsSync13(cwd)) {
3072
3236
  p.cancel(`Directory not found: ${cwd}`);
3073
3237
  process.exit(1);
3074
3238
  }
@@ -3113,8 +3277,17 @@ var renderCommand = defineCommand({
3113
3277
  p.log.message(`${dim("Backup:")} ${ws.backupPath}`);
3114
3278
  }
3115
3279
  }
3280
+ for (const ee of result.extraEngines ?? []) {
3281
+ p.log.message(`${dim("engine")} ${color.cyan(ee.engine)}`);
3282
+ for (const w of ee.written) {
3283
+ p.log.message(` ${renderStatusSymbol(w.status)} ${w.path} ${dim("(")}${renderStatusLabel(w.status)}${dim(")")}`);
3284
+ }
3285
+ for (const s of ee.skipped) p.log.warn(` ${s.path}: ${s.reason}`);
3286
+ for (const warn of ee.warnings) p.log.warn(` ${warn}`);
3287
+ if (ee.backupPath) p.log.message(` ${dim("Backup:")} ${ee.backupPath}`);
3288
+ }
3116
3289
  const allEntries = result.entries.concat(...result.workspaces.map((w) => w.entries));
3117
- const anyPending = result.written || result.workspaces.some((w) => w.written);
3290
+ const anyPending = result.written || result.workspaces.some((w) => w.written) || (result.extraEngines ?? []).some((e) => e.written.length > 0);
3118
3291
  const summary = summarize(allEntries);
3119
3292
  if (preview) {
3120
3293
  if (anyPending) {
@@ -3480,7 +3653,7 @@ function formatWorkspaceSummary(ws, lang = "es") {
3480
3653
  }
3481
3654
 
3482
3655
  // src/engines/claude/prompts-loader.ts
3483
- import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
3656
+ import { readFileSync as readFileSync13, existsSync as existsSync14 } from "fs";
3484
3657
  import { resolve as resolve10 } from "path";
3485
3658
  import { z as z7 } from "zod";
3486
3659
  var SelectOptionSchema = z7.object({
@@ -3520,10 +3693,10 @@ function loadPrompts(enabledPlugins) {
3520
3693
  }
3521
3694
  function loadCorePrompts(warnings) {
3522
3695
  const path = resolve10(getCoreRoot(), CORE_PROMPTS_REL);
3523
- if (!existsSync13(path)) return [];
3696
+ if (!existsSync14(path)) return [];
3524
3697
  let parsed;
3525
3698
  try {
3526
- parsed = JSON.parse(readFileSync12(path, "utf-8"));
3699
+ parsed = JSON.parse(readFileSync13(path, "utf-8"));
3527
3700
  } catch (err) {
3528
3701
  warnings.push(`core prompts.json no parsea: ${err.message}`);
3529
3702
  return [];
@@ -3537,8 +3710,8 @@ function loadCorePrompts(warnings) {
3537
3710
  }
3538
3711
 
3539
3712
  // src/lib/scan.ts
3540
- import { existsSync as existsSync14, readdirSync as readdirSync6 } from "fs";
3541
- import { join as join10 } from "path";
3713
+ import { existsSync as existsSync15, readdirSync as readdirSync6 } from "fs";
3714
+ import { join as join11 } from "path";
3542
3715
  function scanMonorepoWorkspaces(cwd) {
3543
3716
  const patterns = collectWorkspacePatterns(cwd);
3544
3717
  if (patterns.length === 0) return [];
@@ -3565,9 +3738,9 @@ function walk(cwd, accum, remaining) {
3565
3738
  }
3566
3739
  const [head, ...tail] = remaining;
3567
3740
  const currentRel = accum.join("/");
3568
- const currentAbs = currentRel ? join10(cwd, currentRel) : cwd;
3741
+ const currentAbs = currentRel ? join11(cwd, currentRel) : cwd;
3569
3742
  if (head === "*") {
3570
- if (!existsSync14(currentAbs)) return [];
3743
+ if (!existsSync15(currentAbs)) return [];
3571
3744
  let entries;
3572
3745
  try {
3573
3746
  entries = readdirSync6(currentAbs, { withFileTypes: true });
@@ -3576,8 +3749,8 @@ function walk(cwd, accum, remaining) {
3576
3749
  }
3577
3750
  return entries.filter((d) => d.isDirectory() && !d.name.startsWith(".")).flatMap((d) => walk(cwd, [...accum, d.name], tail));
3578
3751
  }
3579
- const next = join10(currentAbs, head);
3580
- if (!existsSync14(next)) return [];
3752
+ const next = join11(currentAbs, head);
3753
+ if (!existsSync15(next)) return [];
3581
3754
  return walk(cwd, [...accum, head], tail);
3582
3755
  }
3583
3756
  function diffWorkspaces(detected, configured) {
@@ -3589,8 +3762,8 @@ function diffWorkspaces(detected, configured) {
3589
3762
  return { added, existing, orphan };
3590
3763
  }
3591
3764
  function describeWorkspace(cwd, relPath) {
3592
- const abs = join10(cwd, relPath);
3593
- if (!existsSync14(join10(abs, "package.json"))) return null;
3765
+ const abs = join11(cwd, relPath);
3766
+ if (!existsSync15(join11(abs, "package.json"))) return null;
3594
3767
  const project = detectProject(abs);
3595
3768
  return {
3596
3769
  name: project.name ?? relPath.split("/").pop(),
@@ -3671,11 +3844,11 @@ var initCommand = defineCommand2({
3671
3844
  p2.intro(brand("init"));
3672
3845
  const bootstrapLang = normalizeLang(args.lang) ?? "es";
3673
3846
  const bootstrapT = t(bootstrapLang);
3674
- if (!existsSync15(cwd)) {
3847
+ if (!existsSync16(cwd)) {
3675
3848
  p2.cancel(bootstrapT.dirNotFound(cwd));
3676
3849
  process.exit(1);
3677
3850
  }
3678
- if (existsSync15(configPath)) {
3851
+ if (existsSync16(configPath)) {
3679
3852
  p2.cancel(bootstrapT.configExists(configPath));
3680
3853
  process.exit(1);
3681
3854
  }
@@ -4289,7 +4462,7 @@ ${lines}${more}`);
4289
4462
  }
4290
4463
  }
4291
4464
  async function offerPreCommitHook(cwd, opts) {
4292
- if (!existsSync15(join11(cwd, ".git"))) return;
4465
+ if (!existsSync16(join12(cwd, ".git"))) return;
4293
4466
  let wanted = opts.force;
4294
4467
  if (!wanted && !opts.autoYes) {
4295
4468
  const answer = await p2.confirm({
@@ -4308,10 +4481,10 @@ async function offerPreCommitHook(cwd, opts) {
4308
4481
  }
4309
4482
  }
4310
4483
  function writePreCommitHook(cwd) {
4311
- const huskyDir = join11(cwd, ".husky");
4312
- const hookPath = existsSync15(huskyDir) ? join11(huskyDir, "pre-commit") : join11(cwd, ".git", "hooks", "pre-commit");
4484
+ const huskyDir = join12(cwd, ".husky");
4485
+ const hookPath = existsSync16(huskyDir) ? join12(huskyDir, "pre-commit") : join12(cwd, ".git", "hooks", "pre-commit");
4313
4486
  const relPath = relative3(cwd, hookPath);
4314
- if (existsSync15(hookPath)) return { ok: false, path: relPath };
4487
+ if (existsSync16(hookPath)) return { ok: false, path: relPath };
4315
4488
  const body = [
4316
4489
  "#!/usr/bin/env sh",
4317
4490
  "# navori pre-commit drift gate \u2014 scaffolded by 'navori init' (opt-in).",
@@ -4323,7 +4496,7 @@ function writePreCommitHook(cwd) {
4323
4496
  "fi",
4324
4497
  ""
4325
4498
  ].join("\n");
4326
- mkdirSync5(dirname6(hookPath), { recursive: true });
4499
+ mkdirSync6(dirname7(hookPath), { recursive: true });
4327
4500
  writeFileAtomic(hookPath, body);
4328
4501
  try {
4329
4502
  chmodSync2(hookPath, 493);
@@ -4548,15 +4721,15 @@ function formatProjectValue(v) {
4548
4721
  // src/commands/doctor.ts
4549
4722
  import { defineCommand as defineCommand3 } from "citty";
4550
4723
  import * as p3 from "@clack/prompts";
4551
- import { existsSync as existsSync17, readFileSync as readFileSync14, readdirSync as readdirSync8 } from "fs";
4552
- import { join as join13, resolve as resolve12, relative as relative4 } from "path";
4724
+ import { existsSync as existsSync18, readFileSync as readFileSync15, readdirSync as readdirSync8 } from "fs";
4725
+ import { join as join14, resolve as resolve12, relative as relative4 } from "path";
4553
4726
 
4554
4727
  // src/lib/health.ts
4555
- import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
4556
- import { join as join12 } from "path";
4728
+ import { existsSync as existsSync17, readFileSync as readFileSync14, readdirSync as readdirSync7 } from "fs";
4729
+ import { join as join13 } from "path";
4557
4730
  function listMarkers(filePath) {
4558
- if (!existsSync16(filePath)) return [];
4559
- const content = readFileSync13(filePath, "utf-8");
4731
+ if (!existsSync17(filePath)) return [];
4732
+ const content = readFileSync14(filePath, "utf-8");
4560
4733
  const re = /<!-- navori:managed [^>]*-->/g;
4561
4734
  const result = [];
4562
4735
  for (const match of content.matchAll(re)) {
@@ -4601,10 +4774,10 @@ function scanManagedDrift(cwd, config) {
4601
4774
  }
4602
4775
  }
4603
4776
  const files = [];
4604
- if (existsSync16(join12(cwd, "CLAUDE.md"))) files.push("CLAUDE.md");
4777
+ if (existsSync17(join13(cwd, "CLAUDE.md"))) files.push("CLAUDE.md");
4605
4778
  for (const dir of [".claude/agents", ".claude/skills"]) {
4606
- const absDir = join12(cwd, dir);
4607
- if (!existsSync16(absDir)) continue;
4779
+ const absDir = join13(cwd, dir);
4780
+ if (!existsSync17(absDir)) continue;
4608
4781
  try {
4609
4782
  for (const file of readdirSync7(absDir)) {
4610
4783
  if (file.endsWith(".md")) files.push(`${dir}/${file}`);
@@ -4614,10 +4787,10 @@ function scanManagedDrift(cwd, config) {
4614
4787
  }
4615
4788
  }
4616
4789
  for (const rel of files) {
4617
- const abs = join12(cwd, rel);
4790
+ const abs = join13(cwd, rel);
4618
4791
  const fileContent = (() => {
4619
4792
  try {
4620
- return readFileSync13(abs, "utf-8");
4793
+ return readFileSync14(abs, "utf-8");
4621
4794
  } catch {
4622
4795
  return null;
4623
4796
  }
@@ -4699,7 +4872,7 @@ var doctorCommand = defineCommand3({
4699
4872
  const configPath = `${cwd}/navori.config.json`;
4700
4873
  const claudeMdPath = `${cwd}/CLAUDE.md`;
4701
4874
  if (!args.json) p3.intro(brand("doctor"));
4702
- if (!existsSync17(cwd)) {
4875
+ if (!existsSync18(cwd)) {
4703
4876
  if (args.json) {
4704
4877
  console.log(JSON.stringify({ ok: false, error: "directory-missing", cwd }));
4705
4878
  } else {
@@ -4707,7 +4880,7 @@ var doctorCommand = defineCommand3({
4707
4880
  }
4708
4881
  process.exit(1);
4709
4882
  }
4710
- if (!existsSync17(configPath)) {
4883
+ if (!existsSync18(configPath)) {
4711
4884
  if (args.json) {
4712
4885
  console.log(JSON.stringify({ ok: false, error: "config-missing", configPath }));
4713
4886
  } else {
@@ -4753,10 +4926,10 @@ var doctorCommand = defineCommand3({
4753
4926
  configPath,
4754
4927
  config,
4755
4928
  checks: {
4756
- claudeMdExists: existsSync17(claudeMdPath),
4757
- agentsMdExists: existsSync17(`${cwd}/AGENTS.md`),
4758
- claudeDirExists: existsSync17(`${cwd}/.claude`),
4759
- progressDirExists: existsSync17(`${cwd}/${config.progress?.dir ?? "progress"}`)
4929
+ claudeMdExists: existsSync18(claudeMdPath),
4930
+ agentsMdExists: existsSync18(`${cwd}/AGENTS.md`),
4931
+ claudeDirExists: existsSync18(`${cwd}/.claude`),
4932
+ progressDirExists: existsSync18(`${cwd}/${config.progress?.dir ?? "progress"}`)
4760
4933
  },
4761
4934
  managedBlocks: markers,
4762
4935
  missingPlugins,
@@ -4844,7 +5017,7 @@ ${lines.join("\n")}`
4844
5017
  );
4845
5018
  }
4846
5019
  const missingLocalSkills = (config.project?.localSkills ?? []).filter(
4847
- (name) => !existsSync17(join13(cwd, ".claude/skills", `${name}.md`))
5020
+ (name) => !existsSync18(join14(cwd, ".claude/skills", `${name}.md`))
4848
5021
  );
4849
5022
  if (missingLocalSkills.length > 0) {
4850
5023
  const lines = missingLocalSkills.map(
@@ -4903,10 +5076,10 @@ ${lines.join("\n")}`
4903
5076
  }
4904
5077
  });
4905
5078
  function scanCorruptedSettings(cwd) {
4906
- const path = join13(cwd, ".claude/settings.json");
4907
- if (!existsSync17(path)) return [];
5079
+ const path = join14(cwd, ".claude/settings.json");
5080
+ if (!existsSync18(path)) return [];
4908
5081
  try {
4909
- JSON.parse(readFileSync14(path, "utf-8"));
5082
+ JSON.parse(readFileSync15(path, "utf-8"));
4910
5083
  return [];
4911
5084
  } catch (err) {
4912
5085
  return [{ path: ".claude/settings.json", error: err.message }];
@@ -4926,7 +5099,7 @@ function scanMissingPresetFiles(cwd, config) {
4926
5099
  const missing = [];
4927
5100
  for (const e of [...managed, ...agents, ...skills, ...hooks]) {
4928
5101
  const abs = resolve12(loaded.assetRoot, e.relPath);
4929
- if (!existsSync17(abs)) missing.push({ id: e.id, path: relative4(cwd, abs) });
5102
+ if (!existsSync18(abs)) missing.push({ id: e.id, path: relative4(cwd, abs) });
4930
5103
  }
4931
5104
  return missing;
4932
5105
  }
@@ -4962,15 +5135,15 @@ function scanMissingInvariants(cwd, config) {
4962
5135
  }
4963
5136
  function readRenderedText(cwd) {
4964
5137
  const parts = [];
4965
- const claudeMd = join13(cwd, "CLAUDE.md");
4966
- if (existsSync17(claudeMd)) {
5138
+ const claudeMd = join14(cwd, "CLAUDE.md");
5139
+ if (existsSync18(claudeMd)) {
4967
5140
  try {
4968
- parts.push(readFileSync14(claudeMd, "utf-8"));
5141
+ parts.push(readFileSync15(claudeMd, "utf-8"));
4969
5142
  } catch {
4970
5143
  }
4971
5144
  }
4972
- const claudeDir = join13(cwd, ".claude");
4973
- if (existsSync17(claudeDir)) collectText(claudeDir, parts);
5145
+ const claudeDir = join14(cwd, ".claude");
5146
+ if (existsSync18(claudeDir)) collectText(claudeDir, parts);
4974
5147
  return parts.join("\n");
4975
5148
  }
4976
5149
  function collectText(dir, parts) {
@@ -4981,12 +5154,12 @@ function collectText(dir, parts) {
4981
5154
  return;
4982
5155
  }
4983
5156
  for (const entry of entries) {
4984
- const abs = join13(dir, entry.name);
5157
+ const abs = join14(dir, entry.name);
4985
5158
  if (entry.isDirectory()) {
4986
5159
  collectText(abs, parts);
4987
5160
  } else if (entry.isFile() && TEXT_EXTENSIONS.some((e) => entry.name.endsWith(e))) {
4988
5161
  try {
4989
- parts.push(readFileSync14(abs, "utf-8"));
5162
+ parts.push(readFileSync15(abs, "utf-8"));
4990
5163
  } catch {
4991
5164
  }
4992
5165
  }
@@ -5018,8 +5191,8 @@ function collectAssignments(config) {
5018
5191
  // src/commands/sync.ts
5019
5192
  import { defineCommand as defineCommand4 } from "citty";
5020
5193
  import * as p4 from "@clack/prompts";
5021
- import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
5022
- import { resolve as resolve13, join as join14 } from "path";
5194
+ import { existsSync as existsSync19, readFileSync as readFileSync16 } from "fs";
5195
+ import { resolve as resolve13, join as join15 } from "path";
5023
5196
 
5024
5197
  // src/lib/diff.ts
5025
5198
  function formatLineDiff(current, proposed, _options = {}) {
@@ -5064,11 +5237,11 @@ var syncCommand = defineCommand4({
5064
5237
  const cwd = resolve13(args.cwd ?? process.cwd());
5065
5238
  const configPath = `${cwd}/navori.config.json`;
5066
5239
  p4.intro(brand("sync"));
5067
- if (!existsSync18(cwd)) {
5240
+ if (!existsSync19(cwd)) {
5068
5241
  p4.cancel(`Directory not found: ${cwd}`);
5069
5242
  process.exit(1);
5070
5243
  }
5071
- if (!existsSync18(configPath)) {
5244
+ if (!existsSync19(configPath)) {
5072
5245
  p4.cancel(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
5073
5246
  process.exit(1);
5074
5247
  }
@@ -5223,8 +5396,8 @@ async function resolveConflictsInteractively(plans) {
5223
5396
  (e) => e.status === "user-modified-skipped"
5224
5397
  );
5225
5398
  if (cmConflicts.length === 0) continue;
5226
- const claudeMdPath = join14(tp.target.cwd, "CLAUDE.md");
5227
- const existing = existsSync18(claudeMdPath) ? readFileSync15(claudeMdPath, "utf-8") : "";
5399
+ const claudeMdPath = join15(tp.target.cwd, "CLAUDE.md");
5400
+ const existing = existsSync19(claudeMdPath) ? readFileSync16(claudeMdPath, "utf-8") : "";
5228
5401
  const skipIds = /* @__PURE__ */ new Set();
5229
5402
  const forceIds = /* @__PURE__ */ new Set();
5230
5403
  for (const e of cmConflicts) {
@@ -5316,13 +5489,13 @@ function summarize2(writtenCount, conflictCount) {
5316
5489
  // src/commands/add.ts
5317
5490
  import { defineCommand as defineCommand5 } from "citty";
5318
5491
  import * as p5 from "@clack/prompts";
5319
- import { existsSync as existsSync20, readFileSync as readFileSync16 } from "fs";
5492
+ import { existsSync as existsSync21, readFileSync as readFileSync17 } from "fs";
5320
5493
  import { resolve as resolve14 } from "path";
5321
5494
  import { spawnSync as spawnSync3 } from "child_process";
5322
5495
 
5323
5496
  // src/lib/which.ts
5324
- import { existsSync as existsSync19, statSync as statSync6 } from "fs";
5325
- import { join as join15 } from "path";
5497
+ import { existsSync as existsSync20, statSync as statSync6 } from "fs";
5498
+ import { join as join16 } from "path";
5326
5499
  function hasBinary(name) {
5327
5500
  const pathEnv = process.env.PATH ?? "";
5328
5501
  const sep2 = process.platform === "win32" ? ";" : ":";
@@ -5330,8 +5503,8 @@ function hasBinary(name) {
5330
5503
  const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
5331
5504
  for (const dir of dirs) {
5332
5505
  for (const ext of exts) {
5333
- const candidate = join15(dir, name + ext);
5334
- if (existsSync19(candidate)) {
5506
+ const candidate = join16(dir, name + ext);
5507
+ if (existsSync20(candidate)) {
5335
5508
  try {
5336
5509
  if (statSync6(candidate).isFile()) return true;
5337
5510
  } catch {
@@ -5407,11 +5580,11 @@ var addCommand = defineCommand5({
5407
5580
  } else {
5408
5581
  p5.intro(brand(`add ${accent(args.plugin)}`));
5409
5582
  }
5410
- if (!existsSync20(cwd)) {
5583
+ if (!existsSync21(cwd)) {
5411
5584
  p5.cancel(`Directory not found: ${cwd}`);
5412
5585
  process.exit(1);
5413
5586
  }
5414
- if (!existsSync20(configPath)) {
5587
+ if (!existsSync21(configPath)) {
5415
5588
  p5.cancel(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
5416
5589
  process.exit(1);
5417
5590
  }
@@ -5445,7 +5618,7 @@ var addCommand = defineCommand5({
5445
5618
  ...config.plugins ?? {},
5446
5619
  [plugin.manifest.id]: { enabled: true }
5447
5620
  };
5448
- const raw = JSON.parse(readFileSync16(configPath, "utf-8"));
5621
+ const raw = JSON.parse(readFileSync17(configPath, "utf-8"));
5449
5622
  writeConfig(configPath, { ...raw, plugins: updatedPlugins });
5450
5623
  p5.log.success(`Added '${plugin.manifest.id}' to ${configPath}`);
5451
5624
  }
@@ -5530,8 +5703,8 @@ function printSuggestions(cwd, configPath) {
5530
5703
  // src/commands/workspace.ts
5531
5704
  import { defineCommand as defineCommand6 } from "citty";
5532
5705
  import * as p6 from "@clack/prompts";
5533
- import { existsSync as existsSync21 } from "fs";
5534
- import { join as join16 } from "path";
5706
+ import { existsSync as existsSync22 } from "fs";
5707
+ import { join as join17 } from "path";
5535
5708
 
5536
5709
  // src/lib/workspace-defaults.ts
5537
5710
  var VALID_DEFAULT_KEYS = "branchBase, prTarget, commits, language, engines, plugins.<id>.enabled";
@@ -5567,8 +5740,8 @@ function applyDefault(current, key, rawValue) {
5567
5740
 
5568
5741
  // src/commands/workspace.ts
5569
5742
  function readRepoConfigName(repoPath) {
5570
- const cfgPath = join16(repoPath, "navori.config.json");
5571
- if (!existsSync21(cfgPath)) return null;
5743
+ const cfgPath = join17(repoPath, "navori.config.json");
5744
+ if (!existsSync22(cfgPath)) return null;
5572
5745
  try {
5573
5746
  return readConfig(cfgPath).name;
5574
5747
  } catch {
@@ -5592,7 +5765,7 @@ var initSubCommand = defineCommand6({
5592
5765
  process.exit(1);
5593
5766
  }
5594
5767
  const path = workspacePath(name);
5595
- if (existsSync21(path)) {
5768
+ if (existsSync22(path)) {
5596
5769
  console.error(`Workspace '${name}' already exists at ${path}`);
5597
5770
  process.exit(1);
5598
5771
  }
@@ -5833,14 +6006,14 @@ var deleteSubCommand = defineCommand6({
5833
6006
  return;
5834
6007
  }
5835
6008
  }
5836
- const { renameSync: renameSync3, existsSync: existsSync31, mkdirSync: mkdirSync10 } = await import("fs");
6009
+ const { renameSync: renameSync3, existsSync: existsSync32, mkdirSync: mkdirSync11 } = await import("fs");
5837
6010
  const { join: joinPath } = await import("path");
5838
6011
  const { homedir: homedir2 } = await import("os");
5839
6012
  const trashRoot = joinPath(homedir2(), ".navori", ".trash");
5840
- mkdirSync10(trashRoot, { recursive: true });
6013
+ mkdirSync11(trashRoot, { recursive: true });
5841
6014
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5842
6015
  const dest = joinPath(trashRoot, `${name}-${ts}`);
5843
- if (existsSync31(dir)) renameSync3(dir, dest);
6016
+ if (existsSync32(dir)) renameSync3(dir, dest);
5844
6017
  p6.outro(`Moved to ${dest}. Restore manually if needed.`);
5845
6018
  }
5846
6019
  });
@@ -5948,7 +6121,7 @@ var renderSubCommand = defineCommand6({
5948
6121
  }
5949
6122
  const rows = [];
5950
6123
  for (const repo of ws.repos) {
5951
- if (!existsSync21(repo.path)) {
6124
+ if (!existsSync22(repo.path)) {
5952
6125
  rows.push({ name: repo.name, status: "missing", detail: repo.path });
5953
6126
  continue;
5954
6127
  }
@@ -6022,11 +6195,11 @@ var workspaceCommand = defineCommand6({
6022
6195
  // src/commands/ticket.ts
6023
6196
  import { defineCommand as defineCommand7 } from "citty";
6024
6197
  import * as p7 from "@clack/prompts";
6025
- import { readFileSync as readFileSync18 } from "fs";
6198
+ import { readFileSync as readFileSync19 } from "fs";
6026
6199
 
6027
6200
  // src/lib/tickets.ts
6028
- import { existsSync as existsSync22, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync7, mkdirSync as mkdirSync6, renameSync as renameSync2, rmSync as rmSync5 } from "fs";
6029
- import { join as join17, resolve as resolve15 } from "path";
6201
+ import { existsSync as existsSync23, readFileSync as readFileSync18, readdirSync as readdirSync9, statSync as statSync7, mkdirSync as mkdirSync7, renameSync as renameSync2, rmSync as rmSync5 } from "fs";
6202
+ import { join as join18, resolve as resolve15 } from "path";
6030
6203
  var TicketError = class extends NavoriError {
6031
6204
  constructor(message) {
6032
6205
  super("ticket-error", message);
@@ -6035,11 +6208,11 @@ var TicketError = class extends NavoriError {
6035
6208
  function ticketsDir(workspaceName) {
6036
6209
  const ws = loadWorkspace(workspaceName);
6037
6210
  if (!ws) throw new TicketError(`Workspace '${workspaceName}' not found`);
6038
- return join17(workspaceDirectory(workspaceName), ws.ticketsDir);
6211
+ return join18(workspaceDirectory(workspaceName), ws.ticketsDir);
6039
6212
  }
6040
6213
  function readTitle(path) {
6041
6214
  try {
6042
- const content = readFileSync17(path, "utf-8").split("\n");
6215
+ const content = readFileSync18(path, "utf-8").split("\n");
6043
6216
  for (const line of content) {
6044
6217
  const trimmed = line.trim();
6045
6218
  if (!trimmed) continue;
@@ -6052,13 +6225,13 @@ function readTitle(path) {
6052
6225
  }
6053
6226
  function listTickets(workspaceName) {
6054
6227
  const dir = ticketsDir(workspaceName);
6055
- if (!existsSync22(dir)) return [];
6228
+ if (!existsSync23(dir)) return [];
6056
6229
  const out = [];
6057
6230
  const collect = (folder, state) => {
6058
- if (!existsSync22(folder)) return;
6231
+ if (!existsSync23(folder)) return;
6059
6232
  for (const entry of readdirSync9(folder)) {
6060
6233
  if (!entry.endsWith(".md")) continue;
6061
- const full = join17(folder, entry);
6234
+ const full = join18(folder, entry);
6062
6235
  try {
6063
6236
  if (!statSync7(full).isFile()) continue;
6064
6237
  } catch {
@@ -6073,7 +6246,7 @@ function listTickets(workspaceName) {
6073
6246
  }
6074
6247
  };
6075
6248
  collect(dir, "active");
6076
- collect(join17(dir, "_archive"), "archive");
6249
+ collect(join18(dir, "_archive"), "archive");
6077
6250
  return out;
6078
6251
  }
6079
6252
  function findTicket(workspaceName, id) {
@@ -6108,9 +6281,9 @@ function archiveTicket(workspaceName, id) {
6108
6281
  if (!summary) throw new TicketError(`Ticket '${id}' not found in workspace '${workspaceName}'`);
6109
6282
  if (summary.state === "archive") return summary;
6110
6283
  const dir = ticketsDir(workspaceName);
6111
- const archiveDir = join17(dir, "_archive");
6112
- mkdirSync6(archiveDir, { recursive: true });
6113
- const dest = join17(archiveDir, `${id}.md`);
6284
+ const archiveDir = join18(dir, "_archive");
6285
+ mkdirSync7(archiveDir, { recursive: true });
6286
+ const dest = join18(archiveDir, `${id}.md`);
6114
6287
  renameSync2(summary.path, dest);
6115
6288
  return { id, path: dest, title: summary.title, state: "archive" };
6116
6289
  }
@@ -6124,9 +6297,9 @@ function createTicket(workspaceName, id, title) {
6124
6297
  throw new TicketError(`Invalid ticket id '${id}'. Use letters, digits, hyphens, underscores.`);
6125
6298
  }
6126
6299
  const dir = ticketsDir(workspaceName);
6127
- if (!existsSync22(dir)) throw new TicketError(`Tickets directory does not exist: ${dir}`);
6128
- const path = join17(dir, `${id}.md`);
6129
- if (existsSync22(path)) throw new TicketError(`Ticket '${id}' already exists at ${path}`);
6300
+ if (!existsSync23(dir)) throw new TicketError(`Tickets directory does not exist: ${dir}`);
6301
+ const path = join18(dir, `${id}.md`);
6302
+ if (existsSync23(path)) throw new TicketError(`Ticket '${id}' already exists at ${path}`);
6130
6303
  const finalTitle = title?.trim() || id;
6131
6304
  writeFileAtomic(path, defaultTemplate(id, finalTitle));
6132
6305
  return {
@@ -6141,10 +6314,10 @@ function findReferencingRepos(repoPaths, ticketId) {
6141
6314
  const idPattern = new RegExp(`\\b${ticketId}\\b`);
6142
6315
  for (const repoPath of repoPaths) {
6143
6316
  const abs = resolve15(repoPath);
6144
- const current = join17(abs, "progress", "current.md");
6145
- if (!existsSync22(current)) continue;
6317
+ const current = join18(abs, "progress", "current.md");
6318
+ if (!existsSync23(current)) continue;
6146
6319
  try {
6147
- const content = readFileSync17(current, "utf-8");
6320
+ const content = readFileSync18(current, "utf-8");
6148
6321
  const matches = [];
6149
6322
  for (const line of content.split("\n")) {
6150
6323
  if (idPattern.test(line)) matches.push(line.trim());
@@ -6231,7 +6404,7 @@ Create it with: navori ticket new ${args.workspace} ${args.id}
6231
6404
  const repoPaths = (ws?.repos ?? []).map((r) => r.path);
6232
6405
  const referencing = findReferencingRepos(repoPaths, args.id);
6233
6406
  if (args.json) {
6234
- const content = readFileSync18(ticket.path, "utf-8");
6407
+ const content = readFileSync19(ticket.path, "utf-8");
6235
6408
  console.log(JSON.stringify({ ticket, referencing, content }, null, 2));
6236
6409
  return;
6237
6410
  }
@@ -6243,7 +6416,7 @@ Create it with: navori ticket new ${args.workspace} ${args.id}
6243
6416
  ["path", ticket.path]
6244
6417
  ])
6245
6418
  );
6246
- p7.note(readFileSync18(ticket.path, "utf-8"), "Content");
6419
+ p7.note(readFileSync19(ticket.path, "utf-8"), "Content");
6247
6420
  if (referencing.length === 0) {
6248
6421
  p7.log.message(dim("Referenced in: (no repo's progress/current.md mentions this ticket)"));
6249
6422
  } else {
@@ -6377,7 +6550,7 @@ var ticketCommand = defineCommand7({
6377
6550
  // src/commands/configure.ts
6378
6551
  import { defineCommand as defineCommand8 } from "citty";
6379
6552
  import * as p8 from "@clack/prompts";
6380
- import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
6553
+ import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
6381
6554
  import { resolve as resolve16 } from "path";
6382
6555
  var ENGINE_OPTIONS2 = [
6383
6556
  { value: "claude", label: "Claude Code (.claude/)" },
@@ -6391,11 +6564,11 @@ function fail(msg) {
6391
6564
  process.exit(1);
6392
6565
  }
6393
6566
  function loadOrExit(cwd) {
6394
- if (!existsSync23(cwd)) fail(`Directory not found: ${cwd}`);
6567
+ if (!existsSync24(cwd)) fail(`Directory not found: ${cwd}`);
6395
6568
  const configPath = resolve16(cwd, "navori.config.json");
6396
- if (!existsSync23(configPath)) fail(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
6569
+ if (!existsSync24(configPath)) fail(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
6397
6570
  const config = readConfig(configPath);
6398
- const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
6571
+ const raw = JSON.parse(readFileSync20(configPath, "utf-8"));
6399
6572
  return { config, path: configPath, raw };
6400
6573
  }
6401
6574
  function persist(path, raw) {
@@ -6709,7 +6882,7 @@ var configureCommand = defineCommand8({
6709
6882
  // src/commands/update.ts
6710
6883
  import { defineCommand as defineCommand9 } from "citty";
6711
6884
  import * as p9 from "@clack/prompts";
6712
- import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
6885
+ import { existsSync as existsSync25, readFileSync as readFileSync21 } from "fs";
6713
6886
  import { resolve as resolve17 } from "path";
6714
6887
  function sameSet(a, b) {
6715
6888
  if (a.length !== b.length) return false;
@@ -6798,11 +6971,11 @@ var updateCommand = defineCommand9({
6798
6971
  const cwd = resolve17(args.cwd ?? process.cwd());
6799
6972
  const configPath = `${cwd}/navori.config.json`;
6800
6973
  p9.intro(brand("update"));
6801
- if (!existsSync24(cwd)) {
6974
+ if (!existsSync25(cwd)) {
6802
6975
  p9.cancel(`Directory not found: ${cwd}`);
6803
6976
  process.exit(1);
6804
6977
  }
6805
- if (!existsSync24(configPath)) {
6978
+ if (!existsSync25(configPath)) {
6806
6979
  p9.cancel(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
6807
6980
  process.exit(1);
6808
6981
  }
@@ -6863,7 +7036,7 @@ ${lines.join("\n")}`);
6863
7036
  }
6864
7037
  }
6865
7038
  if (diffs.length > 0) {
6866
- const raw = JSON.parse(readFileSync20(configPath, "utf-8"));
7039
+ const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
6867
7040
  delete raw.$schema;
6868
7041
  applyDiffs(raw, detected, diffs);
6869
7042
  writeConfig(configPath, raw);
@@ -6896,14 +7069,14 @@ ${lines.join("\n")}`);
6896
7069
  // src/commands/backup.ts
6897
7070
  import { defineCommand as defineCommand10 } from "citty";
6898
7071
  import * as p10 from "@clack/prompts";
6899
- import { existsSync as existsSync25, readdirSync as readdirSync10, statSync as statSync8, copyFileSync as copyFileSync4, mkdirSync as mkdirSync7 } from "fs";
6900
- import { join as join18, relative as relative5, resolve as resolve18, dirname as dirname7 } from "path";
7072
+ import { existsSync as existsSync26, readdirSync as readdirSync10, statSync as statSync8, copyFileSync as copyFileSync4, mkdirSync as mkdirSync8 } from "fs";
7073
+ import { join as join19, relative as relative5, resolve as resolve18, dirname as dirname8 } from "path";
6901
7074
  function listBackups() {
6902
7075
  const root = backupRoot();
6903
- if (!existsSync25(root)) return [];
7076
+ if (!existsSync26(root)) return [];
6904
7077
  const entries = [];
6905
7078
  for (const name of readdirSync10(root)) {
6906
- const full = join18(root, name);
7079
+ const full = join19(root, name);
6907
7080
  try {
6908
7081
  const stat = statSync8(full);
6909
7082
  if (!stat.isDirectory()) continue;
@@ -6918,7 +7091,7 @@ function listBackups() {
6918
7091
  function collectFiles(root, dir) {
6919
7092
  const out = [];
6920
7093
  for (const entry of readdirSync10(dir)) {
6921
- const full = join18(dir, entry);
7094
+ const full = join19(dir, entry);
6922
7095
  try {
6923
7096
  const stat = statSync8(full);
6924
7097
  if (stat.isDirectory()) {
@@ -6984,9 +7157,9 @@ var restoreSubCommand = defineCommand10({
6984
7157
  async run({ args }) {
6985
7158
  const ts = args.timestamp;
6986
7159
  const cwd = resolve18(args.cwd ?? process.cwd());
6987
- const backupDir = join18(backupRoot(), ts);
7160
+ const backupDir = join19(backupRoot(), ts);
6988
7161
  p10.intro(brand(`backup restore ${accent(ts)}`));
6989
- if (!existsSync25(backupDir)) {
7162
+ if (!existsSync26(backupDir)) {
6990
7163
  p10.cancel(`Backup not found: ${backupDir}`);
6991
7164
  process.exit(1);
6992
7165
  }
@@ -7008,9 +7181,9 @@ var restoreSubCommand = defineCommand10({
7008
7181
  }
7009
7182
  }
7010
7183
  for (const rel of files) {
7011
- const src = join18(backupDir, rel);
7012
- const dest = join18(cwd, rel);
7013
- mkdirSync7(dirname7(dest), { recursive: true });
7184
+ const src = join19(backupDir, rel);
7185
+ const dest = join19(cwd, rel);
7186
+ mkdirSync8(dirname8(dest), { recursive: true });
7014
7187
  copyFileSync4(src, dest);
7015
7188
  }
7016
7189
  p10.outro(`Restored ${files.length} file(s)`);
@@ -7040,19 +7213,19 @@ var backupCommand = defineCommand10({
7040
7213
  // src/commands/migrations.ts
7041
7214
  import { defineCommand as defineCommand11 } from "citty";
7042
7215
  import * as p11 from "@clack/prompts";
7043
- import { existsSync as existsSync26, readdirSync as readdirSync11, statSync as statSync9, copyFileSync as copyFileSync5, mkdirSync as mkdirSync8 } from "fs";
7044
- import { join as join19, relative as relative6, resolve as resolve19, dirname as dirname8 } from "path";
7216
+ import { existsSync as existsSync27, readdirSync as readdirSync11, statSync as statSync9, copyFileSync as copyFileSync5, mkdirSync as mkdirSync9 } from "fs";
7217
+ import { join as join20, relative as relative6, resolve as resolve19, dirname as dirname9 } from "path";
7045
7218
  function listMigrations() {
7046
7219
  const root = migrationsRoot();
7047
- if (!existsSync26(root)) return [];
7220
+ if (!existsSync27(root)) return [];
7048
7221
  const entries = [];
7049
7222
  for (const ts of readdirSync11(root)) {
7050
- const tsDir = join19(root, ts);
7223
+ const tsDir = join20(root, ts);
7051
7224
  try {
7052
7225
  const stat = statSync9(tsDir);
7053
7226
  if (!stat.isDirectory()) continue;
7054
7227
  for (const repoName of readdirSync11(tsDir)) {
7055
- const repoDir = join19(tsDir, repoName);
7228
+ const repoDir = join20(tsDir, repoName);
7056
7229
  try {
7057
7230
  const repoStat = statSync9(repoDir);
7058
7231
  if (!repoStat.isDirectory()) continue;
@@ -7070,7 +7243,7 @@ function listMigrations() {
7070
7243
  function collectFiles2(root, dir) {
7071
7244
  const out = [];
7072
7245
  for (const entry of readdirSync11(dir)) {
7073
- const full = join19(dir, entry);
7246
+ const full = join20(dir, entry);
7074
7247
  try {
7075
7248
  const stat = statSync9(full);
7076
7249
  if (stat.isDirectory()) {
@@ -7139,9 +7312,9 @@ var restoreSubCommand2 = defineCommand11({
7139
7312
  const ts = args.timestamp;
7140
7313
  const repoName = args.repo;
7141
7314
  const cwd = resolve19(args.cwd ?? process.cwd());
7142
- const migrationDir = join19(migrationsRoot(), ts, repoName);
7315
+ const migrationDir = join20(migrationsRoot(), ts, repoName);
7143
7316
  p11.intro(brand(`migrations restore ${accent(`${ts}/${repoName}`)}`));
7144
- if (!existsSync26(migrationDir)) {
7317
+ if (!existsSync27(migrationDir)) {
7145
7318
  p11.cancel(`Migration not found: ${migrationDir}`);
7146
7319
  process.exit(1);
7147
7320
  }
@@ -7164,9 +7337,9 @@ var restoreSubCommand2 = defineCommand11({
7164
7337
  }
7165
7338
  }
7166
7339
  for (const rel of files) {
7167
- const src = join19(migrationDir, rel);
7168
- const dest = join19(cwd, rel);
7169
- mkdirSync8(dirname8(dest), { recursive: true });
7340
+ const src = join20(migrationDir, rel);
7341
+ const dest = join20(cwd, rel);
7342
+ mkdirSync9(dirname9(dest), { recursive: true });
7170
7343
  copyFileSync5(src, dest);
7171
7344
  }
7172
7345
  p11.outro(`Restored ${files.length} file(s)`);
@@ -7197,8 +7370,8 @@ var migrationsCommand = defineCommand11({
7197
7370
  // src/commands/preset.ts
7198
7371
  import { defineCommand as defineCommand12 } from "citty";
7199
7372
  import * as p12 from "@clack/prompts";
7200
- import { existsSync as existsSync27, mkdirSync as mkdirSync9 } from "fs";
7201
- import { join as join20, resolve as resolve20 } from "path";
7373
+ import { existsSync as existsSync28, mkdirSync as mkdirSync10 } from "fs";
7374
+ import { join as join21, resolve as resolve20 } from "path";
7202
7375
  var PRESET_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
7203
7376
  function stackTemplate(id) {
7204
7377
  return [
@@ -7266,7 +7439,7 @@ var initSubCommand2 = defineCommand12({
7266
7439
  process.exit(1);
7267
7440
  }
7268
7441
  const presetDir = resolve20(cwd, ".navori/presets", id);
7269
- if (existsSync27(presetDir)) {
7442
+ if (existsSync28(presetDir)) {
7270
7443
  p12.cancel(
7271
7444
  `Ya existe .navori/presets/${id}/ \u2014 b\xF3rralo o usa otro id si quieres regenerarlo.`
7272
7445
  );
@@ -7292,11 +7465,11 @@ var initSubCommand2 = defineCommand12({
7292
7465
  },
7293
7466
  invariants: []
7294
7467
  };
7295
- mkdirSync9(join20(presetDir, "managed"), { recursive: true });
7296
- mkdirSync9(join20(presetDir, "skills"), { recursive: true });
7297
- writeFileAtomic(join20(presetDir, `${id}.json`), JSON.stringify(manifest, null, 2) + "\n");
7298
- writeFileAtomic(join20(presetDir, "managed", "stack.md"), stackTemplate(id));
7299
- writeFileAtomic(join20(presetDir, "skills", `${skillId}.md`), skillTemplate(skillId));
7468
+ mkdirSync10(join21(presetDir, "managed"), { recursive: true });
7469
+ mkdirSync10(join21(presetDir, "skills"), { recursive: true });
7470
+ writeFileAtomic(join21(presetDir, `${id}.json`), JSON.stringify(manifest, null, 2) + "\n");
7471
+ writeFileAtomic(join21(presetDir, "managed", "stack.md"), stackTemplate(id));
7472
+ writeFileAtomic(join21(presetDir, "skills", `${skillId}.md`), skillTemplate(skillId));
7300
7473
  p12.log.success(`Creado .navori/presets/${id}/`);
7301
7474
  p12.log.message(
7302
7475
  [
@@ -7305,8 +7478,8 @@ var initSubCommand2 = defineCommand12({
7305
7478
  ` ${dim("\xB7")} skills/${skillId}.md`
7306
7479
  ].join("\n")
7307
7480
  );
7308
- const configPath = join20(cwd, "navori.config.json");
7309
- if (existsSync27(configPath)) {
7481
+ const configPath = join21(cwd, "navori.config.json");
7482
+ if (existsSync28(configPath)) {
7310
7483
  const config = readConfig(configPath);
7311
7484
  writeConfig(configPath, { ...config, preset: id });
7312
7485
  p12.log.success(`navori.config.json \u2192 preset: ${accent(id)}`);
@@ -7333,10 +7506,10 @@ var presetCommand = defineCommand12({
7333
7506
  import { defineCommand as defineCommand13 } from "citty";
7334
7507
  import * as p13 from "@clack/prompts";
7335
7508
  import { resolve as resolve21 } from "path";
7336
- import { existsSync as existsSync28 } from "fs";
7509
+ import { existsSync as existsSync29 } from "fs";
7337
7510
  function runScan(opts) {
7338
7511
  const configPath = resolve21(opts.cwd, "navori.config.json");
7339
- if (!existsSync28(configPath)) {
7512
+ if (!existsSync29(configPath)) {
7340
7513
  return { kind: "no-config", configPath };
7341
7514
  }
7342
7515
  const config = readConfig(configPath);
@@ -7507,7 +7680,7 @@ async function collectPresetOverrides(added) {
7507
7680
  // src/commands/status.ts
7508
7681
  import { defineCommand as defineCommand14 } from "citty";
7509
7682
  import * as p14 from "@clack/prompts";
7510
- import { existsSync as existsSync29 } from "fs";
7683
+ import { existsSync as existsSync30 } from "fs";
7511
7684
  import { resolve as resolve22 } from "path";
7512
7685
  var statusCommand = defineCommand14({
7513
7686
  meta: {
@@ -7521,7 +7694,7 @@ var statusCommand = defineCommand14({
7521
7694
  async run({ args }) {
7522
7695
  const cwd = resolve22(args.cwd ?? process.cwd());
7523
7696
  const configPath = `${cwd}/navori.config.json`;
7524
- if (!existsSync29(configPath)) {
7697
+ if (!existsSync30(configPath)) {
7525
7698
  if (args.json) {
7526
7699
  console.log(JSON.stringify({ ok: false, error: "config-missing", configPath }));
7527
7700
  } else {
@@ -7545,7 +7718,7 @@ var statusCommand = defineCommand14({
7545
7718
  }
7546
7719
  throw err;
7547
7720
  }
7548
- const claudeMdExists = existsSync29(`${cwd}/CLAUDE.md`);
7721
+ const claudeMdExists = existsSync30(`${cwd}/CLAUDE.md`);
7549
7722
  const missingPlugins = collectMissingPlugins(config);
7550
7723
  const drifts = scanManagedDrift(cwd, config);
7551
7724
  const enabledPlugins = Object.entries(config.plugins ?? {}).filter(([, v]) => v.enabled === true).map(([k]) => k);
@@ -7593,7 +7766,7 @@ var statusCommand = defineCommand14({
7593
7766
  import { defineCommand as defineCommand15 } from "citty";
7594
7767
  import * as p15 from "@clack/prompts";
7595
7768
  import { performance as performance2 } from "perf_hooks";
7596
- import { existsSync as existsSync30 } from "fs";
7769
+ import { existsSync as existsSync31 } from "fs";
7597
7770
  import { resolve as resolve23 } from "path";
7598
7771
  var benchCommand = defineCommand15({
7599
7772
  meta: {
@@ -7607,7 +7780,7 @@ var benchCommand = defineCommand15({
7607
7780
  async run({ args }) {
7608
7781
  const cwd = resolve23(args.cwd ?? process.cwd());
7609
7782
  p15.intro(brand("bench"));
7610
- if (!existsSync30(`${cwd}/navori.config.json`)) {
7783
+ if (!existsSync31(`${cwd}/navori.config.json`)) {
7611
7784
  p15.cancel(`No navori.config.json at ${cwd}. Run 'navori init' first.`);
7612
7785
  process.exit(1);
7613
7786
  }
@@ -7638,13 +7811,13 @@ var benchCommand = defineCommand15({
7638
7811
 
7639
7812
  // src/index.ts
7640
7813
  function readVersion() {
7641
- const here = dirname9(fileURLToPath2(import.meta.url));
7814
+ const here = dirname10(fileURLToPath2(import.meta.url));
7642
7815
  for (const candidate of [
7643
7816
  resolve24(here, "..", "package.json"),
7644
7817
  resolve24(here, "package.json")
7645
7818
  ]) {
7646
7819
  try {
7647
- const pkg = JSON.parse(readFileSync21(candidate, "utf-8"));
7820
+ const pkg = JSON.parse(readFileSync22(candidate, "utf-8"));
7648
7821
  if (pkg.version) return pkg.version;
7649
7822
  } catch {
7650
7823
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "navori",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Multi-agent harness + SDD scaffolder for Claude Code and other AI engines",
5
5
  "type": "module",
6
6
  "bin": {