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.
- package/dist/assets/core/core-assets/agents/commit-pr-pilot.md +2 -6
- package/dist/assets/core/core-assets/agents/explorer.md +2 -2
- package/dist/assets/core/core-assets/agents/implementer.md +1 -1
- package/dist/assets/core/core-assets/agents/leader.md +22 -1
- package/dist/assets/core/core-assets/agents/researcher.md +4 -3
- package/dist/assets/core/core-assets/agents/reviewer.md +1 -1
- package/dist/assets/core/core-assets/agents/ticket-audit.md +1 -1
- package/dist/assets/core/core-assets/managed/arranque-sesion.md +2 -2
- package/dist/assets/core/core-assets/managed/operaciones-seguras.md +1 -0
- package/dist/assets/core/core-assets/managed/orquestacion.md +15 -0
- package/dist/index.js +345 -172
- package/package.json +1 -1
|
@@ -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`
|
|
26
|
-
2. Define el alcance: una carpeta, un módulo lógico, un patrón de archivos.
|
|
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
|
|
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.
|
|
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`
|
|
26
|
-
2.
|
|
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
|
-
-
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
9
|
+
5. **Tarea acotada**: ten claro el alcance de ESTA tarea. Una tarea **de usuario** a la vez (no mezcles pedidos distintos); pero tú 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
|
|
6
|
-
import { dirname as
|
|
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
|
|
13
|
-
import { existsSync as
|
|
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
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
3012
|
-
const
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
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
|
|
3035
|
-
written: engineResult
|
|
3036
|
-
languageFallbacks: engineResult
|
|
3037
|
-
updatesAvailable: engineResult
|
|
3038
|
-
backupPath: engineResult
|
|
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 (!
|
|
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
|
|
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 (!
|
|
3696
|
+
if (!existsSync14(path)) return [];
|
|
3524
3697
|
let parsed;
|
|
3525
3698
|
try {
|
|
3526
|
-
parsed = JSON.parse(
|
|
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
|
|
3541
|
-
import { join as
|
|
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 ?
|
|
3741
|
+
const currentAbs = currentRel ? join11(cwd, currentRel) : cwd;
|
|
3569
3742
|
if (head === "*") {
|
|
3570
|
-
if (!
|
|
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 =
|
|
3580
|
-
if (!
|
|
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 =
|
|
3593
|
-
if (!
|
|
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 (!
|
|
3847
|
+
if (!existsSync16(cwd)) {
|
|
3675
3848
|
p2.cancel(bootstrapT.dirNotFound(cwd));
|
|
3676
3849
|
process.exit(1);
|
|
3677
3850
|
}
|
|
3678
|
-
if (
|
|
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 (!
|
|
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 =
|
|
4312
|
-
const hookPath =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
4552
|
-
import { join as
|
|
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
|
|
4556
|
-
import { join as
|
|
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 (!
|
|
4559
|
-
const content =
|
|
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 (
|
|
4777
|
+
if (existsSync17(join13(cwd, "CLAUDE.md"))) files.push("CLAUDE.md");
|
|
4605
4778
|
for (const dir of [".claude/agents", ".claude/skills"]) {
|
|
4606
|
-
const absDir =
|
|
4607
|
-
if (!
|
|
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 =
|
|
4790
|
+
const abs = join13(cwd, rel);
|
|
4618
4791
|
const fileContent = (() => {
|
|
4619
4792
|
try {
|
|
4620
|
-
return
|
|
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 (!
|
|
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 (!
|
|
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:
|
|
4757
|
-
agentsMdExists:
|
|
4758
|
-
claudeDirExists:
|
|
4759
|
-
progressDirExists:
|
|
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) => !
|
|
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 =
|
|
4907
|
-
if (!
|
|
5079
|
+
const path = join14(cwd, ".claude/settings.json");
|
|
5080
|
+
if (!existsSync18(path)) return [];
|
|
4908
5081
|
try {
|
|
4909
|
-
JSON.parse(
|
|
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 (!
|
|
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 =
|
|
4966
|
-
if (
|
|
5138
|
+
const claudeMd = join14(cwd, "CLAUDE.md");
|
|
5139
|
+
if (existsSync18(claudeMd)) {
|
|
4967
5140
|
try {
|
|
4968
|
-
parts.push(
|
|
5141
|
+
parts.push(readFileSync15(claudeMd, "utf-8"));
|
|
4969
5142
|
} catch {
|
|
4970
5143
|
}
|
|
4971
5144
|
}
|
|
4972
|
-
const claudeDir =
|
|
4973
|
-
if (
|
|
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 =
|
|
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(
|
|
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
|
|
5022
|
-
import { resolve as resolve13, join as
|
|
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 (!
|
|
5240
|
+
if (!existsSync19(cwd)) {
|
|
5068
5241
|
p4.cancel(`Directory not found: ${cwd}`);
|
|
5069
5242
|
process.exit(1);
|
|
5070
5243
|
}
|
|
5071
|
-
if (!
|
|
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 =
|
|
5227
|
-
const existing =
|
|
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
|
|
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
|
|
5325
|
-
import { join as
|
|
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 =
|
|
5334
|
-
if (
|
|
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 (!
|
|
5583
|
+
if (!existsSync21(cwd)) {
|
|
5411
5584
|
p5.cancel(`Directory not found: ${cwd}`);
|
|
5412
5585
|
process.exit(1);
|
|
5413
5586
|
}
|
|
5414
|
-
if (!
|
|
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(
|
|
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
|
|
5534
|
-
import { join as
|
|
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 =
|
|
5571
|
-
if (!
|
|
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 (
|
|
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:
|
|
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
|
-
|
|
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 (
|
|
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 (!
|
|
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
|
|
6198
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
6026
6199
|
|
|
6027
6200
|
// src/lib/tickets.ts
|
|
6028
|
-
import { existsSync as
|
|
6029
|
-
import { join as
|
|
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
|
|
6211
|
+
return join18(workspaceDirectory(workspaceName), ws.ticketsDir);
|
|
6039
6212
|
}
|
|
6040
6213
|
function readTitle(path) {
|
|
6041
6214
|
try {
|
|
6042
|
-
const content =
|
|
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 (!
|
|
6228
|
+
if (!existsSync23(dir)) return [];
|
|
6056
6229
|
const out = [];
|
|
6057
6230
|
const collect = (folder, state) => {
|
|
6058
|
-
if (!
|
|
6231
|
+
if (!existsSync23(folder)) return;
|
|
6059
6232
|
for (const entry of readdirSync9(folder)) {
|
|
6060
6233
|
if (!entry.endsWith(".md")) continue;
|
|
6061
|
-
const full =
|
|
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(
|
|
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 =
|
|
6112
|
-
|
|
6113
|
-
const dest =
|
|
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 (!
|
|
6128
|
-
const path =
|
|
6129
|
-
if (
|
|
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 =
|
|
6145
|
-
if (!
|
|
6317
|
+
const current = join18(abs, "progress", "current.md");
|
|
6318
|
+
if (!existsSync23(current)) continue;
|
|
6146
6319
|
try {
|
|
6147
|
-
const content =
|
|
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 =
|
|
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(
|
|
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
|
|
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 (!
|
|
6567
|
+
if (!existsSync24(cwd)) fail(`Directory not found: ${cwd}`);
|
|
6395
6568
|
const configPath = resolve16(cwd, "navori.config.json");
|
|
6396
|
-
if (!
|
|
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(
|
|
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
|
|
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 (!
|
|
6974
|
+
if (!existsSync25(cwd)) {
|
|
6802
6975
|
p9.cancel(`Directory not found: ${cwd}`);
|
|
6803
6976
|
process.exit(1);
|
|
6804
6977
|
}
|
|
6805
|
-
if (!
|
|
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(
|
|
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
|
|
6900
|
-
import { join as
|
|
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 (!
|
|
7076
|
+
if (!existsSync26(root)) return [];
|
|
6904
7077
|
const entries = [];
|
|
6905
7078
|
for (const name of readdirSync10(root)) {
|
|
6906
|
-
const full =
|
|
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 =
|
|
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 =
|
|
7160
|
+
const backupDir = join19(backupRoot(), ts);
|
|
6988
7161
|
p10.intro(brand(`backup restore ${accent(ts)}`));
|
|
6989
|
-
if (!
|
|
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 =
|
|
7012
|
-
const dest =
|
|
7013
|
-
|
|
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
|
|
7044
|
-
import { join as
|
|
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 (!
|
|
7220
|
+
if (!existsSync27(root)) return [];
|
|
7048
7221
|
const entries = [];
|
|
7049
7222
|
for (const ts of readdirSync11(root)) {
|
|
7050
|
-
const tsDir =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
7315
|
+
const migrationDir = join20(migrationsRoot(), ts, repoName);
|
|
7143
7316
|
p11.intro(brand(`migrations restore ${accent(`${ts}/${repoName}`)}`));
|
|
7144
|
-
if (!
|
|
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 =
|
|
7168
|
-
const dest =
|
|
7169
|
-
|
|
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
|
|
7201
|
-
import { join as
|
|
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 (
|
|
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
|
-
|
|
7296
|
-
|
|
7297
|
-
writeFileAtomic(
|
|
7298
|
-
writeFileAtomic(
|
|
7299
|
-
writeFileAtomic(
|
|
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 =
|
|
7309
|
-
if (
|
|
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
|
|
7509
|
+
import { existsSync as existsSync29 } from "fs";
|
|
7337
7510
|
function runScan(opts) {
|
|
7338
7511
|
const configPath = resolve21(opts.cwd, "navori.config.json");
|
|
7339
|
-
if (!
|
|
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
|
|
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 (!
|
|
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 =
|
|
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
|
|
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 (!
|
|
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 =
|
|
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(
|
|
7820
|
+
const pkg = JSON.parse(readFileSync22(candidate, "utf-8"));
|
|
7648
7821
|
if (pkg.version) return pkg.version;
|
|
7649
7822
|
} catch {
|
|
7650
7823
|
}
|