navori 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +77 -17
- package/dist/assets/core/core-assets/agents/commit-pr-pilot.md +15 -9
- package/dist/assets/core/core-assets/lib-skills/formik.md +57 -0
- package/dist/assets/core/core-assets/lib-skills/joi-validation.md +73 -0
- package/dist/assets/core/core-assets/{presets/express-mongoose/skills → lib-skills}/mongoose.md +3 -2
- package/dist/assets/core/core-assets/lib-skills/redux-toolkit.md +59 -0
- package/dist/assets/core/core-assets/lib-skills/socketio.md +56 -0
- package/dist/assets/core/core-assets/lib-skills/tanstack-query.md +58 -0
- package/dist/assets/core/core-assets/managed/cierre-sesion.md +1 -1
- package/dist/assets/core/core-assets/managed/idioma-rol.md +1 -1
- package/dist/assets/core/core-assets/managed/operaciones-seguras.md +1 -0
- package/dist/assets/core/core-assets/presets/background-worker/managed/stack.md +18 -0
- package/dist/assets/core/core-assets/presets/background-worker/skills/job-scheduling.md +54 -0
- package/dist/assets/core/core-assets/presets/background-worker/skills/queue-consumers.md +55 -0
- package/dist/assets/core/core-assets/presets/background-worker/skills/worker-lifecycle.md +57 -0
- package/dist/assets/core/core-assets/presets/background-worker.json +39 -0
- package/dist/assets/core/core-assets/presets/express-mongoose/managed/stack.md +2 -2
- package/dist/assets/core/core-assets/presets/express-mongoose/skills/pr-create.md +18 -18
- package/dist/assets/core/core-assets/presets/express-mongoose.json +0 -12
- package/dist/assets/core/core-assets/settings/settings-base.json +22 -1
- package/dist/index.js +797 -346
- package/package.json +5 -4
- /package/dist/assets/core/core-assets/{presets/express-mongoose/skills → lib-skills}/zod-validation.md +0 -0
package/dist/index.js
CHANGED
|
@@ -9,8 +9,8 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
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 join11, dirname as dirname6, relative as relative3 } from "path";
|
|
13
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5, chmodSync as chmodSync2 } from "fs";
|
|
14
14
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
15
15
|
|
|
16
16
|
// src/lib/config.ts
|
|
@@ -175,7 +175,18 @@ var ProjectSchema = z2.object({
|
|
|
175
175
|
testsForNewCode: z2.string().optional(),
|
|
176
176
|
/** Skill ids the user owns under `.claude/skills/<id>.md`. navori never
|
|
177
177
|
* writes their content — it only indexes them so agents discover them. */
|
|
178
|
-
localSkills: z2.array(z2.string()).default([])
|
|
178
|
+
localSkills: z2.array(z2.string()).default([]),
|
|
179
|
+
/** Library-skill ids detected in the repo's deps (socketio, mongoose, …).
|
|
180
|
+
* Cross-preset: a skill is materialized whenever its dependency is present,
|
|
181
|
+
* independent of the active preset. Supersedes the old zod/joiValidation
|
|
182
|
+
* flags. See lib/library-skills.ts. */
|
|
183
|
+
libraries: z2.array(z2.string()).default([]),
|
|
184
|
+
/** Programming language of the repo (ts/js/python/rust/go/unknown), from
|
|
185
|
+
* detection. Drives language-aware baseline blocks — e.g. the TS-only
|
|
186
|
+
* `tipado-fuerte` (any/unknown) is suppressed in python/rust/go. The render
|
|
187
|
+
* derives `typedLanguage` from this; absence is treated as JS/TS for
|
|
188
|
+
* back-compat with configs written before this field existed. */
|
|
189
|
+
codeLanguage: z2.string().optional()
|
|
179
190
|
}).passthrough();
|
|
180
191
|
var NavoriConfigSchema = z2.object({
|
|
181
192
|
$schema: z2.string().optional(),
|
|
@@ -186,6 +197,12 @@ var NavoriConfigSchema = z2.object({
|
|
|
186
197
|
preset: z2.string().min(1),
|
|
187
198
|
language: z2.enum(["es", "en"]).default("es"),
|
|
188
199
|
branchBase: z2.string().default("main"),
|
|
200
|
+
/** Target branch for PRs (`gh pr create --base`). When omitted, PRs target
|
|
201
|
+
* branchBase. Decouples the fork point / protected branch (branchBase) from
|
|
202
|
+
* the PR target — e.g. branch off `main` but open PRs against `develop`.
|
|
203
|
+
* The render derives the effective value (prTarget ?? branchBase) so this
|
|
204
|
+
* stays out of configs that don't need it. */
|
|
205
|
+
prTarget: z2.string().optional(),
|
|
189
206
|
commits: z2.enum(["conventional", "conventional-es", "free"]).default("conventional-es"),
|
|
190
207
|
qualityGate: QualityGateSchema.optional(),
|
|
191
208
|
sdd: SddSchema.optional(),
|
|
@@ -207,6 +224,15 @@ function writeConfig(path, input) {
|
|
|
207
224
|
const validated = NavoriConfigSchema.parse({ $schema: SCHEMA_URL, ...input });
|
|
208
225
|
writeFileAtomic(path, JSON.stringify(validated, null, 2) + "\n");
|
|
209
226
|
}
|
|
227
|
+
function effectiveConfig(config) {
|
|
228
|
+
const codeLanguage = config.project?.codeLanguage;
|
|
229
|
+
const typedLanguage = !(codeLanguage === "python" || codeLanguage === "rust" || codeLanguage === "go");
|
|
230
|
+
return {
|
|
231
|
+
...config,
|
|
232
|
+
prTarget: config.prTarget ?? config.branchBase,
|
|
233
|
+
project: { ...config.project ?? {}, typedLanguage }
|
|
234
|
+
};
|
|
235
|
+
}
|
|
210
236
|
var ConfigError = class extends NavoriError {
|
|
211
237
|
issues;
|
|
212
238
|
constructor(message, issues) {
|
|
@@ -238,8 +264,8 @@ function readConfig(path) {
|
|
|
238
264
|
}
|
|
239
265
|
|
|
240
266
|
// src/lib/detect.ts
|
|
241
|
-
import { existsSync as
|
|
242
|
-
import { join as
|
|
267
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
268
|
+
import { join as join5, basename as basename2 } from "path";
|
|
243
269
|
import { spawnSync } from "child_process";
|
|
244
270
|
|
|
245
271
|
// src/lib/presets.ts
|
|
@@ -304,7 +330,16 @@ var PresetExtraFileSchema = z4.object({
|
|
|
304
330
|
id: z4.string().min(1),
|
|
305
331
|
relPath: safeRelPath,
|
|
306
332
|
/** Where in `.claude/` the file lands (e.g. "skills/medusa-db-migrations.md"). */
|
|
307
|
-
destRelPath: safeRelPath
|
|
333
|
+
destRelPath: safeRelPath,
|
|
334
|
+
/**
|
|
335
|
+
* Optional config path (e.g. "project.posture") evaluated with the same
|
|
336
|
+
* truthy semantics as CoreManagedAsset.condition (see resolveCondition). When
|
|
337
|
+
* set, the extra is only materialized — and only listed in the skills index —
|
|
338
|
+
* if the path resolves truthy. Omit for an always-on extra (the default).
|
|
339
|
+
* Lets a preset ship a variant gated on a config flag. (Dependency-driven
|
|
340
|
+
* skills now live in the cross-preset library-skills layer instead.)
|
|
341
|
+
*/
|
|
342
|
+
condition: z4.string().optional()
|
|
308
343
|
});
|
|
309
344
|
var PresetDefinitionSchema = z4.object({
|
|
310
345
|
id: z4.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "preset id must be kebab-case"),
|
|
@@ -375,9 +410,116 @@ function loadPreset(id, repoRoot) {
|
|
|
375
410
|
return { def: result.data, assetRoot: resolved.assetRoot, source: resolved.source };
|
|
376
411
|
}
|
|
377
412
|
|
|
378
|
-
// src/lib/
|
|
379
|
-
import { existsSync as existsSync3,
|
|
413
|
+
// src/lib/workspace-patterns.ts
|
|
414
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
380
415
|
import { join as join3 } from "path";
|
|
416
|
+
function collectWorkspacePatterns(cwd) {
|
|
417
|
+
const pnpmYaml = join3(cwd, "pnpm-workspace.yaml");
|
|
418
|
+
if (existsSync3(pnpmYaml)) {
|
|
419
|
+
try {
|
|
420
|
+
return parsePnpmWorkspaceYaml(readFileSync4(pnpmYaml, "utf-8"));
|
|
421
|
+
} catch {
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const pkgPath = join3(cwd, "package.json");
|
|
426
|
+
if (existsSync3(pkgPath)) {
|
|
427
|
+
try {
|
|
428
|
+
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8").replace(/^/, ""));
|
|
429
|
+
return readPackageJsonWorkspaces(pkg.workspaces);
|
|
430
|
+
} catch {
|
|
431
|
+
return [];
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return [];
|
|
435
|
+
}
|
|
436
|
+
function parsePnpmWorkspaceYaml(content) {
|
|
437
|
+
const lines = content.split(/\r?\n/);
|
|
438
|
+
let i = 0;
|
|
439
|
+
while (i < lines.length) {
|
|
440
|
+
const line = lines[i];
|
|
441
|
+
const m = line.match(/^packages\s*:\s*(.*)$/);
|
|
442
|
+
if (m) {
|
|
443
|
+
const inline = m[1].trim();
|
|
444
|
+
if (inline.startsWith("[")) {
|
|
445
|
+
return parseInlineYamlArray(inline);
|
|
446
|
+
}
|
|
447
|
+
const items = [];
|
|
448
|
+
i++;
|
|
449
|
+
while (i < lines.length) {
|
|
450
|
+
const cur = lines[i];
|
|
451
|
+
if (/^\s*#/.test(cur) || /^\s*$/.test(cur)) {
|
|
452
|
+
i++;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const itemMatch = cur.match(/^\s+-\s+(.+?)\s*(?:#.*)?$/);
|
|
456
|
+
if (!itemMatch) break;
|
|
457
|
+
const raw = itemMatch[1].trim();
|
|
458
|
+
const unquoted = stripYamlQuotes(raw);
|
|
459
|
+
if (unquoted && !unquoted.startsWith("!")) items.push(unquoted);
|
|
460
|
+
i++;
|
|
461
|
+
}
|
|
462
|
+
return items;
|
|
463
|
+
}
|
|
464
|
+
i++;
|
|
465
|
+
}
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
function parseInlineYamlArray(inline) {
|
|
469
|
+
const closed = inline.lastIndexOf("]");
|
|
470
|
+
if (closed < 0) return [];
|
|
471
|
+
const body = inline.slice(1, closed);
|
|
472
|
+
return body.split(",").map((s) => stripYamlQuotes(s.trim())).filter((s) => s && !s.startsWith("!"));
|
|
473
|
+
}
|
|
474
|
+
function stripYamlQuotes(raw) {
|
|
475
|
+
if (raw.length >= 2) {
|
|
476
|
+
const first = raw[0];
|
|
477
|
+
const last = raw[raw.length - 1];
|
|
478
|
+
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
479
|
+
return raw.slice(1, -1);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return raw;
|
|
483
|
+
}
|
|
484
|
+
function readPackageJsonWorkspaces(raw) {
|
|
485
|
+
if (Array.isArray(raw)) {
|
|
486
|
+
return raw.filter((s) => typeof s === "string" && !s.startsWith("!"));
|
|
487
|
+
}
|
|
488
|
+
if (raw && typeof raw === "object" && "packages" in raw) {
|
|
489
|
+
const pkgs = raw.packages;
|
|
490
|
+
if (Array.isArray(pkgs)) {
|
|
491
|
+
return pkgs.filter((s) => typeof s === "string" && !s.startsWith("!"));
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return [];
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/lib/library-skills.ts
|
|
498
|
+
var LIBRARY_SKILLS = [
|
|
499
|
+
{ id: "socketio", deps: ["socket.io", "socket.io-client"], label: "Socket.IO realtime" },
|
|
500
|
+
{ id: "redux-toolkit", deps: ["@reduxjs/toolkit", "redux"], label: "Redux Toolkit" },
|
|
501
|
+
{
|
|
502
|
+
id: "tanstack-query",
|
|
503
|
+
deps: ["@tanstack/react-query", "vue-query", "solid-query"],
|
|
504
|
+
label: "TanStack Query"
|
|
505
|
+
},
|
|
506
|
+
{ id: "formik", deps: ["formik"], label: "Formik forms" },
|
|
507
|
+
{ id: "mongoose", deps: ["mongoose", "@nestjs/mongoose"], label: "Mongoose ODM" },
|
|
508
|
+
{ id: "zod-validation", deps: ["zod"], label: "Zod validation" },
|
|
509
|
+
{ id: "joi-validation", deps: ["joi", "@hapi/joi"], label: "Joi validation" }
|
|
510
|
+
];
|
|
511
|
+
var BY_ID = new Map(LIBRARY_SKILLS.map((s) => [s.id, s]));
|
|
512
|
+
function detectLibrarySkills(deps) {
|
|
513
|
+
const present = new Set(deps);
|
|
514
|
+
return LIBRARY_SKILLS.filter((s) => s.deps.some((d) => present.has(d))).map((s) => s.id);
|
|
515
|
+
}
|
|
516
|
+
function librarySkillById(id) {
|
|
517
|
+
return BY_ID.get(id) ?? null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/lib/claude-infra.ts
|
|
521
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
522
|
+
import { join as join4 } from "path";
|
|
381
523
|
function safeReaddir(path) {
|
|
382
524
|
try {
|
|
383
525
|
return readdirSync2(path);
|
|
@@ -391,10 +533,10 @@ function listMarkdownFiles(dir) {
|
|
|
391
533
|
function listSkillDirs(dir) {
|
|
392
534
|
const result = [];
|
|
393
535
|
for (const entry of safeReaddir(dir)) {
|
|
394
|
-
const full =
|
|
536
|
+
const full = join4(dir, entry);
|
|
395
537
|
try {
|
|
396
538
|
if (statSync2(full).isDirectory()) {
|
|
397
|
-
if (
|
|
539
|
+
if (existsSync4(join4(full, "SKILL.md"))) result.push(entry);
|
|
398
540
|
} else if (entry.endsWith(".md")) {
|
|
399
541
|
result.push(entry);
|
|
400
542
|
}
|
|
@@ -407,7 +549,7 @@ function countFilesIn(dir) {
|
|
|
407
549
|
let count = 0;
|
|
408
550
|
for (const entry of safeReaddir(dir)) {
|
|
409
551
|
try {
|
|
410
|
-
if (statSync2(
|
|
552
|
+
if (statSync2(join4(dir, entry)).isFile()) count++;
|
|
411
553
|
} catch {
|
|
412
554
|
}
|
|
413
555
|
}
|
|
@@ -417,27 +559,27 @@ function countSubdirs(dir) {
|
|
|
417
559
|
let count = 0;
|
|
418
560
|
for (const entry of safeReaddir(dir)) {
|
|
419
561
|
try {
|
|
420
|
-
if (statSync2(
|
|
562
|
+
if (statSync2(join4(dir, entry)).isDirectory()) count++;
|
|
421
563
|
} catch {
|
|
422
564
|
}
|
|
423
565
|
}
|
|
424
566
|
return count;
|
|
425
567
|
}
|
|
426
568
|
function detectClaudeInfra(cwd) {
|
|
427
|
-
const claudeDir =
|
|
428
|
-
const agentsDir =
|
|
429
|
-
const skillsDir =
|
|
569
|
+
const claudeDir = join4(cwd, ".claude");
|
|
570
|
+
const agentsDir = join4(claudeDir, "agents");
|
|
571
|
+
const skillsDir = join4(claudeDir, "skills");
|
|
430
572
|
const agentFiles = listMarkdownFiles(agentsDir);
|
|
431
573
|
const skillFiles = listSkillDirs(skillsDir);
|
|
432
|
-
const hasSettings =
|
|
433
|
-
const hasLocalSettings =
|
|
434
|
-
const hasClaudeMd =
|
|
435
|
-
const hasAgentsMd =
|
|
436
|
-
const hasCheckpointsMd =
|
|
437
|
-
const hasFeatureList =
|
|
438
|
-
const hasNavoriConfig =
|
|
439
|
-
const progressFiles =
|
|
440
|
-
const specsDirs =
|
|
574
|
+
const hasSettings = existsSync4(join4(claudeDir, "settings.json"));
|
|
575
|
+
const hasLocalSettings = existsSync4(join4(claudeDir, "settings.local.json"));
|
|
576
|
+
const hasClaudeMd = existsSync4(join4(cwd, "CLAUDE.md"));
|
|
577
|
+
const hasAgentsMd = existsSync4(join4(cwd, "AGENTS.md"));
|
|
578
|
+
const hasCheckpointsMd = existsSync4(join4(cwd, "CHECKPOINTS.md"));
|
|
579
|
+
const hasFeatureList = existsSync4(join4(cwd, "feature_list.json"));
|
|
580
|
+
const hasNavoriConfig = existsSync4(join4(cwd, "navori.config.json"));
|
|
581
|
+
const progressFiles = existsSync4(join4(cwd, "progress")) ? countFilesIn(join4(cwd, "progress")) : 0;
|
|
582
|
+
const specsDirs = existsSync4(join4(cwd, "specs")) ? countSubdirs(join4(cwd, "specs")) : 0;
|
|
441
583
|
const present = agentFiles.length > 0 || skillFiles.length > 0 || hasSettings || hasLocalSettings || hasClaudeMd || hasAgentsMd || hasCheckpointsMd || hasFeatureList || progressFiles > 0 || specsDirs > 0;
|
|
442
584
|
return {
|
|
443
585
|
present,
|
|
@@ -474,6 +616,7 @@ function detectProject(cwd) {
|
|
|
474
616
|
const packageManager = detectPackageManager(cwd);
|
|
475
617
|
const monorepo = detectMonorepo(cwd);
|
|
476
618
|
const stack = detectStack(cwd, pkg, pyproject, cargo);
|
|
619
|
+
const libraries = detectLibrarySkills(stack.deps);
|
|
477
620
|
const { preset: suggestedPreset, gap: suggestedPresetGap } = suggestPreset(stack, monorepo);
|
|
478
621
|
const qualityGate = guessQualityGate(pkg, packageManager, stack);
|
|
479
622
|
const claudeInfra = detectClaudeInfra(cwd);
|
|
@@ -484,6 +627,7 @@ function detectProject(cwd) {
|
|
|
484
627
|
packageManager,
|
|
485
628
|
monorepo,
|
|
486
629
|
stack,
|
|
630
|
+
libraries,
|
|
487
631
|
suggestedPreset,
|
|
488
632
|
suggestedPresetGap,
|
|
489
633
|
qualityGate,
|
|
@@ -495,6 +639,29 @@ function detectProject(cwd) {
|
|
|
495
639
|
}
|
|
496
640
|
};
|
|
497
641
|
}
|
|
642
|
+
var PLACEHOLDER_NAMES = /* @__PURE__ */ new Set([
|
|
643
|
+
"temp-app",
|
|
644
|
+
"temp",
|
|
645
|
+
"tmp",
|
|
646
|
+
"my-app",
|
|
647
|
+
"myapp",
|
|
648
|
+
"my-project",
|
|
649
|
+
"your-app",
|
|
650
|
+
"your-project",
|
|
651
|
+
"your-app-name",
|
|
652
|
+
"app-name",
|
|
653
|
+
"project-name",
|
|
654
|
+
"changeme",
|
|
655
|
+
"change-me",
|
|
656
|
+
"example-app",
|
|
657
|
+
"sample-app",
|
|
658
|
+
"new-project",
|
|
659
|
+
"untitled",
|
|
660
|
+
"placeholder"
|
|
661
|
+
]);
|
|
662
|
+
function isPlaceholderName(name) {
|
|
663
|
+
return PLACEHOLDER_NAMES.has(name.trim().toLowerCase());
|
|
664
|
+
}
|
|
498
665
|
function normalizeName(raw) {
|
|
499
666
|
if (typeof raw !== "string" || !raw) return null;
|
|
500
667
|
const cleaned = raw.trim().toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -502,20 +669,20 @@ function normalizeName(raw) {
|
|
|
502
669
|
return /^[a-z0-9]/.test(cleaned) ? cleaned : null;
|
|
503
670
|
}
|
|
504
671
|
function readPackageJson(cwd) {
|
|
505
|
-
const path =
|
|
506
|
-
if (!
|
|
672
|
+
const path = join5(cwd, "package.json");
|
|
673
|
+
if (!existsSync5(path)) return null;
|
|
507
674
|
try {
|
|
508
|
-
const raw =
|
|
675
|
+
const raw = readFileSync5(path, "utf-8").replace(/^/, "");
|
|
509
676
|
return JSON.parse(raw);
|
|
510
677
|
} catch {
|
|
511
678
|
return null;
|
|
512
679
|
}
|
|
513
680
|
}
|
|
514
681
|
function readPyproject(cwd) {
|
|
515
|
-
const path =
|
|
516
|
-
if (!
|
|
682
|
+
const path = join5(cwd, "pyproject.toml");
|
|
683
|
+
if (!existsSync5(path)) return null;
|
|
517
684
|
try {
|
|
518
|
-
const content =
|
|
685
|
+
const content = readFileSync5(path, "utf-8");
|
|
519
686
|
const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
520
687
|
const deps = [];
|
|
521
688
|
const depBlock = content.match(/\[(?:tool\.poetry\.)?dependencies\]([\s\S]*?)(?:\n\[|$)/);
|
|
@@ -539,10 +706,10 @@ function readPyproject(cwd) {
|
|
|
539
706
|
}
|
|
540
707
|
}
|
|
541
708
|
function readCargoToml(cwd) {
|
|
542
|
-
const path =
|
|
543
|
-
if (!
|
|
709
|
+
const path = join5(cwd, "Cargo.toml");
|
|
710
|
+
if (!existsSync5(path)) return null;
|
|
544
711
|
try {
|
|
545
|
-
const content =
|
|
712
|
+
const content = readFileSync5(path, "utf-8");
|
|
546
713
|
const match = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
547
714
|
return { name: match?.[1] ?? null };
|
|
548
715
|
} catch {
|
|
@@ -588,10 +755,10 @@ function detectBranchBase(cwd) {
|
|
|
588
755
|
}
|
|
589
756
|
function detectExistingEngines(cwd) {
|
|
590
757
|
const found = [];
|
|
591
|
-
if (
|
|
592
|
-
if (
|
|
593
|
-
if (
|
|
594
|
-
if (
|
|
758
|
+
if (existsSync5(join5(cwd, ".claude"))) found.push("claude");
|
|
759
|
+
if (existsSync5(join5(cwd, "AGENTS.md"))) found.push("agents-md");
|
|
760
|
+
if (existsSync5(join5(cwd, ".cursor"))) found.push("cursor");
|
|
761
|
+
if (existsSync5(join5(cwd, ".github", "copilot-instructions.md"))) found.push("copilot");
|
|
595
762
|
return found;
|
|
596
763
|
}
|
|
597
764
|
function detectPackageManager(cwd) {
|
|
@@ -600,42 +767,44 @@ function detectPackageManager(cwd) {
|
|
|
600
767
|
const tool = pkg.packageManager.split("@")[0];
|
|
601
768
|
if (tool === "pnpm" || tool === "npm" || tool === "yarn" || tool === "bun") return tool;
|
|
602
769
|
}
|
|
603
|
-
if (
|
|
604
|
-
if (
|
|
605
|
-
if (
|
|
606
|
-
if (
|
|
770
|
+
if (existsSync5(join5(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
771
|
+
if (existsSync5(join5(cwd, "bun.lockb")) || existsSync5(join5(cwd, "bun.lock"))) return "bun";
|
|
772
|
+
if (existsSync5(join5(cwd, "yarn.lock"))) return "yarn";
|
|
773
|
+
if (existsSync5(join5(cwd, "package-lock.json"))) return "npm";
|
|
607
774
|
return null;
|
|
608
775
|
}
|
|
609
776
|
function detectPackageManagerSource(cwd) {
|
|
610
777
|
const pkg = readPackageJson(cwd);
|
|
611
778
|
if (pkg?.packageManager) return "package.json";
|
|
612
|
-
if (
|
|
613
|
-
if (
|
|
614
|
-
if (
|
|
615
|
-
if (
|
|
779
|
+
if (existsSync5(join5(cwd, "pnpm-lock.yaml"))) return "pnpm-lock.yaml";
|
|
780
|
+
if (existsSync5(join5(cwd, "bun.lockb")) || existsSync5(join5(cwd, "bun.lock"))) return "bun.lock";
|
|
781
|
+
if (existsSync5(join5(cwd, "yarn.lock"))) return "yarn.lock";
|
|
782
|
+
if (existsSync5(join5(cwd, "package-lock.json"))) return "package-lock.json";
|
|
616
783
|
return "unknown";
|
|
617
784
|
}
|
|
618
785
|
function detectMonorepo(cwd) {
|
|
619
|
-
if (
|
|
620
|
-
if (
|
|
621
|
-
|
|
786
|
+
if (existsSync5(join5(cwd, "pnpm-workspace.yaml"))) {
|
|
787
|
+
if (collectWorkspacePatterns(cwd).length > 0) {
|
|
788
|
+
if (existsSync5(join5(cwd, "turbo.json"))) {
|
|
789
|
+
return { tool: "turbo", source: "turbo.json + pnpm-workspace.yaml" };
|
|
790
|
+
}
|
|
791
|
+
return { tool: "pnpm", source: "pnpm-workspace.yaml" };
|
|
622
792
|
}
|
|
623
|
-
return { tool: "pnpm", source: "pnpm-workspace.yaml" };
|
|
624
793
|
}
|
|
625
|
-
if (
|
|
794
|
+
if (existsSync5(join5(cwd, "turbo.json"))) {
|
|
626
795
|
return { tool: "turbo", source: "turbo.json" };
|
|
627
796
|
}
|
|
628
|
-
if (
|
|
797
|
+
if (existsSync5(join5(cwd, "nx.json"))) {
|
|
629
798
|
return { tool: "nx", source: "nx.json" };
|
|
630
799
|
}
|
|
631
|
-
if (
|
|
800
|
+
if (existsSync5(join5(cwd, "rush.json"))) {
|
|
632
801
|
return { tool: "rush", source: "rush.json" };
|
|
633
802
|
}
|
|
634
|
-
if (
|
|
803
|
+
if (existsSync5(join5(cwd, "lerna.json"))) {
|
|
635
804
|
return { tool: "lerna", source: "lerna.json" };
|
|
636
805
|
}
|
|
637
806
|
const pkg = readPackageJson(cwd);
|
|
638
|
-
if (pkg?.workspaces) {
|
|
807
|
+
if (pkg?.workspaces && collectWorkspacePatterns(cwd).length > 0) {
|
|
639
808
|
return { tool: "npm", source: "package.json workspaces" };
|
|
640
809
|
}
|
|
641
810
|
return null;
|
|
@@ -664,6 +833,7 @@ function detectStack(cwd, pkg, pyproject, cargo) {
|
|
|
664
833
|
forms: pick(deps, "pydantic") ?? null,
|
|
665
834
|
state: null,
|
|
666
835
|
test: pick(deps, "pytest") ?? null,
|
|
836
|
+
worker: pick(deps, "celery", "rq", "dramatiq", "apscheduler") ?? null,
|
|
667
837
|
deps: Array.from(deps)
|
|
668
838
|
};
|
|
669
839
|
}
|
|
@@ -675,6 +845,7 @@ function detectStack(cwd, pkg, pyproject, cargo) {
|
|
|
675
845
|
forms: null,
|
|
676
846
|
state: null,
|
|
677
847
|
test: null,
|
|
848
|
+
worker: null,
|
|
678
849
|
deps: []
|
|
679
850
|
};
|
|
680
851
|
}
|
|
@@ -687,10 +858,11 @@ function detectStack(cwd, pkg, pyproject, cargo) {
|
|
|
687
858
|
forms: null,
|
|
688
859
|
state: null,
|
|
689
860
|
test: null,
|
|
861
|
+
worker: null,
|
|
690
862
|
deps: []
|
|
691
863
|
};
|
|
692
864
|
}
|
|
693
|
-
const hasTs = nodeDeps.has("typescript") ||
|
|
865
|
+
const hasTs = nodeDeps.has("typescript") || existsSync5(join5(cwd, "tsconfig.json"));
|
|
694
866
|
const framework = (
|
|
695
867
|
// Application frameworks (they own the project)
|
|
696
868
|
pick(nodeDeps, "next") ?? pick(nodeDeps, "@nestjs/core") ?? pick(nodeDeps, "@medusajs/medusa") ?? pick(nodeDeps, "@keystone-6/core") ?? pick(nodeDeps, "expo") ?? pick(nodeDeps, "react-native") ?? pick(nodeDeps, "remix") ?? // Meta-frameworks on top of build tools — check before vite/react
|
|
@@ -702,6 +874,21 @@ function detectStack(cwd, pkg, pyproject, cargo) {
|
|
|
702
874
|
const forms = pick(nodeDeps, "formik") ?? pick(nodeDeps, "react-hook-form") ?? pick(nodeDeps, "@mantine/form") ?? pick(nodeDeps, "vee-validate") ?? null;
|
|
703
875
|
const state = pick(nodeDeps, "@reduxjs/toolkit") ?? pick(nodeDeps, "redux") ?? pick(nodeDeps, "zustand") ?? pick(nodeDeps, "jotai") ?? pick(nodeDeps, "valtio") ?? pick(nodeDeps, "@tanstack/react-query") ?? pick(nodeDeps, "@apollo/client") ?? null;
|
|
704
876
|
const test = pick(nodeDeps, "vitest") ?? pick(nodeDeps, "jest") ?? pick(nodeDeps, "@playwright/test") ?? pick(nodeDeps, "cypress") ?? null;
|
|
877
|
+
const worker = pick(
|
|
878
|
+
nodeDeps,
|
|
879
|
+
"agenda",
|
|
880
|
+
"bullmq",
|
|
881
|
+
"bull",
|
|
882
|
+
"bee-queue",
|
|
883
|
+
"bree",
|
|
884
|
+
"node-cron",
|
|
885
|
+
"cron",
|
|
886
|
+
"amqplib",
|
|
887
|
+
"amqp-connection-manager",
|
|
888
|
+
"kafkajs",
|
|
889
|
+
"sqs-consumer",
|
|
890
|
+
"rhea"
|
|
891
|
+
);
|
|
705
892
|
return {
|
|
706
893
|
language: hasTs ? "ts" : pkg ? "js" : "unknown",
|
|
707
894
|
framework,
|
|
@@ -709,6 +896,7 @@ function detectStack(cwd, pkg, pyproject, cargo) {
|
|
|
709
896
|
forms,
|
|
710
897
|
state,
|
|
711
898
|
test,
|
|
899
|
+
worker,
|
|
712
900
|
deps: Array.from(nodeDeps)
|
|
713
901
|
};
|
|
714
902
|
}
|
|
@@ -758,6 +946,7 @@ function pickPresetCandidate(stack, monorepo) {
|
|
|
758
946
|
if (fw === "fastify") return "fastify";
|
|
759
947
|
if (fw === "hono") return "hono";
|
|
760
948
|
if (fw === "elysia") return "elysia";
|
|
949
|
+
if (stack.worker && (fw === "express" || fw === null)) return "background-worker";
|
|
761
950
|
if (fw === "express") return "express-mongoose";
|
|
762
951
|
return "custom";
|
|
763
952
|
}
|
|
@@ -772,29 +961,24 @@ function guessQualityGate(pkg, pm, stack) {
|
|
|
772
961
|
const runner = pm ?? "npm";
|
|
773
962
|
const scripts = pkg.scripts ?? {};
|
|
774
963
|
const has = (name) => typeof scripts[name] === "string";
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
if (has("check:all")) {
|
|
779
|
-
return { fast: `${runner} run typecheck`, full: `${runner} run check:all` };
|
|
780
|
-
}
|
|
964
|
+
const run = (name) => `${runner} run ${name}`;
|
|
965
|
+
const typecheck = has("typecheck") ? run("typecheck") : has("type-check") ? run("type-check") : has("check") ? run("check") : has("compile") ? run("compile") : null;
|
|
966
|
+
if (has("validate")) return { fast: typecheck ?? run("validate"), full: run("validate") };
|
|
967
|
+
if (has("check:all")) return { fast: typecheck ?? run("check:all"), full: run("check:all") };
|
|
781
968
|
const fastParts = [];
|
|
782
|
-
if (
|
|
783
|
-
else if (has("type-check")) fastParts.push(`${runner} run type-check`);
|
|
784
|
-
else if (has("check")) fastParts.push(`${runner} run check`);
|
|
785
|
-
else if (has("compile")) fastParts.push(`${runner} run compile`);
|
|
969
|
+
if (typecheck) fastParts.push(typecheck);
|
|
786
970
|
const fullParts = [...fastParts];
|
|
787
|
-
if (has("lint")) fullParts.push(
|
|
788
|
-
if (has("test:unit")) fullParts.push(
|
|
789
|
-
else if (has("test")) fullParts.push(
|
|
790
|
-
if (
|
|
791
|
-
const fast = fastParts.join(" && ")
|
|
792
|
-
const full = fullParts.join(" && ")
|
|
971
|
+
if (has("lint")) fullParts.push(run("lint"));
|
|
972
|
+
if (has("test:unit")) fullParts.push(run("test:unit"));
|
|
973
|
+
else if (has("test")) fullParts.push(run("test"));
|
|
974
|
+
if (fullParts.length === 0) return null;
|
|
975
|
+
const fast = fastParts.length > 0 ? fastParts.join(" && ") : fullParts[0];
|
|
976
|
+
const full = fullParts.join(" && ");
|
|
793
977
|
return { fast, full };
|
|
794
978
|
}
|
|
795
979
|
|
|
796
980
|
// src/lib/plugins.ts
|
|
797
|
-
import { readFileSync as
|
|
981
|
+
import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
|
|
798
982
|
import { resolve as resolve3, sep } from "path";
|
|
799
983
|
import { z as z5 } from "zod";
|
|
800
984
|
var AGENT_ROLES = [
|
|
@@ -914,12 +1098,12 @@ function loadPlugin(pluginId) {
|
|
|
914
1098
|
}
|
|
915
1099
|
const packageRoot = getPluginPath(pluginId);
|
|
916
1100
|
const manifestPath = bundledPluginManifestPath(pluginId);
|
|
917
|
-
if (!
|
|
1101
|
+
if (!existsSync6(manifestPath)) {
|
|
918
1102
|
throw new PluginManifestError(`plugin.json not found at ${manifestPath}`);
|
|
919
1103
|
}
|
|
920
1104
|
let parsed;
|
|
921
1105
|
try {
|
|
922
|
-
parsed = JSON.parse(
|
|
1106
|
+
parsed = JSON.parse(readFileSync6(manifestPath, "utf-8"));
|
|
923
1107
|
} catch (err) {
|
|
924
1108
|
throw new PluginManifestError(`Invalid JSON in ${manifestPath}: ${err.message}`);
|
|
925
1109
|
}
|
|
@@ -976,8 +1160,8 @@ function loadEnabledPlugins(pluginsConfig) {
|
|
|
976
1160
|
}
|
|
977
1161
|
|
|
978
1162
|
// src/lib/migrate.ts
|
|
979
|
-
import { mkdirSync, existsSync as
|
|
980
|
-
import { join as
|
|
1163
|
+
import { mkdirSync, existsSync as existsSync7, copyFileSync, readdirSync as readdirSync3, statSync as statSync3, rmSync as rmSync2 } from "fs";
|
|
1164
|
+
import { join as join6, resolve as resolve4, dirname as dirname3 } from "path";
|
|
981
1165
|
|
|
982
1166
|
// src/lib/home.ts
|
|
983
1167
|
import { homedir } from "os";
|
|
@@ -994,7 +1178,7 @@ function safeHomedir() {
|
|
|
994
1178
|
|
|
995
1179
|
// src/lib/migrate.ts
|
|
996
1180
|
function migrationsRootLazy() {
|
|
997
|
-
return
|
|
1181
|
+
return join6(safeHomedir(), ".navori", "migrations");
|
|
998
1182
|
}
|
|
999
1183
|
function timestamp() {
|
|
1000
1184
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -1018,7 +1202,7 @@ function copyRecursive(src, dest) {
|
|
|
1018
1202
|
if (stat.isDirectory()) {
|
|
1019
1203
|
mkdirSync(dest, { recursive: true });
|
|
1020
1204
|
for (const entry of readdirSync3(src)) {
|
|
1021
|
-
copyRecursive(
|
|
1205
|
+
copyRecursive(join6(src, entry), join6(dest, entry));
|
|
1022
1206
|
}
|
|
1023
1207
|
} else if (stat.isFile()) {
|
|
1024
1208
|
mkdirSync(dirname3(dest), { recursive: true });
|
|
@@ -1026,7 +1210,7 @@ function copyRecursive(src, dest) {
|
|
|
1026
1210
|
}
|
|
1027
1211
|
}
|
|
1028
1212
|
function createMigrationBackup(repoRoot, repoName) {
|
|
1029
|
-
const dir =
|
|
1213
|
+
const dir = join6(migrationsRootLazy(), timestamp(), repoName);
|
|
1030
1214
|
mkdirSync(dir, { recursive: true });
|
|
1031
1215
|
const candidates = [
|
|
1032
1216
|
".claude",
|
|
@@ -1040,8 +1224,8 @@ function createMigrationBackup(repoRoot, repoName) {
|
|
|
1040
1224
|
const moved = [];
|
|
1041
1225
|
for (const rel of candidates) {
|
|
1042
1226
|
const src = resolve4(repoRoot, rel);
|
|
1043
|
-
if (!
|
|
1044
|
-
const dest =
|
|
1227
|
+
if (!existsSync7(src)) continue;
|
|
1228
|
+
const dest = join6(dir, rel);
|
|
1045
1229
|
copyRecursive(src, dest);
|
|
1046
1230
|
moved.push(rel);
|
|
1047
1231
|
}
|
|
@@ -1050,7 +1234,7 @@ function createMigrationBackup(repoRoot, repoName) {
|
|
|
1050
1234
|
function removeOriginals(repoRoot, paths) {
|
|
1051
1235
|
for (const rel of paths) {
|
|
1052
1236
|
const target = resolve4(repoRoot, rel);
|
|
1053
|
-
if (!
|
|
1237
|
+
if (!existsSync7(target)) continue;
|
|
1054
1238
|
rmSync2(target, { recursive: true, force: true });
|
|
1055
1239
|
}
|
|
1056
1240
|
}
|
|
@@ -1059,11 +1243,11 @@ function migrationsRoot() {
|
|
|
1059
1243
|
}
|
|
1060
1244
|
|
|
1061
1245
|
// src/lib/workspace.ts
|
|
1062
|
-
import { existsSync as
|
|
1063
|
-
import { join as
|
|
1246
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, readdirSync as readdirSync4, mkdirSync as mkdirSync2, statSync as statSync4, copyFileSync as copyFileSync2, rmSync as rmSync3 } from "fs";
|
|
1247
|
+
import { join as join7 } from "path";
|
|
1064
1248
|
import { z as z6 } from "zod";
|
|
1065
1249
|
function workspacesRootLazy() {
|
|
1066
|
-
return
|
|
1250
|
+
return join7(safeHomedir(), ".navori", "workspaces");
|
|
1067
1251
|
}
|
|
1068
1252
|
var MANIFEST_NAME = "workspace.json";
|
|
1069
1253
|
var RepoEntrySchema = z6.object({
|
|
@@ -1075,6 +1259,7 @@ var RepoEntrySchema = z6.object({
|
|
|
1075
1259
|
});
|
|
1076
1260
|
var WorkspaceDefaultsSchema = z6.object({
|
|
1077
1261
|
branchBase: z6.string().optional(),
|
|
1262
|
+
prTarget: z6.string().optional(),
|
|
1078
1263
|
commits: z6.enum(["conventional", "conventional-es", "free"]).optional(),
|
|
1079
1264
|
language: z6.enum(["es", "en"]).optional(),
|
|
1080
1265
|
engines: z6.array(z6.string()).optional(),
|
|
@@ -1105,13 +1290,13 @@ var WorkspaceError = class extends NavoriError {
|
|
|
1105
1290
|
}
|
|
1106
1291
|
};
|
|
1107
1292
|
function workspaceDirectory(name) {
|
|
1108
|
-
return
|
|
1293
|
+
return join7(workspacesRootLazy(), name);
|
|
1109
1294
|
}
|
|
1110
1295
|
function workspacePath(name) {
|
|
1111
|
-
return
|
|
1296
|
+
return join7(workspaceDirectory(name), MANIFEST_NAME);
|
|
1112
1297
|
}
|
|
1113
1298
|
function legacyWorkspacePath(name) {
|
|
1114
|
-
return
|
|
1299
|
+
return join7(workspacesRootLazy(), `${name}.json`);
|
|
1115
1300
|
}
|
|
1116
1301
|
function ensureWorkspacesRoot() {
|
|
1117
1302
|
mkdirSync2(workspacesRootLazy(), { recursive: true });
|
|
@@ -1119,7 +1304,7 @@ function ensureWorkspacesRoot() {
|
|
|
1119
1304
|
function migrateLegacyLayoutIfNeeded(name) {
|
|
1120
1305
|
const legacy = legacyWorkspacePath(name);
|
|
1121
1306
|
const current = workspacePath(name);
|
|
1122
|
-
if (!
|
|
1307
|
+
if (!existsSync8(legacy) || existsSync8(current)) return;
|
|
1123
1308
|
const dir = workspaceDirectory(name);
|
|
1124
1309
|
mkdirSync2(dir, { recursive: true });
|
|
1125
1310
|
copyFileSync2(legacy, current);
|
|
@@ -1129,7 +1314,7 @@ function migrateLegacyLayoutIfNeeded(name) {
|
|
|
1129
1314
|
}
|
|
1130
1315
|
}
|
|
1131
1316
|
function listWorkspaces() {
|
|
1132
|
-
if (!
|
|
1317
|
+
if (!existsSync8(workspacesRootLazy())) return [];
|
|
1133
1318
|
for (const entry of readdirSync4(workspacesRootLazy())) {
|
|
1134
1319
|
if (entry.endsWith(".json")) {
|
|
1135
1320
|
const name = entry.replace(/\.json$/, "");
|
|
@@ -1138,23 +1323,23 @@ function listWorkspaces() {
|
|
|
1138
1323
|
}
|
|
1139
1324
|
const names = [];
|
|
1140
1325
|
for (const entry of readdirSync4(workspacesRootLazy())) {
|
|
1141
|
-
const full =
|
|
1326
|
+
const full = join7(workspacesRootLazy(), entry);
|
|
1142
1327
|
try {
|
|
1143
1328
|
if (!statSync4(full).isDirectory()) continue;
|
|
1144
1329
|
} catch {
|
|
1145
1330
|
continue;
|
|
1146
1331
|
}
|
|
1147
|
-
if (
|
|
1332
|
+
if (existsSync8(join7(full, MANIFEST_NAME))) names.push(entry);
|
|
1148
1333
|
}
|
|
1149
1334
|
return names.sort();
|
|
1150
1335
|
}
|
|
1151
1336
|
function loadWorkspace(name) {
|
|
1152
1337
|
migrateLegacyLayoutIfNeeded(name);
|
|
1153
1338
|
const path = workspacePath(name);
|
|
1154
|
-
if (!
|
|
1339
|
+
if (!existsSync8(path)) return null;
|
|
1155
1340
|
let raw;
|
|
1156
1341
|
try {
|
|
1157
|
-
raw =
|
|
1342
|
+
raw = readFileSync7(path, "utf-8").replace(/^/, "");
|
|
1158
1343
|
} catch (err) {
|
|
1159
1344
|
throw new WorkspaceError(`Cannot read workspace '${name}': ${err.message}`);
|
|
1160
1345
|
}
|
|
@@ -1174,7 +1359,7 @@ function writeWorkspace(workspace) {
|
|
|
1174
1359
|
ensureWorkspacesRoot();
|
|
1175
1360
|
const dir = workspaceDirectory(workspace.name);
|
|
1176
1361
|
mkdirSync2(dir, { recursive: true });
|
|
1177
|
-
mkdirSync2(
|
|
1362
|
+
mkdirSync2(join7(dir, workspace.ticketsDir), { recursive: true });
|
|
1178
1363
|
const path = workspacePath(workspace.name);
|
|
1179
1364
|
const validated = WorkspaceConfigSchema.parse({
|
|
1180
1365
|
$schema: "https://navori.dev/schema/navori.workspace.v1.json",
|
|
@@ -1187,18 +1372,18 @@ function writeWorkspace(workspace) {
|
|
|
1187
1372
|
// src/commands/render.ts
|
|
1188
1373
|
import { defineCommand } from "citty";
|
|
1189
1374
|
import * as p from "@clack/prompts";
|
|
1190
|
-
import { existsSync as
|
|
1375
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1191
1376
|
import { resolve as resolve9 } from "path";
|
|
1192
1377
|
|
|
1193
1378
|
// src/engines/claude/index.ts
|
|
1194
|
-
import { existsSync as
|
|
1195
|
-
import { basename as basename3, dirname as dirname5, join as
|
|
1379
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync11, chmodSync } from "fs";
|
|
1380
|
+
import { basename as basename3, dirname as dirname5, join as join9, relative as relative2, resolve as resolve8 } from "path";
|
|
1196
1381
|
|
|
1197
1382
|
// src/lib/backup.ts
|
|
1198
|
-
import { mkdirSync as mkdirSync3, copyFileSync as copyFileSync3, existsSync as
|
|
1199
|
-
import { dirname as dirname4, join as
|
|
1383
|
+
import { mkdirSync as mkdirSync3, copyFileSync as copyFileSync3, existsSync as existsSync9, readdirSync as readdirSync5, rmSync as rmSync4, statSync as statSync5 } from "fs";
|
|
1384
|
+
import { dirname as dirname4, join as join8, relative, resolve as resolve5 } from "path";
|
|
1200
1385
|
function backupRootLazy() {
|
|
1201
|
-
return
|
|
1386
|
+
return join8(safeHomedir(), ".navori", "backups");
|
|
1202
1387
|
}
|
|
1203
1388
|
var DEFAULT_RETENTION_DAYS = 30;
|
|
1204
1389
|
function timestamp2() {
|
|
@@ -1219,13 +1404,13 @@ function timestamp2() {
|
|
|
1219
1404
|
].join("");
|
|
1220
1405
|
}
|
|
1221
1406
|
function createBackup(repoRoot, paths, options = {}) {
|
|
1222
|
-
const dir =
|
|
1407
|
+
const dir = join8(backupRootLazy(), timestamp2());
|
|
1223
1408
|
mkdirSync3(dir, { recursive: true });
|
|
1224
1409
|
const exclude = (options.exclude ?? []).map((e) => e.replace(/\/+$/, ""));
|
|
1225
1410
|
const copied = [];
|
|
1226
1411
|
for (const p16 of paths) {
|
|
1227
1412
|
const abs = resolve5(repoRoot, p16);
|
|
1228
|
-
if (!
|
|
1413
|
+
if (!existsSync9(abs)) continue;
|
|
1229
1414
|
copyRecursive2(abs, dir, repoRoot, exclude, copied);
|
|
1230
1415
|
}
|
|
1231
1416
|
return { path: dir, files: copied };
|
|
@@ -1236,12 +1421,12 @@ function copyRecursive2(abs, backupDir, repoRoot, exclude, copied) {
|
|
|
1236
1421
|
const stat = statSync5(abs);
|
|
1237
1422
|
if (stat.isDirectory()) {
|
|
1238
1423
|
for (const entry of readdirSync5(abs)) {
|
|
1239
|
-
copyRecursive2(
|
|
1424
|
+
copyRecursive2(join8(abs, entry), backupDir, repoRoot, exclude, copied);
|
|
1240
1425
|
}
|
|
1241
1426
|
return;
|
|
1242
1427
|
}
|
|
1243
1428
|
if (!stat.isFile()) return;
|
|
1244
|
-
const dest =
|
|
1429
|
+
const dest = join8(backupDir, rel);
|
|
1245
1430
|
mkdirSync3(dirname4(dest), { recursive: true });
|
|
1246
1431
|
copyFileSync3(abs, dest);
|
|
1247
1432
|
copied.push(rel);
|
|
@@ -1255,11 +1440,11 @@ function isExcluded(rel, exclude) {
|
|
|
1255
1440
|
}
|
|
1256
1441
|
function purgeOldBackups(retentionDays = DEFAULT_RETENTION_DAYS) {
|
|
1257
1442
|
const root = backupRootLazy();
|
|
1258
|
-
if (!
|
|
1443
|
+
if (!existsSync9(root)) return [];
|
|
1259
1444
|
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
|
|
1260
1445
|
const pruned = [];
|
|
1261
1446
|
for (const entry of readdirSync5(root)) {
|
|
1262
|
-
const full =
|
|
1447
|
+
const full = join8(root, entry);
|
|
1263
1448
|
const stat = statSync5(full);
|
|
1264
1449
|
if (!stat.isDirectory()) continue;
|
|
1265
1450
|
if (stat.mtimeMs < cutoff) {
|
|
@@ -1274,7 +1459,7 @@ function backupRoot() {
|
|
|
1274
1459
|
}
|
|
1275
1460
|
|
|
1276
1461
|
// src/lib/render-plan.ts
|
|
1277
|
-
import { readFileSync as
|
|
1462
|
+
import { readFileSync as readFileSync8, existsSync as existsSync10 } from "fs";
|
|
1278
1463
|
import { resolve as resolve6 } from "path";
|
|
1279
1464
|
|
|
1280
1465
|
// src/lib/marker.ts
|
|
@@ -1492,7 +1677,7 @@ var CORE_SOURCE_ID = "@navori/core";
|
|
|
1492
1677
|
var CORE_MANAGED_ASSETS = [
|
|
1493
1678
|
{ id: "idioma-rol", relPath: "core-assets/managed/idioma-rol.md", availableLanguages: ["es"] },
|
|
1494
1679
|
{ id: "formato-respuesta", relPath: "core-assets/managed/formato-respuesta.md", availableLanguages: ["es"] },
|
|
1495
|
-
{ id: "tipado-fuerte", relPath: "core-assets/managed/tipado-fuerte.md", availableLanguages: ["es"] },
|
|
1680
|
+
{ id: "tipado-fuerte", relPath: "core-assets/managed/tipado-fuerte.md", availableLanguages: ["es"], condition: "project.typedLanguage" },
|
|
1496
1681
|
{ id: "operaciones-seguras", relPath: "core-assets/managed/operaciones-seguras.md", availableLanguages: ["es"] },
|
|
1497
1682
|
{ id: "arranque-sesion", relPath: "core-assets/managed/arranque-sesion.md", availableLanguages: ["es"] },
|
|
1498
1683
|
{ id: "cierre-sesion", relPath: "core-assets/managed/cierre-sesion.md", availableLanguages: ["es"] }
|
|
@@ -1505,7 +1690,7 @@ function resolveAssetPath(asset, language = "es") {
|
|
|
1505
1690
|
}
|
|
1506
1691
|
const langPath = asset.relPath.replace(/^core-assets\/managed\//, `core-assets/managed/${language}/`);
|
|
1507
1692
|
const abs = resolve6(root, langPath);
|
|
1508
|
-
if (
|
|
1693
|
+
if (existsSync10(abs)) return { path: abs, fallback: false };
|
|
1509
1694
|
return { path: resolve6(root, asset.relPath), fallback: true };
|
|
1510
1695
|
}
|
|
1511
1696
|
function interpolateTemplate(content, config) {
|
|
@@ -1529,7 +1714,8 @@ function interpolateTemplate(content, config) {
|
|
|
1529
1714
|
return match;
|
|
1530
1715
|
});
|
|
1531
1716
|
}
|
|
1532
|
-
function computeRenderPlan(existing,
|
|
1717
|
+
function computeRenderPlan(existing, inputConfig, repoRoot, options = {}) {
|
|
1718
|
+
const config = effectiveConfig(inputConfig);
|
|
1533
1719
|
const skipIds = options.skipIds ?? /* @__PURE__ */ new Set();
|
|
1534
1720
|
const forceIds = options.forceIds ?? /* @__PURE__ */ new Set();
|
|
1535
1721
|
let working = existing;
|
|
@@ -1558,7 +1744,7 @@ function computeRenderPlan(existing, config, repoRoot, options = {}) {
|
|
|
1558
1744
|
}
|
|
1559
1745
|
const resolved = resolveAssetPath(asset, language);
|
|
1560
1746
|
if (resolved.fallback) languageFallbacks.push(asset.id);
|
|
1561
|
-
const rawContent =
|
|
1747
|
+
const rawContent = readFileSync8(resolved.path, "utf-8");
|
|
1562
1748
|
const content = interpolateTemplate(rawContent, config);
|
|
1563
1749
|
const result = injectManagedSection(working, asset.id, content, {
|
|
1564
1750
|
source: CORE_SOURCE_ID,
|
|
@@ -1603,7 +1789,7 @@ function computeRenderPlan(existing, config, repoRoot, options = {}) {
|
|
|
1603
1789
|
for (const extra of loaded.def.extras.managed) {
|
|
1604
1790
|
if (skipIds.has(extra.id)) continue;
|
|
1605
1791
|
const absPath = resolve6(loaded.assetRoot, extra.relPath);
|
|
1606
|
-
const rawContent =
|
|
1792
|
+
const rawContent = readFileSync8(absPath, "utf-8");
|
|
1607
1793
|
const content = interpolateTemplate(rawContent, config);
|
|
1608
1794
|
const result = injectManagedSection(working, extra.id, content, {
|
|
1609
1795
|
source: CORE_SOURCE_ID,
|
|
@@ -1649,7 +1835,7 @@ function computeRenderPlan(existing, config, repoRoot, options = {}) {
|
|
|
1649
1835
|
const pluginSource = `@navori/plugin-${plugin.manifest.id}`;
|
|
1650
1836
|
for (const entry of plugin.managedAssets) {
|
|
1651
1837
|
if (skipIds.has(entry.id)) continue;
|
|
1652
|
-
const rawContent =
|
|
1838
|
+
const rawContent = readFileSync8(entry.absPath, "utf-8");
|
|
1653
1839
|
const content = interpolateTemplate(rawContent, config);
|
|
1654
1840
|
const result = injectManagedSection(working, entry.id, content, {
|
|
1655
1841
|
source: pluginSource,
|
|
@@ -1707,7 +1893,7 @@ function isNavoriOwnedSettings(parsed) {
|
|
|
1707
1893
|
}
|
|
1708
1894
|
|
|
1709
1895
|
// src/engines/claude/build-settings.ts
|
|
1710
|
-
import { readFileSync as
|
|
1896
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
1711
1897
|
import { resolve as resolve7 } from "path";
|
|
1712
1898
|
|
|
1713
1899
|
// src/engines/claude/interpolate.ts
|
|
@@ -1794,7 +1980,7 @@ var GUARD_HOOK_DEST = ".claude/hooks/guard-destructive.sh";
|
|
|
1794
1980
|
var SETTINGS_BASE_REL = "core-assets/settings/settings-base.json";
|
|
1795
1981
|
function buildClaudeSettings(config, plugins) {
|
|
1796
1982
|
const basePath = resolve7(getCoreRoot(), SETTINGS_BASE_REL);
|
|
1797
|
-
const baseRaw =
|
|
1983
|
+
const baseRaw = readFileSync9(basePath, "utf-8");
|
|
1798
1984
|
const baseInterp = interpolate(baseRaw, config, {
|
|
1799
1985
|
extraVars: { coreVersion: readBundledCoreVersion() }
|
|
1800
1986
|
});
|
|
@@ -1846,7 +2032,38 @@ function buildClaudeSettings(config, plugins) {
|
|
|
1846
2032
|
});
|
|
1847
2033
|
}
|
|
1848
2034
|
}
|
|
1849
|
-
return settings;
|
|
2035
|
+
return coalesceHookMatchers(settings);
|
|
2036
|
+
}
|
|
2037
|
+
function coalesceHookMatchers(settings) {
|
|
2038
|
+
const hooks = settings.hooks;
|
|
2039
|
+
if (typeof hooks !== "object" || hooks === null || Array.isArray(hooks)) return settings;
|
|
2040
|
+
const coalesced = {};
|
|
2041
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
2042
|
+
if (!Array.isArray(entries)) {
|
|
2043
|
+
coalesced[event] = entries;
|
|
2044
|
+
continue;
|
|
2045
|
+
}
|
|
2046
|
+
const buckets = [];
|
|
2047
|
+
const passthrough = [];
|
|
2048
|
+
for (const entry of entries) {
|
|
2049
|
+
if (typeof entry !== "object" || entry === null || !Array.isArray(entry.hooks)) {
|
|
2050
|
+
passthrough.push(entry);
|
|
2051
|
+
continue;
|
|
2052
|
+
}
|
|
2053
|
+
const e = entry;
|
|
2054
|
+
const existing = buckets.find((b) => b.matcher === e.matcher);
|
|
2055
|
+
if (existing) {
|
|
2056
|
+
const seen = new Set(existing.hooks.map((h) => JSON.stringify(h)));
|
|
2057
|
+
for (const h of e.hooks) {
|
|
2058
|
+
if (!seen.has(JSON.stringify(h))) existing.hooks.push(h);
|
|
2059
|
+
}
|
|
2060
|
+
} else {
|
|
2061
|
+
buckets.push({ ...e.matcher !== void 0 ? { matcher: e.matcher } : {}, hooks: [...e.hooks] });
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
coalesced[event] = [...buckets, ...passthrough];
|
|
2065
|
+
}
|
|
2066
|
+
return { ...settings, hooks: coalesced };
|
|
1850
2067
|
}
|
|
1851
2068
|
function pluginHooksToClaudeShape(entries) {
|
|
1852
2069
|
const grouped = {};
|
|
@@ -1866,7 +2083,7 @@ function pluginHooksToClaudeShape(entries) {
|
|
|
1866
2083
|
}
|
|
1867
2084
|
|
|
1868
2085
|
// src/engines/claude/render-managed-file.ts
|
|
1869
|
-
import { readFileSync as
|
|
2086
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
1870
2087
|
|
|
1871
2088
|
// src/lib/frontmatter.ts
|
|
1872
2089
|
var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
@@ -1943,7 +2160,7 @@ function serialize(merged, assetFm) {
|
|
|
1943
2160
|
// src/engines/claude/render-managed-file.ts
|
|
1944
2161
|
function renderManagedFile(input) {
|
|
1945
2162
|
const commentStyle = input.commentStyle ?? inferCommentStyle(input.assetPath);
|
|
1946
|
-
const raw =
|
|
2163
|
+
const raw = readFileSync10(input.assetPath, "utf-8");
|
|
1947
2164
|
const asset = parseAsset(raw, commentStyle);
|
|
1948
2165
|
const interpolatedFmObj = interpolateFrontmatter(asset.frontmatter, input.config, input.extraVars);
|
|
1949
2166
|
const interpolatedBody = interpolate(asset.managedBody, input.config, {
|
|
@@ -2077,27 +2294,44 @@ var CORE_AGENTS = [
|
|
|
2077
2294
|
var CORE_SKILLS = ["verify-before-done", "loop-back-debug", "review-diff"];
|
|
2078
2295
|
var CORE_META = { source: "@navori/core", version: readBundledCoreVersion() };
|
|
2079
2296
|
var SKILLS_INDEX_ID = "skills-index";
|
|
2297
|
+
function extraConditionMet(extra, config) {
|
|
2298
|
+
if (!extra.condition) return true;
|
|
2299
|
+
return resolveCondition(config, extra.condition);
|
|
2300
|
+
}
|
|
2080
2301
|
function buildSkillsIndexBody(config, localSkills, repoRoot) {
|
|
2081
2302
|
const rows = [];
|
|
2082
|
-
|
|
2303
|
+
const listed = /* @__PURE__ */ new Set();
|
|
2304
|
+
for (const id of CORE_SKILLS) {
|
|
2305
|
+
rows.push(`- \`${id}\` \u2014 navori`);
|
|
2306
|
+
listed.add(id);
|
|
2307
|
+
}
|
|
2083
2308
|
if (config.preset && config.preset !== "custom") {
|
|
2084
2309
|
try {
|
|
2085
2310
|
const loaded = loadPreset(config.preset, repoRoot);
|
|
2086
2311
|
for (const e of loaded?.def.extras.skills ?? []) {
|
|
2312
|
+
if (!extraConditionMet(e, config)) continue;
|
|
2087
2313
|
const name = basename3(e.destRelPath).replace(/\.md$/, "");
|
|
2088
2314
|
rows.push(`- \`${name}\` \u2014 preset (\`${config.preset}\`)`);
|
|
2315
|
+
listed.add(name);
|
|
2089
2316
|
}
|
|
2090
2317
|
} catch {
|
|
2091
2318
|
}
|
|
2092
2319
|
}
|
|
2320
|
+
for (const id of config.project?.libraries ?? []) {
|
|
2321
|
+
if (listed.has(id) || !librarySkillById(id)) continue;
|
|
2322
|
+
rows.push(`- \`${id}\` \u2014 library (detected)`);
|
|
2323
|
+
listed.add(id);
|
|
2324
|
+
}
|
|
2093
2325
|
for (const name of localSkills) {
|
|
2094
2326
|
rows.push(`- \`${name}\` \u2014 project-local (\`.claude/skills/${name}.md\`)`);
|
|
2095
2327
|
}
|
|
2328
|
+
if (rows.length === 0) return null;
|
|
2329
|
+
const localNote = localSkills.length > 0 ? ["Los `project-local` son tuyos \u2014 navori los indexa pero no toca su contenido."] : [];
|
|
2096
2330
|
return [
|
|
2097
2331
|
"## Skills disponibles",
|
|
2098
2332
|
"",
|
|
2099
2333
|
"Skills que los agentes pueden aplicar; cada uno vive en `.claude/skills/<id>.md`.",
|
|
2100
|
-
|
|
2334
|
+
...localNote,
|
|
2101
2335
|
"",
|
|
2102
2336
|
...rows,
|
|
2103
2337
|
""
|
|
@@ -2147,7 +2381,8 @@ function buildContextoProyectoBody(config) {
|
|
|
2147
2381
|
""
|
|
2148
2382
|
].join("\n");
|
|
2149
2383
|
}
|
|
2150
|
-
function renderClaudeEngine(cwd,
|
|
2384
|
+
function renderClaudeEngine(cwd, inputConfig, options = {}) {
|
|
2385
|
+
const config = effectiveConfig(inputConfig);
|
|
2151
2386
|
const dryRun = options.dryRun === true;
|
|
2152
2387
|
const force = options.force === true;
|
|
2153
2388
|
const repoRoot = options.repoRoot ?? cwd;
|
|
@@ -2156,8 +2391,8 @@ function renderClaudeEngine(cwd, config, options = {}) {
|
|
|
2156
2391
|
const warnings = [];
|
|
2157
2392
|
const pending = [];
|
|
2158
2393
|
let inspected = 0;
|
|
2159
|
-
const claudeMdPath =
|
|
2160
|
-
const claudeMdExisting =
|
|
2394
|
+
const claudeMdPath = join9(cwd, "CLAUDE.md");
|
|
2395
|
+
const claudeMdExisting = existsSync11(claudeMdPath) ? readFileSync11(claudeMdPath, "utf-8") : "";
|
|
2161
2396
|
const claudeMdPlan = computeRenderPlan(claudeMdExisting, config, repoRoot, {
|
|
2162
2397
|
skipIds: options.skipIds,
|
|
2163
2398
|
forceIds: options.forceIds
|
|
@@ -2165,11 +2400,12 @@ function renderClaudeEngine(cwd, config, options = {}) {
|
|
|
2165
2400
|
inspected += 1;
|
|
2166
2401
|
const localSkills = config.project?.localSkills ?? [];
|
|
2167
2402
|
let claudeMdContent = claudeMdPlan.next;
|
|
2168
|
-
|
|
2403
|
+
const skillsIndexBody = buildSkillsIndexBody(config, localSkills, repoRoot);
|
|
2404
|
+
if (skillsIndexBody !== null) {
|
|
2169
2405
|
const result = injectManagedSection(
|
|
2170
2406
|
claudeMdContent,
|
|
2171
2407
|
SKILLS_INDEX_ID,
|
|
2172
|
-
|
|
2408
|
+
skillsIndexBody,
|
|
2173
2409
|
CORE_META,
|
|
2174
2410
|
"html",
|
|
2175
2411
|
options.forceIds?.has(SKILLS_INDEX_ID) ?? false
|
|
@@ -2313,6 +2549,9 @@ function renderClaudeEngine(cwd, config, options = {}) {
|
|
|
2313
2549
|
} else {
|
|
2314
2550
|
warnings.push("quality-gate hook skipped: config.qualityGate.fast no est\xE1 set");
|
|
2315
2551
|
}
|
|
2552
|
+
const renderedSkillDests = new Set(
|
|
2553
|
+
CORE_SKILLS.map((id) => `.claude/skills/${id}.md`)
|
|
2554
|
+
);
|
|
2316
2555
|
if (config.preset && config.preset !== "custom") {
|
|
2317
2556
|
let loaded = null;
|
|
2318
2557
|
try {
|
|
@@ -2336,6 +2575,8 @@ function renderClaudeEngine(cwd, config, options = {}) {
|
|
|
2336
2575
|
...loaded.def.extras.hooks.map((e) => ({ extra: e, exec: true }))
|
|
2337
2576
|
];
|
|
2338
2577
|
for (const { extra, exec } of allFileExtras) {
|
|
2578
|
+
if (!extraConditionMet(extra, config)) continue;
|
|
2579
|
+
renderedSkillDests.add(extra.destRelPath);
|
|
2339
2580
|
inspected += 1;
|
|
2340
2581
|
applyManagedFilePlan(
|
|
2341
2582
|
planManagedFile({
|
|
@@ -2354,6 +2595,32 @@ function renderClaudeEngine(cwd, config, options = {}) {
|
|
|
2354
2595
|
}
|
|
2355
2596
|
}
|
|
2356
2597
|
}
|
|
2598
|
+
for (const id of config.project?.libraries ?? []) {
|
|
2599
|
+
const skill = librarySkillById(id);
|
|
2600
|
+
if (!skill) continue;
|
|
2601
|
+
const destRelPath = `.claude/skills/${id}.md`;
|
|
2602
|
+
if (renderedSkillDests.has(destRelPath)) continue;
|
|
2603
|
+
renderedSkillDests.add(destRelPath);
|
|
2604
|
+
inspected += 1;
|
|
2605
|
+
applyManagedFilePlan(
|
|
2606
|
+
planManagedFile({
|
|
2607
|
+
cwd,
|
|
2608
|
+
assetRoot: coreAssets,
|
|
2609
|
+
assetRelPath: `lib-skills/${id}.md`,
|
|
2610
|
+
destRelPath,
|
|
2611
|
+
// The managed-block id is the bare skill id — the SAME id the
|
|
2612
|
+
// express-mongoose preset used to write for mongoose/zod/joi before they
|
|
2613
|
+
// moved to this layer. On upgrade the marker is recognized and updated
|
|
2614
|
+
// in place; a distinct id (e.g. `${id}-lib`) would leave the preset-era
|
|
2615
|
+
// block untouched and append a duplicate block in the same file.
|
|
2616
|
+
managedId: id,
|
|
2617
|
+
config
|
|
2618
|
+
}),
|
|
2619
|
+
cwd,
|
|
2620
|
+
pending,
|
|
2621
|
+
skipped
|
|
2622
|
+
);
|
|
2623
|
+
}
|
|
2357
2624
|
for (const plugin of enabledPlugins) {
|
|
2358
2625
|
for (const script of plugin.scriptAssets) {
|
|
2359
2626
|
inspected += 1;
|
|
@@ -2399,7 +2666,7 @@ function renderClaudeEngine(cwd, config, options = {}) {
|
|
|
2399
2666
|
};
|
|
2400
2667
|
}
|
|
2401
2668
|
if (!dryRun) {
|
|
2402
|
-
const hasExistingTarget = pending.some((p16) =>
|
|
2669
|
+
const hasExistingTarget = pending.some((p16) => existsSync11(p16.path));
|
|
2403
2670
|
if (hasExistingTarget) {
|
|
2404
2671
|
const handle = createBackup(cwd, ["CLAUDE.md", ".claude"], {
|
|
2405
2672
|
exclude: [".claude/settings.local.json", ".claude/progress"]
|
|
@@ -2444,15 +2711,15 @@ function isAgentEnabled(config, key) {
|
|
|
2444
2711
|
return h[key] !== false;
|
|
2445
2712
|
}
|
|
2446
2713
|
function planSettings(cwd, config, plugins, force = false) {
|
|
2447
|
-
const path =
|
|
2714
|
+
const path = join9(cwd, ".claude/settings.json");
|
|
2448
2715
|
const newSettings = buildClaudeSettings(config, plugins);
|
|
2449
2716
|
const newJson = JSON.stringify(newSettings, null, 2) + "\n";
|
|
2450
|
-
if (!
|
|
2717
|
+
if (!existsSync11(path)) {
|
|
2451
2718
|
return { kind: "write", path, content: newJson, status: "created" };
|
|
2452
2719
|
}
|
|
2453
2720
|
let parsed;
|
|
2454
2721
|
try {
|
|
2455
|
-
parsed = JSON.parse(
|
|
2722
|
+
parsed = JSON.parse(readFileSync11(path, "utf-8"));
|
|
2456
2723
|
} catch (err) {
|
|
2457
2724
|
if (force) {
|
|
2458
2725
|
return { kind: "write", path, content: newJson, status: "updated" };
|
|
@@ -2473,14 +2740,14 @@ function planSettings(cwd, config, plugins, force = false) {
|
|
|
2473
2740
|
reason: "settings.json existe sin `$navori.managed = true`. Corr\xE9 'navori init' en modo replace o 'navori render --force --apply' para adoptar."
|
|
2474
2741
|
};
|
|
2475
2742
|
}
|
|
2476
|
-
const current =
|
|
2743
|
+
const current = readFileSync11(path, "utf-8");
|
|
2477
2744
|
if (current === newJson) return { kind: "noop" };
|
|
2478
2745
|
return { kind: "write", path, content: newJson, status: "updated" };
|
|
2479
2746
|
}
|
|
2480
2747
|
function planManagedFile(input) {
|
|
2481
2748
|
const assetPath = resolve8(input.assetRoot, input.assetRelPath);
|
|
2482
|
-
const destPath =
|
|
2483
|
-
const existing =
|
|
2749
|
+
const destPath = join9(input.cwd, input.destRelPath);
|
|
2750
|
+
const existing = existsSync11(destPath) ? readFileSync11(destPath, "utf-8") : null;
|
|
2484
2751
|
const result = renderManagedFile({
|
|
2485
2752
|
assetPath,
|
|
2486
2753
|
existingContent: existing,
|
|
@@ -2507,10 +2774,10 @@ function applyManagedFilePlan(plan, cwd, pending, skipped, chmodExec = false) {
|
|
|
2507
2774
|
pending.push({ path: plan.path, content: plan.content, status: plan.status, chmodExec });
|
|
2508
2775
|
}
|
|
2509
2776
|
function planBootstrapFile(input) {
|
|
2510
|
-
const destPath =
|
|
2511
|
-
if (
|
|
2777
|
+
const destPath = join9(input.cwd, input.destRelPath);
|
|
2778
|
+
if (existsSync11(destPath)) return { kind: "noop" };
|
|
2512
2779
|
const assetPath = resolve8(getCoreRoot(), "core-assets", input.assetRelPath);
|
|
2513
|
-
const raw =
|
|
2780
|
+
const raw = readFileSync11(assetPath, "utf-8");
|
|
2514
2781
|
return { kind: "write", path: destPath, content: interpolate(raw, input.config) };
|
|
2515
2782
|
}
|
|
2516
2783
|
function applyBootstrapPlan(plan, _cwd, pending) {
|
|
@@ -2518,20 +2785,20 @@ function applyBootstrapPlan(plan, _cwd, pending) {
|
|
|
2518
2785
|
pending.push({ path: plan.path, content: plan.content, status: "created" });
|
|
2519
2786
|
}
|
|
2520
2787
|
function applySubBlockInject(input) {
|
|
2521
|
-
const targetAbs =
|
|
2788
|
+
const targetAbs = join9(input.cwd, input.skill.injectInto);
|
|
2522
2789
|
let currentContent;
|
|
2523
2790
|
const pendingEntry = input.pending.find((p16) => p16.path === targetAbs);
|
|
2524
2791
|
if (pendingEntry) {
|
|
2525
2792
|
currentContent = pendingEntry.content;
|
|
2526
|
-
} else if (
|
|
2527
|
-
currentContent =
|
|
2793
|
+
} else if (existsSync11(targetAbs)) {
|
|
2794
|
+
currentContent = readFileSync11(targetAbs, "utf-8");
|
|
2528
2795
|
} else {
|
|
2529
2796
|
input.warnings.push(
|
|
2530
2797
|
`skill '${input.skill.id}' (de @navori/plugin-${input.plugin.manifest.id}) no inyectado: target ${input.skill.injectInto} ausente (\xBFagente disabled en config.harness?)`
|
|
2531
2798
|
);
|
|
2532
2799
|
return;
|
|
2533
2800
|
}
|
|
2534
|
-
const rawSkill =
|
|
2801
|
+
const rawSkill = readFileSync11(input.skill.absPath, "utf-8");
|
|
2535
2802
|
const skillBody = stripFrontmatter(rawSkill);
|
|
2536
2803
|
const interpolated = interpolate(skillBody, input.config);
|
|
2537
2804
|
const result = injectManagedSection(
|
|
@@ -2563,10 +2830,10 @@ function applySubBlockInject(input) {
|
|
|
2563
2830
|
});
|
|
2564
2831
|
}
|
|
2565
2832
|
function planPluginScript(cwd, script, config) {
|
|
2566
|
-
const destPath =
|
|
2567
|
-
const raw =
|
|
2833
|
+
const destPath = join9(cwd, ".claude/scripts", script.dest);
|
|
2834
|
+
const raw = readFileSync11(script.src, "utf-8");
|
|
2568
2835
|
const interpolated = interpolate(raw, config);
|
|
2569
|
-
const existing =
|
|
2836
|
+
const existing = existsSync11(destPath) ? readFileSync11(destPath, "utf-8") : null;
|
|
2570
2837
|
if (existing === interpolated) return { kind: "noop" };
|
|
2571
2838
|
return {
|
|
2572
2839
|
kind: "write",
|
|
@@ -2661,7 +2928,7 @@ function runRender(cwd, dryRunOrOptions = false, force = false) {
|
|
|
2661
2928
|
const workspaceFilter = opts.workspaceFilter ?? null;
|
|
2662
2929
|
const configPath = `${cwd}/navori.config.json`;
|
|
2663
2930
|
const claudeMdPath = `${cwd}/CLAUDE.md`;
|
|
2664
|
-
if (!
|
|
2931
|
+
if (!existsSync12(configPath)) {
|
|
2665
2932
|
return {
|
|
2666
2933
|
ok: false,
|
|
2667
2934
|
reason: `No navori.config.json at ${configPath}`,
|
|
@@ -2799,7 +3066,7 @@ var renderCommand = defineCommand({
|
|
|
2799
3066
|
benchStart();
|
|
2800
3067
|
const cwd = resolve9(args.cwd ?? process.cwd());
|
|
2801
3068
|
p.intro(brand("render"));
|
|
2802
|
-
if (!
|
|
3069
|
+
if (!existsSync12(cwd)) {
|
|
2803
3070
|
p.cancel(`Directory not found: ${cwd}`);
|
|
2804
3071
|
process.exit(1);
|
|
2805
3072
|
}
|
|
@@ -3005,6 +3272,7 @@ var ES = {
|
|
|
3005
3272
|
wroteConfig: (path) => `Escrib\xED ${path}`,
|
|
3006
3273
|
recPluginsEnabled: (list) => `Plugins recomendados activados: ${list}`,
|
|
3007
3274
|
presetGapNotice: (stack) => `Detect\xE9 un proyecto '${stack}', pero todav\xEDa no hay un preset oficial para ese stack. Se instala el harness completo (agentes, gates, protocolo, SDD) y funciona desde ya; lo \xFAnico que falta son skills espec\xEDficas de '${stack}'. Quedas con el baseline (preset: custom). Para cubrir el hueco: crea tu preset local con 'navori preset init ${stack}', o agrega skills sueltas en project.localSkills.`,
|
|
3275
|
+
placeholderNameNotice: (name) => `El name '${name}' parece un placeholder de scaffold (heredado del package.json sin renombrar). Ren\xF3mbralo en package.json o edita "name" en navori.config.json si no es el nombre real del repo.`,
|
|
3008
3276
|
workspaceDefaultsTitle: (name) => `Defaults del workspace \xB7 ${name}`,
|
|
3009
3277
|
detectedTitle: "Detectado en este repo",
|
|
3010
3278
|
filesFoundTitle: "Archivos encontrados",
|
|
@@ -3111,6 +3379,7 @@ var EN = {
|
|
|
3111
3379
|
wroteConfig: (path) => `Wrote ${path}`,
|
|
3112
3380
|
recPluginsEnabled: (list) => `Recommended plugins enabled: ${list}`,
|
|
3113
3381
|
presetGapNotice: (stack) => `Detected a '${stack}' project, but there's no official preset for that stack yet. The full harness installs (agents, gates, protocol, SDD) and works right away; the only thing missing are '${stack}'-specific skills. You stay on the baseline (preset: custom). To cover the gap: scaffold your local preset with 'navori preset init ${stack}', or add individual skills via project.localSkills.`,
|
|
3382
|
+
placeholderNameNotice: (name) => `The name '${name}' looks like a scaffold placeholder (carried over from an un-renamed package.json). Rename it in package.json or edit "name" in navori.config.json if it isn't the repo's real name.`,
|
|
3114
3383
|
workspaceDefaultsTitle: (name) => `Workspace defaults \xB7 ${name}`,
|
|
3115
3384
|
detectedTitle: "Detected from this repo",
|
|
3116
3385
|
filesFoundTitle: "Files found",
|
|
@@ -3209,7 +3478,7 @@ function formatWorkspaceSummary(ws, lang = "es") {
|
|
|
3209
3478
|
}
|
|
3210
3479
|
|
|
3211
3480
|
// src/engines/claude/prompts-loader.ts
|
|
3212
|
-
import { readFileSync as
|
|
3481
|
+
import { readFileSync as readFileSync12, existsSync as existsSync13 } from "fs";
|
|
3213
3482
|
import { resolve as resolve10 } from "path";
|
|
3214
3483
|
import { z as z7 } from "zod";
|
|
3215
3484
|
var SelectOptionSchema = z7.object({
|
|
@@ -3249,10 +3518,10 @@ function loadPrompts(enabledPlugins) {
|
|
|
3249
3518
|
}
|
|
3250
3519
|
function loadCorePrompts(warnings) {
|
|
3251
3520
|
const path = resolve10(getCoreRoot(), CORE_PROMPTS_REL);
|
|
3252
|
-
if (!
|
|
3521
|
+
if (!existsSync13(path)) return [];
|
|
3253
3522
|
let parsed;
|
|
3254
3523
|
try {
|
|
3255
|
-
parsed = JSON.parse(
|
|
3524
|
+
parsed = JSON.parse(readFileSync12(path, "utf-8"));
|
|
3256
3525
|
} catch (err) {
|
|
3257
3526
|
warnings.push(`core prompts.json no parsea: ${err.message}`);
|
|
3258
3527
|
return [];
|
|
@@ -3266,8 +3535,8 @@ function loadCorePrompts(warnings) {
|
|
|
3266
3535
|
}
|
|
3267
3536
|
|
|
3268
3537
|
// src/lib/scan.ts
|
|
3269
|
-
import { existsSync as
|
|
3270
|
-
import { join as
|
|
3538
|
+
import { existsSync as existsSync14, readdirSync as readdirSync6 } from "fs";
|
|
3539
|
+
import { join as join10 } from "path";
|
|
3271
3540
|
function scanMonorepoWorkspaces(cwd) {
|
|
3272
3541
|
const patterns = collectWorkspacePatterns(cwd);
|
|
3273
3542
|
if (patterns.length === 0) return [];
|
|
@@ -3283,86 +3552,6 @@ function scanMonorepoWorkspaces(cwd) {
|
|
|
3283
3552
|
}
|
|
3284
3553
|
return results.sort((a, b) => a.path.localeCompare(b.path));
|
|
3285
3554
|
}
|
|
3286
|
-
function collectWorkspacePatterns(cwd) {
|
|
3287
|
-
const pnpmYaml = join9(cwd, "pnpm-workspace.yaml");
|
|
3288
|
-
if (existsSync13(pnpmYaml)) {
|
|
3289
|
-
try {
|
|
3290
|
-
return parsePnpmWorkspaceYaml(readFileSync12(pnpmYaml, "utf-8"));
|
|
3291
|
-
} catch {
|
|
3292
|
-
return [];
|
|
3293
|
-
}
|
|
3294
|
-
}
|
|
3295
|
-
const pkgPath = join9(cwd, "package.json");
|
|
3296
|
-
if (existsSync13(pkgPath)) {
|
|
3297
|
-
try {
|
|
3298
|
-
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8").replace(/^/, ""));
|
|
3299
|
-
return readPackageJsonWorkspaces(pkg.workspaces);
|
|
3300
|
-
} catch {
|
|
3301
|
-
return [];
|
|
3302
|
-
}
|
|
3303
|
-
}
|
|
3304
|
-
return [];
|
|
3305
|
-
}
|
|
3306
|
-
function parsePnpmWorkspaceYaml(content) {
|
|
3307
|
-
const lines = content.split(/\r?\n/);
|
|
3308
|
-
let i = 0;
|
|
3309
|
-
while (i < lines.length) {
|
|
3310
|
-
const line = lines[i];
|
|
3311
|
-
const m = line.match(/^packages\s*:\s*(.*)$/);
|
|
3312
|
-
if (m) {
|
|
3313
|
-
const inline = m[1].trim();
|
|
3314
|
-
if (inline.startsWith("[")) {
|
|
3315
|
-
return parseInlineYamlArray(inline);
|
|
3316
|
-
}
|
|
3317
|
-
const items = [];
|
|
3318
|
-
i++;
|
|
3319
|
-
while (i < lines.length) {
|
|
3320
|
-
const cur = lines[i];
|
|
3321
|
-
if (/^\s*#/.test(cur) || /^\s*$/.test(cur)) {
|
|
3322
|
-
i++;
|
|
3323
|
-
continue;
|
|
3324
|
-
}
|
|
3325
|
-
const itemMatch = cur.match(/^\s+-\s+(.+?)\s*(?:#.*)?$/);
|
|
3326
|
-
if (!itemMatch) break;
|
|
3327
|
-
const raw = itemMatch[1].trim();
|
|
3328
|
-
const unquoted = stripYamlQuotes(raw);
|
|
3329
|
-
if (unquoted && !unquoted.startsWith("!")) items.push(unquoted);
|
|
3330
|
-
i++;
|
|
3331
|
-
}
|
|
3332
|
-
return items;
|
|
3333
|
-
}
|
|
3334
|
-
i++;
|
|
3335
|
-
}
|
|
3336
|
-
return [];
|
|
3337
|
-
}
|
|
3338
|
-
function parseInlineYamlArray(inline) {
|
|
3339
|
-
const closed = inline.lastIndexOf("]");
|
|
3340
|
-
if (closed < 0) return [];
|
|
3341
|
-
const body = inline.slice(1, closed);
|
|
3342
|
-
return body.split(",").map((s) => stripYamlQuotes(s.trim())).filter((s) => s && !s.startsWith("!"));
|
|
3343
|
-
}
|
|
3344
|
-
function stripYamlQuotes(raw) {
|
|
3345
|
-
if (raw.length >= 2) {
|
|
3346
|
-
const first = raw[0];
|
|
3347
|
-
const last = raw[raw.length - 1];
|
|
3348
|
-
if (first === '"' && last === '"' || first === "'" && last === "'") {
|
|
3349
|
-
return raw.slice(1, -1);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
return raw;
|
|
3353
|
-
}
|
|
3354
|
-
function readPackageJsonWorkspaces(raw) {
|
|
3355
|
-
if (Array.isArray(raw)) {
|
|
3356
|
-
return raw.filter((s) => typeof s === "string" && !s.startsWith("!"));
|
|
3357
|
-
}
|
|
3358
|
-
if (raw && typeof raw === "object" && "packages" in raw) {
|
|
3359
|
-
const pkgs = raw.packages;
|
|
3360
|
-
if (Array.isArray(pkgs)) {
|
|
3361
|
-
return pkgs.filter((s) => typeof s === "string" && !s.startsWith("!"));
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
return [];
|
|
3365
|
-
}
|
|
3366
3555
|
function expandPattern(cwd, pattern) {
|
|
3367
3556
|
const segments = pattern.split("/").filter(Boolean);
|
|
3368
3557
|
if (segments.length === 0) return [];
|
|
@@ -3374,9 +3563,9 @@ function walk(cwd, accum, remaining) {
|
|
|
3374
3563
|
}
|
|
3375
3564
|
const [head, ...tail] = remaining;
|
|
3376
3565
|
const currentRel = accum.join("/");
|
|
3377
|
-
const currentAbs = currentRel ?
|
|
3566
|
+
const currentAbs = currentRel ? join10(cwd, currentRel) : cwd;
|
|
3378
3567
|
if (head === "*") {
|
|
3379
|
-
if (!
|
|
3568
|
+
if (!existsSync14(currentAbs)) return [];
|
|
3380
3569
|
let entries;
|
|
3381
3570
|
try {
|
|
3382
3571
|
entries = readdirSync6(currentAbs, { withFileTypes: true });
|
|
@@ -3385,8 +3574,8 @@ function walk(cwd, accum, remaining) {
|
|
|
3385
3574
|
}
|
|
3386
3575
|
return entries.filter((d) => d.isDirectory() && !d.name.startsWith(".")).flatMap((d) => walk(cwd, [...accum, d.name], tail));
|
|
3387
3576
|
}
|
|
3388
|
-
const next =
|
|
3389
|
-
if (!
|
|
3577
|
+
const next = join10(currentAbs, head);
|
|
3578
|
+
if (!existsSync14(next)) return [];
|
|
3390
3579
|
return walk(cwd, [...accum, head], tail);
|
|
3391
3580
|
}
|
|
3392
3581
|
function diffWorkspaces(detected, configured) {
|
|
@@ -3398,8 +3587,8 @@ function diffWorkspaces(detected, configured) {
|
|
|
3398
3587
|
return { added, existing, orphan };
|
|
3399
3588
|
}
|
|
3400
3589
|
function describeWorkspace(cwd, relPath) {
|
|
3401
|
-
const abs =
|
|
3402
|
-
if (!
|
|
3590
|
+
const abs = join10(cwd, relPath);
|
|
3591
|
+
if (!existsSync14(join10(abs, "package.json"))) return null;
|
|
3403
3592
|
const project = detectProject(abs);
|
|
3404
3593
|
return {
|
|
3405
3594
|
name: project.name ?? relPath.split("/").pop(),
|
|
@@ -3480,11 +3669,11 @@ var initCommand = defineCommand2({
|
|
|
3480
3669
|
p2.intro(brand("init"));
|
|
3481
3670
|
const bootstrapLang = normalizeLang(args.lang) ?? "es";
|
|
3482
3671
|
const bootstrapT = t(bootstrapLang);
|
|
3483
|
-
if (!
|
|
3672
|
+
if (!existsSync15(cwd)) {
|
|
3484
3673
|
p2.cancel(bootstrapT.dirNotFound(cwd));
|
|
3485
3674
|
process.exit(1);
|
|
3486
3675
|
}
|
|
3487
|
-
if (
|
|
3676
|
+
if (existsSync15(configPath)) {
|
|
3488
3677
|
p2.cancel(bootstrapT.configExists(configPath));
|
|
3489
3678
|
process.exit(1);
|
|
3490
3679
|
}
|
|
@@ -3539,9 +3728,13 @@ var initCommand = defineCommand2({
|
|
|
3539
3728
|
if (detected.suggestedPresetGap) {
|
|
3540
3729
|
p2.log.warn(tr.presetGapNotice(detected.suggestedPresetGap));
|
|
3541
3730
|
}
|
|
3731
|
+
if (detected.name && isPlaceholderName(detected.name)) {
|
|
3732
|
+
p2.log.warn(tr.placeholderNameNotice(detected.name));
|
|
3733
|
+
}
|
|
3542
3734
|
const wsDefaults = workspaceConfig?.defaults;
|
|
3543
3735
|
const defaultEngines = wsDefaults?.engines ?? (detected.existingEngines.length > 0 ? detected.existingEngines : ["claude"]);
|
|
3544
3736
|
const defaultBranchBase = wsDefaults?.branchBase ?? detected.branchBase ?? "main";
|
|
3737
|
+
const defaultPrTarget = wsDefaults?.prTarget;
|
|
3545
3738
|
const defaultLanguage = wsDefaults?.language ?? lang;
|
|
3546
3739
|
const defaultCommits = wsDefaults?.commits;
|
|
3547
3740
|
if (autoYes) {
|
|
@@ -3562,7 +3755,11 @@ var initCommand = defineCommand2({
|
|
|
3562
3755
|
rootPreset: detected.suggestedPreset
|
|
3563
3756
|
});
|
|
3564
3757
|
const fallbackQg = args.recommended ? detected.qualityGate ?? buildRecommendedQualityGate(detected) : detected.qualityGate;
|
|
3565
|
-
const projectBlock =
|
|
3758
|
+
const projectBlock = {
|
|
3759
|
+
...args.recommended ? buildRecommendedProject(detected) : {},
|
|
3760
|
+
libraries: detected.libraries,
|
|
3761
|
+
codeLanguage: detected.stack.language
|
|
3762
|
+
};
|
|
3566
3763
|
writeConfig(configPath, {
|
|
3567
3764
|
name: detected.name,
|
|
3568
3765
|
...args.workspace ? { workspace: args.workspace } : {},
|
|
@@ -3570,6 +3767,7 @@ var initCommand = defineCommand2({
|
|
|
3570
3767
|
preset: detected.suggestedPreset,
|
|
3571
3768
|
language: defaultLanguage,
|
|
3572
3769
|
branchBase: defaultBranchBase,
|
|
3770
|
+
...defaultPrTarget ? { prTarget: defaultPrTarget } : {},
|
|
3573
3771
|
...defaultCommits ? { commits: defaultCommits } : {},
|
|
3574
3772
|
...fallbackQg ? { qualityGate: fallbackQg } : {},
|
|
3575
3773
|
...Object.keys(mergedPlugins2).length > 0 ? { plugins: mergedPlugins2 } : {},
|
|
@@ -3877,13 +4075,19 @@ var initCommand = defineCommand2({
|
|
|
3877
4075
|
preset,
|
|
3878
4076
|
language,
|
|
3879
4077
|
branchBase,
|
|
4078
|
+
...defaultPrTarget ? { prTarget: defaultPrTarget } : {},
|
|
3880
4079
|
...defaultCommits ? { commits: defaultCommits } : {},
|
|
3881
4080
|
...qualityGate ? { qualityGate } : {},
|
|
3882
4081
|
...Object.keys(mergedPlugins).length > 0 ? { plugins: mergedPlugins } : {},
|
|
3883
4082
|
...Object.keys(agentAssignments).length > 0 ? { agentAssignments } : {},
|
|
3884
4083
|
// Always write `project` so the schema fills empty arrays and render emits
|
|
3885
4084
|
// no `<not configured: project.*>` placeholders (see autoYes path above).
|
|
3886
|
-
|
|
4085
|
+
// Library skills and codeLanguage are auto-derived from the stack.
|
|
4086
|
+
project: {
|
|
4087
|
+
...project ?? {},
|
|
4088
|
+
libraries: detected.libraries,
|
|
4089
|
+
codeLanguage: detected.stack.language
|
|
4090
|
+
},
|
|
3887
4091
|
...monorepoBlock ? { monorepo: monorepoBlock } : {}
|
|
3888
4092
|
});
|
|
3889
4093
|
p2.log.success(tr.wroteConfig(configPath));
|
|
@@ -4083,7 +4287,7 @@ ${lines}${more}`);
|
|
|
4083
4287
|
}
|
|
4084
4288
|
}
|
|
4085
4289
|
async function offerPreCommitHook(cwd, opts) {
|
|
4086
|
-
if (!
|
|
4290
|
+
if (!existsSync15(join11(cwd, ".git"))) return;
|
|
4087
4291
|
let wanted = opts.force;
|
|
4088
4292
|
if (!wanted && !opts.autoYes) {
|
|
4089
4293
|
const answer = await p2.confirm({
|
|
@@ -4102,10 +4306,10 @@ async function offerPreCommitHook(cwd, opts) {
|
|
|
4102
4306
|
}
|
|
4103
4307
|
}
|
|
4104
4308
|
function writePreCommitHook(cwd) {
|
|
4105
|
-
const huskyDir =
|
|
4106
|
-
const hookPath =
|
|
4309
|
+
const huskyDir = join11(cwd, ".husky");
|
|
4310
|
+
const hookPath = existsSync15(huskyDir) ? join11(huskyDir, "pre-commit") : join11(cwd, ".git", "hooks", "pre-commit");
|
|
4107
4311
|
const relPath = relative3(cwd, hookPath);
|
|
4108
|
-
if (
|
|
4312
|
+
if (existsSync15(hookPath)) return { ok: false, path: relPath };
|
|
4109
4313
|
const body = [
|
|
4110
4314
|
"#!/usr/bin/env sh",
|
|
4111
4315
|
"# navori pre-commit drift gate \u2014 scaffolded by 'navori init' (opt-in).",
|
|
@@ -4342,14 +4546,14 @@ function formatProjectValue(v) {
|
|
|
4342
4546
|
// src/commands/doctor.ts
|
|
4343
4547
|
import { defineCommand as defineCommand3 } from "citty";
|
|
4344
4548
|
import * as p3 from "@clack/prompts";
|
|
4345
|
-
import { existsSync as
|
|
4346
|
-
import { join as
|
|
4549
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14, readdirSync as readdirSync8 } from "fs";
|
|
4550
|
+
import { join as join13, resolve as resolve12, relative as relative4 } from "path";
|
|
4347
4551
|
|
|
4348
4552
|
// src/lib/health.ts
|
|
4349
|
-
import { existsSync as
|
|
4350
|
-
import { join as
|
|
4553
|
+
import { existsSync as existsSync16, readFileSync as readFileSync13, readdirSync as readdirSync7 } from "fs";
|
|
4554
|
+
import { join as join12 } from "path";
|
|
4351
4555
|
function listMarkers(filePath) {
|
|
4352
|
-
if (!
|
|
4556
|
+
if (!existsSync16(filePath)) return [];
|
|
4353
4557
|
const content = readFileSync13(filePath, "utf-8");
|
|
4354
4558
|
const re = /<!-- navori:managed [^>]*-->/g;
|
|
4355
4559
|
const result = [];
|
|
@@ -4395,10 +4599,10 @@ function scanManagedDrift(cwd, config) {
|
|
|
4395
4599
|
}
|
|
4396
4600
|
}
|
|
4397
4601
|
const files = [];
|
|
4398
|
-
if (
|
|
4602
|
+
if (existsSync16(join12(cwd, "CLAUDE.md"))) files.push("CLAUDE.md");
|
|
4399
4603
|
for (const dir of [".claude/agents", ".claude/skills"]) {
|
|
4400
|
-
const absDir =
|
|
4401
|
-
if (!
|
|
4604
|
+
const absDir = join12(cwd, dir);
|
|
4605
|
+
if (!existsSync16(absDir)) continue;
|
|
4402
4606
|
try {
|
|
4403
4607
|
for (const file of readdirSync7(absDir)) {
|
|
4404
4608
|
if (file.endsWith(".md")) files.push(`${dir}/${file}`);
|
|
@@ -4408,7 +4612,7 @@ function scanManagedDrift(cwd, config) {
|
|
|
4408
4612
|
}
|
|
4409
4613
|
}
|
|
4410
4614
|
for (const rel of files) {
|
|
4411
|
-
const abs =
|
|
4615
|
+
const abs = join12(cwd, rel);
|
|
4412
4616
|
const fileContent = (() => {
|
|
4413
4617
|
try {
|
|
4414
4618
|
return readFileSync13(abs, "utf-8");
|
|
@@ -4493,7 +4697,7 @@ var doctorCommand = defineCommand3({
|
|
|
4493
4697
|
const configPath = `${cwd}/navori.config.json`;
|
|
4494
4698
|
const claudeMdPath = `${cwd}/CLAUDE.md`;
|
|
4495
4699
|
if (!args.json) p3.intro(brand("doctor"));
|
|
4496
|
-
if (!
|
|
4700
|
+
if (!existsSync17(cwd)) {
|
|
4497
4701
|
if (args.json) {
|
|
4498
4702
|
console.log(JSON.stringify({ ok: false, error: "directory-missing", cwd }));
|
|
4499
4703
|
} else {
|
|
@@ -4501,7 +4705,7 @@ var doctorCommand = defineCommand3({
|
|
|
4501
4705
|
}
|
|
4502
4706
|
process.exit(1);
|
|
4503
4707
|
}
|
|
4504
|
-
if (!
|
|
4708
|
+
if (!existsSync17(configPath)) {
|
|
4505
4709
|
if (args.json) {
|
|
4506
4710
|
console.log(JSON.stringify({ ok: false, error: "config-missing", configPath }));
|
|
4507
4711
|
} else {
|
|
@@ -4537,6 +4741,7 @@ var doctorCommand = defineCommand3({
|
|
|
4537
4741
|
const missingPreset = config.preset !== "custom" && resolvedPreset === null ? config.preset : null;
|
|
4538
4742
|
const presetOverride = resolvedPreset?.source === "local" && presetExists(config.preset) ? config.preset : null;
|
|
4539
4743
|
const missingPresetFiles = scanMissingPresetFiles(cwd, config);
|
|
4744
|
+
const placeholderName = isPlaceholderName(config.name) ? config.name : null;
|
|
4540
4745
|
const report = {
|
|
4541
4746
|
// Drift is informational ("update available"), not an error — don't
|
|
4542
4747
|
// flip `ok` for it. Missing plugins, corrupted settings.json, missing
|
|
@@ -4546,10 +4751,10 @@ var doctorCommand = defineCommand3({
|
|
|
4546
4751
|
configPath,
|
|
4547
4752
|
config,
|
|
4548
4753
|
checks: {
|
|
4549
|
-
claudeMdExists:
|
|
4550
|
-
agentsMdExists:
|
|
4551
|
-
claudeDirExists:
|
|
4552
|
-
progressDirExists:
|
|
4754
|
+
claudeMdExists: existsSync17(claudeMdPath),
|
|
4755
|
+
agentsMdExists: existsSync17(`${cwd}/AGENTS.md`),
|
|
4756
|
+
claudeDirExists: existsSync17(`${cwd}/.claude`),
|
|
4757
|
+
progressDirExists: existsSync17(`${cwd}/${config.progress?.dir ?? "progress"}`)
|
|
4553
4758
|
},
|
|
4554
4759
|
managedBlocks: markers,
|
|
4555
4760
|
missingPlugins,
|
|
@@ -4558,7 +4763,8 @@ var doctorCommand = defineCommand3({
|
|
|
4558
4763
|
missingInvariants,
|
|
4559
4764
|
missingPreset,
|
|
4560
4765
|
presetOverride,
|
|
4561
|
-
missingPresetFiles
|
|
4766
|
+
missingPresetFiles,
|
|
4767
|
+
placeholderName
|
|
4562
4768
|
};
|
|
4563
4769
|
if (args.json) {
|
|
4564
4770
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -4621,6 +4827,11 @@ ${lines.join("\n")}`);
|
|
|
4621
4827
|
`Preset local '${presetOverride}' (.navori/presets/${presetOverride}/) sombrea el preset oficial del mismo nombre \u2014 se usa el local. Ren\xF3mbralo si el override no es intencional.`
|
|
4622
4828
|
);
|
|
4623
4829
|
}
|
|
4830
|
+
if (placeholderName) {
|
|
4831
|
+
p3.log.warn(
|
|
4832
|
+
`El name '${placeholderName}' parece un placeholder de scaffold (probablemente heredado del package.json sin renombrar). Edita "name" en navori.config.json si no es el nombre real del repo.`
|
|
4833
|
+
);
|
|
4834
|
+
}
|
|
4624
4835
|
if (missingPresetFiles.length > 0) {
|
|
4625
4836
|
const lines = missingPresetFiles.map(
|
|
4626
4837
|
(f) => ` ${color.red(sym.fail)} ${accent(f.id)} ${dim(`\u2014 falta ${f.path}`)}`
|
|
@@ -4631,7 +4842,7 @@ ${lines.join("\n")}`
|
|
|
4631
4842
|
);
|
|
4632
4843
|
}
|
|
4633
4844
|
const missingLocalSkills = (config.project?.localSkills ?? []).filter(
|
|
4634
|
-
(name) => !
|
|
4845
|
+
(name) => !existsSync17(join13(cwd, ".claude/skills", `${name}.md`))
|
|
4635
4846
|
);
|
|
4636
4847
|
if (missingLocalSkills.length > 0) {
|
|
4637
4848
|
const lines = missingLocalSkills.map(
|
|
@@ -4690,8 +4901,8 @@ ${lines.join("\n")}`
|
|
|
4690
4901
|
}
|
|
4691
4902
|
});
|
|
4692
4903
|
function scanCorruptedSettings(cwd) {
|
|
4693
|
-
const path =
|
|
4694
|
-
if (!
|
|
4904
|
+
const path = join13(cwd, ".claude/settings.json");
|
|
4905
|
+
if (!existsSync17(path)) return [];
|
|
4695
4906
|
try {
|
|
4696
4907
|
JSON.parse(readFileSync14(path, "utf-8"));
|
|
4697
4908
|
return [];
|
|
@@ -4713,7 +4924,7 @@ function scanMissingPresetFiles(cwd, config) {
|
|
|
4713
4924
|
const missing = [];
|
|
4714
4925
|
for (const e of [...managed, ...agents, ...skills, ...hooks]) {
|
|
4715
4926
|
const abs = resolve12(loaded.assetRoot, e.relPath);
|
|
4716
|
-
if (!
|
|
4927
|
+
if (!existsSync17(abs)) missing.push({ id: e.id, path: relative4(cwd, abs) });
|
|
4717
4928
|
}
|
|
4718
4929
|
return missing;
|
|
4719
4930
|
}
|
|
@@ -4749,15 +4960,15 @@ function scanMissingInvariants(cwd, config) {
|
|
|
4749
4960
|
}
|
|
4750
4961
|
function readRenderedText(cwd) {
|
|
4751
4962
|
const parts = [];
|
|
4752
|
-
const claudeMd =
|
|
4753
|
-
if (
|
|
4963
|
+
const claudeMd = join13(cwd, "CLAUDE.md");
|
|
4964
|
+
if (existsSync17(claudeMd)) {
|
|
4754
4965
|
try {
|
|
4755
4966
|
parts.push(readFileSync14(claudeMd, "utf-8"));
|
|
4756
4967
|
} catch {
|
|
4757
4968
|
}
|
|
4758
4969
|
}
|
|
4759
|
-
const claudeDir =
|
|
4760
|
-
if (
|
|
4970
|
+
const claudeDir = join13(cwd, ".claude");
|
|
4971
|
+
if (existsSync17(claudeDir)) collectText(claudeDir, parts);
|
|
4761
4972
|
return parts.join("\n");
|
|
4762
4973
|
}
|
|
4763
4974
|
function collectText(dir, parts) {
|
|
@@ -4768,7 +4979,7 @@ function collectText(dir, parts) {
|
|
|
4768
4979
|
return;
|
|
4769
4980
|
}
|
|
4770
4981
|
for (const entry of entries) {
|
|
4771
|
-
const abs =
|
|
4982
|
+
const abs = join13(dir, entry.name);
|
|
4772
4983
|
if (entry.isDirectory()) {
|
|
4773
4984
|
collectText(abs, parts);
|
|
4774
4985
|
} else if (entry.isFile() && TEXT_EXTENSIONS.some((e) => entry.name.endsWith(e))) {
|
|
@@ -4805,8 +5016,8 @@ function collectAssignments(config) {
|
|
|
4805
5016
|
// src/commands/sync.ts
|
|
4806
5017
|
import { defineCommand as defineCommand4 } from "citty";
|
|
4807
5018
|
import * as p4 from "@clack/prompts";
|
|
4808
|
-
import { existsSync as
|
|
4809
|
-
import { resolve as resolve13, join as
|
|
5019
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
5020
|
+
import { resolve as resolve13, join as join14 } from "path";
|
|
4810
5021
|
|
|
4811
5022
|
// src/lib/diff.ts
|
|
4812
5023
|
function formatLineDiff(current, proposed, _options = {}) {
|
|
@@ -4851,11 +5062,11 @@ var syncCommand = defineCommand4({
|
|
|
4851
5062
|
const cwd = resolve13(args.cwd ?? process.cwd());
|
|
4852
5063
|
const configPath = `${cwd}/navori.config.json`;
|
|
4853
5064
|
p4.intro(brand("sync"));
|
|
4854
|
-
if (!
|
|
5065
|
+
if (!existsSync18(cwd)) {
|
|
4855
5066
|
p4.cancel(`Directory not found: ${cwd}`);
|
|
4856
5067
|
process.exit(1);
|
|
4857
5068
|
}
|
|
4858
|
-
if (!
|
|
5069
|
+
if (!existsSync18(configPath)) {
|
|
4859
5070
|
p4.cancel(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
|
|
4860
5071
|
process.exit(1);
|
|
4861
5072
|
}
|
|
@@ -5010,8 +5221,8 @@ async function resolveConflictsInteractively(plans) {
|
|
|
5010
5221
|
(e) => e.status === "user-modified-skipped"
|
|
5011
5222
|
);
|
|
5012
5223
|
if (cmConflicts.length === 0) continue;
|
|
5013
|
-
const claudeMdPath =
|
|
5014
|
-
const existing =
|
|
5224
|
+
const claudeMdPath = join14(tp.target.cwd, "CLAUDE.md");
|
|
5225
|
+
const existing = existsSync18(claudeMdPath) ? readFileSync15(claudeMdPath, "utf-8") : "";
|
|
5015
5226
|
const skipIds = /* @__PURE__ */ new Set();
|
|
5016
5227
|
const forceIds = /* @__PURE__ */ new Set();
|
|
5017
5228
|
for (const e of cmConflicts) {
|
|
@@ -5103,13 +5314,13 @@ function summarize2(writtenCount, conflictCount) {
|
|
|
5103
5314
|
// src/commands/add.ts
|
|
5104
5315
|
import { defineCommand as defineCommand5 } from "citty";
|
|
5105
5316
|
import * as p5 from "@clack/prompts";
|
|
5106
|
-
import { existsSync as
|
|
5317
|
+
import { existsSync as existsSync20, readFileSync as readFileSync16 } from "fs";
|
|
5107
5318
|
import { resolve as resolve14 } from "path";
|
|
5108
5319
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
5109
5320
|
|
|
5110
5321
|
// src/lib/which.ts
|
|
5111
|
-
import { existsSync as
|
|
5112
|
-
import { join as
|
|
5322
|
+
import { existsSync as existsSync19, statSync as statSync6 } from "fs";
|
|
5323
|
+
import { join as join15 } from "path";
|
|
5113
5324
|
function hasBinary(name) {
|
|
5114
5325
|
const pathEnv = process.env.PATH ?? "";
|
|
5115
5326
|
const sep2 = process.platform === "win32" ? ";" : ":";
|
|
@@ -5117,8 +5328,8 @@ function hasBinary(name) {
|
|
|
5117
5328
|
const exts = process.platform === "win32" ? (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";") : [""];
|
|
5118
5329
|
for (const dir of dirs) {
|
|
5119
5330
|
for (const ext of exts) {
|
|
5120
|
-
const candidate =
|
|
5121
|
-
if (
|
|
5331
|
+
const candidate = join15(dir, name + ext);
|
|
5332
|
+
if (existsSync19(candidate)) {
|
|
5122
5333
|
try {
|
|
5123
5334
|
if (statSync6(candidate).isFile()) return true;
|
|
5124
5335
|
} catch {
|
|
@@ -5194,11 +5405,11 @@ var addCommand = defineCommand5({
|
|
|
5194
5405
|
} else {
|
|
5195
5406
|
p5.intro(brand(`add ${accent(args.plugin)}`));
|
|
5196
5407
|
}
|
|
5197
|
-
if (!
|
|
5408
|
+
if (!existsSync20(cwd)) {
|
|
5198
5409
|
p5.cancel(`Directory not found: ${cwd}`);
|
|
5199
5410
|
process.exit(1);
|
|
5200
5411
|
}
|
|
5201
|
-
if (!
|
|
5412
|
+
if (!existsSync20(configPath)) {
|
|
5202
5413
|
p5.cancel(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
|
|
5203
5414
|
process.exit(1);
|
|
5204
5415
|
}
|
|
@@ -5317,7 +5528,51 @@ function printSuggestions(cwd, configPath) {
|
|
|
5317
5528
|
// src/commands/workspace.ts
|
|
5318
5529
|
import { defineCommand as defineCommand6 } from "citty";
|
|
5319
5530
|
import * as p6 from "@clack/prompts";
|
|
5320
|
-
import { existsSync as
|
|
5531
|
+
import { existsSync as existsSync21 } from "fs";
|
|
5532
|
+
import { join as join16 } from "path";
|
|
5533
|
+
|
|
5534
|
+
// src/lib/workspace-defaults.ts
|
|
5535
|
+
var VALID_DEFAULT_KEYS = "branchBase, prTarget, commits, language, engines, plugins.<id>.enabled";
|
|
5536
|
+
function parseBool(value) {
|
|
5537
|
+
if (value === "true") return true;
|
|
5538
|
+
if (value === "false") return false;
|
|
5539
|
+
return null;
|
|
5540
|
+
}
|
|
5541
|
+
function applyDefault(current, key, rawValue) {
|
|
5542
|
+
const next = { ...current };
|
|
5543
|
+
const pluginMatch = key.match(/^plugins\.([a-z0-9][a-z0-9-]*)\.enabled$/);
|
|
5544
|
+
if (pluginMatch) {
|
|
5545
|
+
const id = pluginMatch[1];
|
|
5546
|
+
const enabled2 = parseBool(rawValue);
|
|
5547
|
+
if (enabled2 === null) {
|
|
5548
|
+
return { ok: false, error: `Value for '${key}' must be 'true' or 'false', got '${rawValue}'.` };
|
|
5549
|
+
}
|
|
5550
|
+
next.plugins = { ...current.plugins ?? {}, [id]: { enabled: enabled2 } };
|
|
5551
|
+
} else if (key === "engines") {
|
|
5552
|
+
next.engines = rawValue.split(",").map((s) => s.trim()).filter(Boolean);
|
|
5553
|
+
} else if (key === "branchBase" || key === "prTarget" || key === "commits" || key === "language") {
|
|
5554
|
+
next[key] = rawValue;
|
|
5555
|
+
} else {
|
|
5556
|
+
return { ok: false, error: `Unknown default key '${key}'. Valid keys: ${VALID_DEFAULT_KEYS}.` };
|
|
5557
|
+
}
|
|
5558
|
+
const parsed = WorkspaceDefaultsSchema.safeParse(next);
|
|
5559
|
+
if (!parsed.success) {
|
|
5560
|
+
const detail = parsed.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
5561
|
+
return { ok: false, error: `Invalid value for '${key}': ${detail}` };
|
|
5562
|
+
}
|
|
5563
|
+
return { ok: true, defaults: parsed.data };
|
|
5564
|
+
}
|
|
5565
|
+
|
|
5566
|
+
// src/commands/workspace.ts
|
|
5567
|
+
function readRepoConfigName(repoPath) {
|
|
5568
|
+
const cfgPath = join16(repoPath, "navori.config.json");
|
|
5569
|
+
if (!existsSync21(cfgPath)) return null;
|
|
5570
|
+
try {
|
|
5571
|
+
return readConfig(cfgPath).name;
|
|
5572
|
+
} catch {
|
|
5573
|
+
return null;
|
|
5574
|
+
}
|
|
5575
|
+
}
|
|
5321
5576
|
var initSubCommand = defineCommand6({
|
|
5322
5577
|
meta: {
|
|
5323
5578
|
name: "init",
|
|
@@ -5335,7 +5590,7 @@ var initSubCommand = defineCommand6({
|
|
|
5335
5590
|
process.exit(1);
|
|
5336
5591
|
}
|
|
5337
5592
|
const path = workspacePath(name);
|
|
5338
|
-
if (
|
|
5593
|
+
if (existsSync21(path)) {
|
|
5339
5594
|
console.error(`Workspace '${name}' already exists at ${path}`);
|
|
5340
5595
|
process.exit(1);
|
|
5341
5596
|
}
|
|
@@ -5449,13 +5704,42 @@ Or list known workspaces: navori workspace ls
|
|
|
5449
5704
|
rows.push(["repos", String(workspace.repos.length)]);
|
|
5450
5705
|
p6.log.message(kv(rows));
|
|
5451
5706
|
if (workspace.repos.length > 0) {
|
|
5707
|
+
const configNames = /* @__PURE__ */ new Map();
|
|
5708
|
+
for (const repo of workspace.repos) {
|
|
5709
|
+
configNames.set(repo.name, readRepoConfigName(repo.path));
|
|
5710
|
+
}
|
|
5452
5711
|
const repoLines = workspace.repos.map((repo) => {
|
|
5453
5712
|
const stack = repo.stack ? dim(` [${repo.stack}]`) : "";
|
|
5454
5713
|
const desc = repo.description ? dim(` \u2014 ${repo.description}`) : "";
|
|
5455
|
-
|
|
5714
|
+
const cfgName = configNames.get(repo.name);
|
|
5715
|
+
const cfgLabel = cfgName && cfgName !== repo.name ? dim(` (name: ${cfgName})`) : "";
|
|
5716
|
+
return ` ${color.cyan(sym.bullet)} ${accent(repo.name)}${stack}${cfgLabel} ${dim(repo.path)}${desc}`;
|
|
5456
5717
|
});
|
|
5457
5718
|
p6.log.message(`Repos:
|
|
5458
5719
|
${repoLines.join("\n")}`);
|
|
5720
|
+
const byConfigName = /* @__PURE__ */ new Map();
|
|
5721
|
+
const placeholders = [];
|
|
5722
|
+
for (const repo of workspace.repos) {
|
|
5723
|
+
const cfgName = configNames.get(repo.name);
|
|
5724
|
+
if (!cfgName) continue;
|
|
5725
|
+
byConfigName.set(cfgName, [...byConfigName.get(cfgName) ?? [], repo.name]);
|
|
5726
|
+
if (isPlaceholderName(cfgName)) placeholders.push({ repo: repo.name, name: cfgName });
|
|
5727
|
+
}
|
|
5728
|
+
const collisions = [...byConfigName.entries()].filter(([, repos]) => repos.length > 1);
|
|
5729
|
+
if (collisions.length > 0) {
|
|
5730
|
+
const lines = collisions.map(
|
|
5731
|
+
([name2, repos]) => ` ${color.red(sym.fail)} ${accent(name2)} ${dim("\u2190")} ${repos.join(", ")}`
|
|
5732
|
+
);
|
|
5733
|
+
p6.log.warn(
|
|
5734
|
+
`Colisi\xF3n de name entre repos (mismo config.name) \u2014 ren\xF3mbralos en su package.json / navori.config.json para que cada repo tenga identidad \xFAnica:
|
|
5735
|
+
${lines.join("\n")}`
|
|
5736
|
+
);
|
|
5737
|
+
}
|
|
5738
|
+
if (placeholders.length > 0) {
|
|
5739
|
+
const lines = placeholders.map((pl) => ` ${color.yellow("!")} ${accent(pl.repo)} ${dim("\u2192")} '${pl.name}'`);
|
|
5740
|
+
p6.log.warn(`Names placeholder (scaffold sin renombrar):
|
|
5741
|
+
${lines.join("\n")}`);
|
|
5742
|
+
}
|
|
5459
5743
|
}
|
|
5460
5744
|
p6.outro(dim("Done"));
|
|
5461
5745
|
}
|
|
@@ -5547,14 +5831,14 @@ var deleteSubCommand = defineCommand6({
|
|
|
5547
5831
|
return;
|
|
5548
5832
|
}
|
|
5549
5833
|
}
|
|
5550
|
-
const { renameSync: renameSync3, existsSync:
|
|
5834
|
+
const { renameSync: renameSync3, existsSync: existsSync31, mkdirSync: mkdirSync10 } = await import("fs");
|
|
5551
5835
|
const { join: joinPath } = await import("path");
|
|
5552
5836
|
const { homedir: homedir2 } = await import("os");
|
|
5553
5837
|
const trashRoot = joinPath(homedir2(), ".navori", ".trash");
|
|
5554
5838
|
mkdirSync10(trashRoot, { recursive: true });
|
|
5555
5839
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
5556
5840
|
const dest = joinPath(trashRoot, `${name}-${ts}`);
|
|
5557
|
-
if (
|
|
5841
|
+
if (existsSync31(dir)) renameSync3(dir, dest);
|
|
5558
5842
|
p6.outro(`Moved to ${dest}. Restore manually if needed.`);
|
|
5559
5843
|
}
|
|
5560
5844
|
});
|
|
@@ -5592,6 +5876,130 @@ var addRepoSubCommand = defineCommand6({
|
|
|
5592
5876
|
p6.outro(dim("Done"));
|
|
5593
5877
|
}
|
|
5594
5878
|
});
|
|
5879
|
+
var setDefaultSubCommand = defineCommand6({
|
|
5880
|
+
meta: {
|
|
5881
|
+
name: "set-default",
|
|
5882
|
+
description: "Set a default applied to every repo in a workspace"
|
|
5883
|
+
},
|
|
5884
|
+
args: {
|
|
5885
|
+
workspace: { type: "positional", description: "Workspace name", required: true },
|
|
5886
|
+
key: {
|
|
5887
|
+
type: "positional",
|
|
5888
|
+
description: `Default key (${VALID_DEFAULT_KEYS})`,
|
|
5889
|
+
required: true
|
|
5890
|
+
},
|
|
5891
|
+
value: {
|
|
5892
|
+
type: "positional",
|
|
5893
|
+
description: "Value (engines: comma-separated; plugins enabled: true|false)",
|
|
5894
|
+
required: true
|
|
5895
|
+
}
|
|
5896
|
+
},
|
|
5897
|
+
run({ args }) {
|
|
5898
|
+
const ws = loadWorkspace(args.workspace);
|
|
5899
|
+
if (!ws) {
|
|
5900
|
+
console.error(`Workspace '${args.workspace}' not found`);
|
|
5901
|
+
process.exit(1);
|
|
5902
|
+
}
|
|
5903
|
+
const result = applyDefault(ws.defaults, args.key, args.value);
|
|
5904
|
+
if (!result.ok || !result.defaults) {
|
|
5905
|
+
console.error(result.error ?? "Could not apply default");
|
|
5906
|
+
process.exit(1);
|
|
5907
|
+
}
|
|
5908
|
+
ws.defaults = result.defaults;
|
|
5909
|
+
const written = writeWorkspace(ws);
|
|
5910
|
+
p6.intro(brand(`workspace set-default ${accent(ws.name)}`));
|
|
5911
|
+
p6.log.success(`Set ${accent(args.key)} ${dim(`(${written})`)}`);
|
|
5912
|
+
p6.log.message(kv([["defaults", JSON.stringify(ws.defaults)]]));
|
|
5913
|
+
p6.outro(dim("Done"));
|
|
5914
|
+
}
|
|
5915
|
+
});
|
|
5916
|
+
var renderSubCommand = defineCommand6({
|
|
5917
|
+
meta: {
|
|
5918
|
+
name: "render",
|
|
5919
|
+
description: "Render every repo registered in a workspace"
|
|
5920
|
+
},
|
|
5921
|
+
args: {
|
|
5922
|
+
name: { type: "positional", description: "Workspace name", required: true },
|
|
5923
|
+
apply: {
|
|
5924
|
+
type: "boolean",
|
|
5925
|
+
description: "Write changes to disk. Without it, every repo is previewed (no files touched)."
|
|
5926
|
+
},
|
|
5927
|
+
force: {
|
|
5928
|
+
type: "boolean",
|
|
5929
|
+
description: "Regenerate settings.json even if corrupted or missing the $navori marker."
|
|
5930
|
+
}
|
|
5931
|
+
},
|
|
5932
|
+
run({ args }) {
|
|
5933
|
+
const ws = loadWorkspace(args.name);
|
|
5934
|
+
if (!ws) {
|
|
5935
|
+
console.error(`Workspace '${args.name}' not found`);
|
|
5936
|
+
process.exit(1);
|
|
5937
|
+
}
|
|
5938
|
+
const apply = Boolean(args.apply);
|
|
5939
|
+
const preview = !apply;
|
|
5940
|
+
const force = Boolean(args.force);
|
|
5941
|
+
p6.intro(brand(`workspace render ${accent(ws.name)}`));
|
|
5942
|
+
if (ws.repos.length === 0) {
|
|
5943
|
+
p6.log.info("No repos registered. Add one with 'navori workspace add-repo'.");
|
|
5944
|
+
p6.outro(dim("Done"));
|
|
5945
|
+
return;
|
|
5946
|
+
}
|
|
5947
|
+
const rows = [];
|
|
5948
|
+
for (const repo of ws.repos) {
|
|
5949
|
+
if (!existsSync21(repo.path)) {
|
|
5950
|
+
rows.push({ name: repo.name, status: "missing", detail: repo.path });
|
|
5951
|
+
continue;
|
|
5952
|
+
}
|
|
5953
|
+
try {
|
|
5954
|
+
const result = runRender(repo.path, { dryRun: preview, force });
|
|
5955
|
+
if (!result.ok) {
|
|
5956
|
+
rows.push({ name: repo.name, status: "error", detail: result.reason ?? "render failed" });
|
|
5957
|
+
continue;
|
|
5958
|
+
}
|
|
5959
|
+
const allEntries = result.entries.concat(...result.workspaces.map((w) => w.entries));
|
|
5960
|
+
const anyPending = result.written || result.workspaces.some((w) => w.written);
|
|
5961
|
+
const status = anyPending ? preview ? "would-write" : "written" : "up-to-date";
|
|
5962
|
+
rows.push({ name: repo.name, status, detail: summarizeEntries(allEntries) });
|
|
5963
|
+
} catch (err) {
|
|
5964
|
+
rows.push({ name: repo.name, status: "error", detail: err.message });
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
const marker = {
|
|
5968
|
+
written: color.green(sym.ok),
|
|
5969
|
+
"would-write": color.yellow(sym.bullet),
|
|
5970
|
+
"up-to-date": dim(sym.bullet),
|
|
5971
|
+
missing: color.red(sym.fail),
|
|
5972
|
+
error: color.red(sym.fail)
|
|
5973
|
+
};
|
|
5974
|
+
const lines = rows.map((r) => {
|
|
5975
|
+
const detail = r.detail ? dim(` ${r.detail}`) : "";
|
|
5976
|
+
return ` ${marker[r.status]} ${accent(r.name)} ${dim(r.status)}${detail}`;
|
|
5977
|
+
});
|
|
5978
|
+
p6.log.message(lines.join("\n"));
|
|
5979
|
+
const failed = rows.filter((r) => r.status === "error" || r.status === "missing").length;
|
|
5980
|
+
const pending = rows.filter((r) => r.status === "written" || r.status === "would-write").length;
|
|
5981
|
+
const ok = rows.length - failed;
|
|
5982
|
+
const summary = `${ok}/${rows.length} ok \xB7 ${pending} ${preview ? "would change" : "changed"} \xB7 ${failed} failed`;
|
|
5983
|
+
if (failed > 0) {
|
|
5984
|
+
p6.outro(`${color.yellow("Done with errors")} ${dim(summary)}`);
|
|
5985
|
+
process.exit(1);
|
|
5986
|
+
}
|
|
5987
|
+
p6.outro(`${preview ? color.yellow("Preview") : color.green("Done")} ${dim(summary)}`);
|
|
5988
|
+
}
|
|
5989
|
+
});
|
|
5990
|
+
function summarizeEntries(entries) {
|
|
5991
|
+
const counts = entries.reduce((acc, e) => {
|
|
5992
|
+
acc[e.status] = (acc[e.status] ?? 0) + 1;
|
|
5993
|
+
return acc;
|
|
5994
|
+
}, {});
|
|
5995
|
+
const parts = [];
|
|
5996
|
+
if (counts.created) parts.push(`${counts.created} created`);
|
|
5997
|
+
if (counts.updated) parts.push(`${counts.updated} updated`);
|
|
5998
|
+
if (counts["user-modified-skipped"]) parts.push(`${counts["user-modified-skipped"]} conflict`);
|
|
5999
|
+
if (counts["removed-condition-false"]) parts.push(`${counts["removed-condition-false"]} removed`);
|
|
6000
|
+
if (counts.unchanged) parts.push(`${counts.unchanged} unchanged`);
|
|
6001
|
+
return parts.join(", ");
|
|
6002
|
+
}
|
|
5595
6003
|
var workspaceCommand = defineCommand6({
|
|
5596
6004
|
meta: {
|
|
5597
6005
|
name: "workspace",
|
|
@@ -5602,6 +6010,8 @@ var workspaceCommand = defineCommand6({
|
|
|
5602
6010
|
ls: lsSubCommand,
|
|
5603
6011
|
show: showSubCommand,
|
|
5604
6012
|
"add-repo": addRepoSubCommand,
|
|
6013
|
+
"set-default": setDefaultSubCommand,
|
|
6014
|
+
render: renderSubCommand,
|
|
5605
6015
|
rename: renameSubCommand,
|
|
5606
6016
|
delete: deleteSubCommand
|
|
5607
6017
|
}
|
|
@@ -5613,8 +6023,8 @@ import * as p7 from "@clack/prompts";
|
|
|
5613
6023
|
import { readFileSync as readFileSync18 } from "fs";
|
|
5614
6024
|
|
|
5615
6025
|
// src/lib/tickets.ts
|
|
5616
|
-
import { existsSync as
|
|
5617
|
-
import { join as
|
|
6026
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17, readdirSync as readdirSync9, statSync as statSync7, mkdirSync as mkdirSync6, renameSync as renameSync2, rmSync as rmSync5 } from "fs";
|
|
6027
|
+
import { join as join17, resolve as resolve15 } from "path";
|
|
5618
6028
|
var TicketError = class extends NavoriError {
|
|
5619
6029
|
constructor(message) {
|
|
5620
6030
|
super("ticket-error", message);
|
|
@@ -5623,7 +6033,7 @@ var TicketError = class extends NavoriError {
|
|
|
5623
6033
|
function ticketsDir(workspaceName) {
|
|
5624
6034
|
const ws = loadWorkspace(workspaceName);
|
|
5625
6035
|
if (!ws) throw new TicketError(`Workspace '${workspaceName}' not found`);
|
|
5626
|
-
return
|
|
6036
|
+
return join17(workspaceDirectory(workspaceName), ws.ticketsDir);
|
|
5627
6037
|
}
|
|
5628
6038
|
function readTitle(path) {
|
|
5629
6039
|
try {
|
|
@@ -5640,13 +6050,13 @@ function readTitle(path) {
|
|
|
5640
6050
|
}
|
|
5641
6051
|
function listTickets(workspaceName) {
|
|
5642
6052
|
const dir = ticketsDir(workspaceName);
|
|
5643
|
-
if (!
|
|
6053
|
+
if (!existsSync22(dir)) return [];
|
|
5644
6054
|
const out = [];
|
|
5645
6055
|
const collect = (folder, state) => {
|
|
5646
|
-
if (!
|
|
6056
|
+
if (!existsSync22(folder)) return;
|
|
5647
6057
|
for (const entry of readdirSync9(folder)) {
|
|
5648
6058
|
if (!entry.endsWith(".md")) continue;
|
|
5649
|
-
const full =
|
|
6059
|
+
const full = join17(folder, entry);
|
|
5650
6060
|
try {
|
|
5651
6061
|
if (!statSync7(full).isFile()) continue;
|
|
5652
6062
|
} catch {
|
|
@@ -5661,7 +6071,7 @@ function listTickets(workspaceName) {
|
|
|
5661
6071
|
}
|
|
5662
6072
|
};
|
|
5663
6073
|
collect(dir, "active");
|
|
5664
|
-
collect(
|
|
6074
|
+
collect(join17(dir, "_archive"), "archive");
|
|
5665
6075
|
return out;
|
|
5666
6076
|
}
|
|
5667
6077
|
function findTicket(workspaceName, id) {
|
|
@@ -5696,9 +6106,9 @@ function archiveTicket(workspaceName, id) {
|
|
|
5696
6106
|
if (!summary) throw new TicketError(`Ticket '${id}' not found in workspace '${workspaceName}'`);
|
|
5697
6107
|
if (summary.state === "archive") return summary;
|
|
5698
6108
|
const dir = ticketsDir(workspaceName);
|
|
5699
|
-
const archiveDir =
|
|
6109
|
+
const archiveDir = join17(dir, "_archive");
|
|
5700
6110
|
mkdirSync6(archiveDir, { recursive: true });
|
|
5701
|
-
const dest =
|
|
6111
|
+
const dest = join17(archiveDir, `${id}.md`);
|
|
5702
6112
|
renameSync2(summary.path, dest);
|
|
5703
6113
|
return { id, path: dest, title: summary.title, state: "archive" };
|
|
5704
6114
|
}
|
|
@@ -5712,9 +6122,9 @@ function createTicket(workspaceName, id, title) {
|
|
|
5712
6122
|
throw new TicketError(`Invalid ticket id '${id}'. Use letters, digits, hyphens, underscores.`);
|
|
5713
6123
|
}
|
|
5714
6124
|
const dir = ticketsDir(workspaceName);
|
|
5715
|
-
if (!
|
|
5716
|
-
const path =
|
|
5717
|
-
if (
|
|
6125
|
+
if (!existsSync22(dir)) throw new TicketError(`Tickets directory does not exist: ${dir}`);
|
|
6126
|
+
const path = join17(dir, `${id}.md`);
|
|
6127
|
+
if (existsSync22(path)) throw new TicketError(`Ticket '${id}' already exists at ${path}`);
|
|
5718
6128
|
const finalTitle = title?.trim() || id;
|
|
5719
6129
|
writeFileAtomic(path, defaultTemplate(id, finalTitle));
|
|
5720
6130
|
return {
|
|
@@ -5729,8 +6139,8 @@ function findReferencingRepos(repoPaths, ticketId) {
|
|
|
5729
6139
|
const idPattern = new RegExp(`\\b${ticketId}\\b`);
|
|
5730
6140
|
for (const repoPath of repoPaths) {
|
|
5731
6141
|
const abs = resolve15(repoPath);
|
|
5732
|
-
const current =
|
|
5733
|
-
if (!
|
|
6142
|
+
const current = join17(abs, "progress", "current.md");
|
|
6143
|
+
if (!existsSync22(current)) continue;
|
|
5734
6144
|
try {
|
|
5735
6145
|
const content = readFileSync17(current, "utf-8");
|
|
5736
6146
|
const matches = [];
|
|
@@ -5965,7 +6375,7 @@ var ticketCommand = defineCommand7({
|
|
|
5965
6375
|
// src/commands/configure.ts
|
|
5966
6376
|
import { defineCommand as defineCommand8 } from "citty";
|
|
5967
6377
|
import * as p8 from "@clack/prompts";
|
|
5968
|
-
import { existsSync as
|
|
6378
|
+
import { existsSync as existsSync23, readFileSync as readFileSync19 } from "fs";
|
|
5969
6379
|
import { resolve as resolve16 } from "path";
|
|
5970
6380
|
var ENGINE_OPTIONS2 = [
|
|
5971
6381
|
{ value: "claude", label: "Claude Code (.claude/)" },
|
|
@@ -5979,9 +6389,9 @@ function fail(msg) {
|
|
|
5979
6389
|
process.exit(1);
|
|
5980
6390
|
}
|
|
5981
6391
|
function loadOrExit(cwd) {
|
|
5982
|
-
if (!
|
|
6392
|
+
if (!existsSync23(cwd)) fail(`Directory not found: ${cwd}`);
|
|
5983
6393
|
const configPath = resolve16(cwd, "navori.config.json");
|
|
5984
|
-
if (!
|
|
6394
|
+
if (!existsSync23(configPath)) fail(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
|
|
5985
6395
|
const config = readConfig(configPath);
|
|
5986
6396
|
const raw = JSON.parse(readFileSync19(configPath, "utf-8"));
|
|
5987
6397
|
return { config, path: configPath, raw };
|
|
@@ -6167,6 +6577,46 @@ var branchBaseSubCommand = defineCommand8({
|
|
|
6167
6577
|
p8.outro("Run 'navori render --apply' to update the gate scripts.");
|
|
6168
6578
|
}
|
|
6169
6579
|
});
|
|
6580
|
+
var prTargetSubCommand = defineCommand8({
|
|
6581
|
+
meta: {
|
|
6582
|
+
name: "pr-target",
|
|
6583
|
+
description: "Set the branch PRs target (gh pr create --base); defaults to branchBase"
|
|
6584
|
+
},
|
|
6585
|
+
args: {
|
|
6586
|
+
cwd: { type: "string", description: "Directory (default: cwd)" },
|
|
6587
|
+
value: { type: "positional", description: "Branch name (e.g. develop)", required: false }
|
|
6588
|
+
},
|
|
6589
|
+
async run({ args }) {
|
|
6590
|
+
const cwd = resolve16(args.cwd ?? process.cwd());
|
|
6591
|
+
const { config, path, raw } = loadOrExit(cwd);
|
|
6592
|
+
p8.intro(brand("configure pr-target"));
|
|
6593
|
+
let value = args.value?.trim();
|
|
6594
|
+
if (!value) {
|
|
6595
|
+
const fallback = config.prTarget ?? config.branchBase;
|
|
6596
|
+
const input = await p8.text({
|
|
6597
|
+
message: "Branch PRs open against (gh pr create --base)",
|
|
6598
|
+
placeholder: fallback,
|
|
6599
|
+
defaultValue: fallback
|
|
6600
|
+
});
|
|
6601
|
+
if (p8.isCancel(input)) {
|
|
6602
|
+
p8.cancel("Cancelled");
|
|
6603
|
+
return;
|
|
6604
|
+
}
|
|
6605
|
+
value = input.trim();
|
|
6606
|
+
}
|
|
6607
|
+
if (!value) {
|
|
6608
|
+
p8.cancel("Branch name cannot be empty");
|
|
6609
|
+
return;
|
|
6610
|
+
}
|
|
6611
|
+
raw.prTarget = value;
|
|
6612
|
+
persist(path, raw);
|
|
6613
|
+
p8.log.success(`prTarget \u2192 ${value}`);
|
|
6614
|
+
if (value === config.branchBase) {
|
|
6615
|
+
p8.log.message(dim(`Same as branchBase \u2014 PRs target ${value} as before.`));
|
|
6616
|
+
}
|
|
6617
|
+
p8.outro("Run 'navori render --apply' to update the PR skills.");
|
|
6618
|
+
}
|
|
6619
|
+
});
|
|
6170
6620
|
var enginesSubCommand = defineCommand8({
|
|
6171
6621
|
meta: {
|
|
6172
6622
|
name: "engines",
|
|
@@ -6247,6 +6697,7 @@ var configureCommand = defineCommand8({
|
|
|
6247
6697
|
plugins: pluginsSubCommand,
|
|
6248
6698
|
"quality-gate": qualityGateSubCommand,
|
|
6249
6699
|
"branch-base": branchBaseSubCommand,
|
|
6700
|
+
"pr-target": prTargetSubCommand,
|
|
6250
6701
|
language: languageSubCommand,
|
|
6251
6702
|
engines: enginesSubCommand,
|
|
6252
6703
|
workspace: workspaceSubCommand
|
|
@@ -6256,7 +6707,7 @@ var configureCommand = defineCommand8({
|
|
|
6256
6707
|
// src/commands/update.ts
|
|
6257
6708
|
import { defineCommand as defineCommand9 } from "citty";
|
|
6258
6709
|
import * as p9 from "@clack/prompts";
|
|
6259
|
-
import { existsSync as
|
|
6710
|
+
import { existsSync as existsSync24, readFileSync as readFileSync20 } from "fs";
|
|
6260
6711
|
import { resolve as resolve17 } from "path";
|
|
6261
6712
|
function diffConfig(current, detected) {
|
|
6262
6713
|
const out = [];
|
|
@@ -6317,18 +6768,18 @@ var updateCommand = defineCommand9({
|
|
|
6317
6768
|
const cwd = resolve17(args.cwd ?? process.cwd());
|
|
6318
6769
|
const configPath = `${cwd}/navori.config.json`;
|
|
6319
6770
|
p9.intro(brand("update"));
|
|
6320
|
-
if (!
|
|
6771
|
+
if (!existsSync24(cwd)) {
|
|
6321
6772
|
p9.cancel(`Directory not found: ${cwd}`);
|
|
6322
6773
|
process.exit(1);
|
|
6323
6774
|
}
|
|
6324
|
-
if (!
|
|
6775
|
+
if (!existsSync24(configPath)) {
|
|
6325
6776
|
p9.cancel(`No navori.config.json at ${configPath}. Run 'navori init' first.`);
|
|
6326
6777
|
process.exit(1);
|
|
6327
6778
|
}
|
|
6328
6779
|
const config = readConfig(configPath);
|
|
6329
6780
|
const detected = detectProject(cwd);
|
|
6330
6781
|
const diffs = diffConfig(config, detected);
|
|
6331
|
-
const claudeMd =
|
|
6782
|
+
const claudeMd = existsSync24(`${cwd}/CLAUDE.md`) ? readFileSync20(`${cwd}/CLAUDE.md`, "utf-8") : "";
|
|
6332
6783
|
const plan = computeRenderPlan(claudeMd, config, cwd);
|
|
6333
6784
|
if (diffs.length === 0 && !plan.changed && plan.updatesAvailable.length === 0) {
|
|
6334
6785
|
p9.outro("Up to date \u2014 nothing to update");
|
|
@@ -6381,7 +6832,7 @@ ${lines.join("\n")}`);
|
|
|
6381
6832
|
}
|
|
6382
6833
|
if (plan.changed || plan.updatesAvailable.length > 0 || diffs.length > 0) {
|
|
6383
6834
|
const freshConfig = readConfig(configPath);
|
|
6384
|
-
const claudeMdNow =
|
|
6835
|
+
const claudeMdNow = existsSync24(`${cwd}/CLAUDE.md`) ? readFileSync20(`${cwd}/CLAUDE.md`, "utf-8") : "";
|
|
6385
6836
|
const freshPlan = computeRenderPlan(claudeMdNow, freshConfig, cwd);
|
|
6386
6837
|
const fresheConflicts = freshPlan.entries.filter((e) => e.status === "user-modified-skipped");
|
|
6387
6838
|
if (fresheConflicts.length > 0 && !args.yes) {
|
|
@@ -6392,7 +6843,7 @@ ${lines.join("\n")}`);
|
|
|
6392
6843
|
return;
|
|
6393
6844
|
}
|
|
6394
6845
|
if (freshPlan.changed) {
|
|
6395
|
-
if (
|
|
6846
|
+
if (existsSync24(`${cwd}/CLAUDE.md`)) {
|
|
6396
6847
|
const handle = createBackup(cwd, ["CLAUDE.md"]);
|
|
6397
6848
|
purgeOldBackups();
|
|
6398
6849
|
p9.log.message(`Backup: ${handle.path}`);
|
|
@@ -6410,14 +6861,14 @@ ${lines.join("\n")}`);
|
|
|
6410
6861
|
// src/commands/backup.ts
|
|
6411
6862
|
import { defineCommand as defineCommand10 } from "citty";
|
|
6412
6863
|
import * as p10 from "@clack/prompts";
|
|
6413
|
-
import { existsSync as
|
|
6414
|
-
import { join as
|
|
6864
|
+
import { existsSync as existsSync25, readdirSync as readdirSync10, statSync as statSync8, copyFileSync as copyFileSync4, mkdirSync as mkdirSync7 } from "fs";
|
|
6865
|
+
import { join as join18, relative as relative5, resolve as resolve18, dirname as dirname7 } from "path";
|
|
6415
6866
|
function listBackups() {
|
|
6416
6867
|
const root = backupRoot();
|
|
6417
|
-
if (!
|
|
6868
|
+
if (!existsSync25(root)) return [];
|
|
6418
6869
|
const entries = [];
|
|
6419
6870
|
for (const name of readdirSync10(root)) {
|
|
6420
|
-
const full =
|
|
6871
|
+
const full = join18(root, name);
|
|
6421
6872
|
try {
|
|
6422
6873
|
const stat = statSync8(full);
|
|
6423
6874
|
if (!stat.isDirectory()) continue;
|
|
@@ -6432,7 +6883,7 @@ function listBackups() {
|
|
|
6432
6883
|
function collectFiles(root, dir) {
|
|
6433
6884
|
const out = [];
|
|
6434
6885
|
for (const entry of readdirSync10(dir)) {
|
|
6435
|
-
const full =
|
|
6886
|
+
const full = join18(dir, entry);
|
|
6436
6887
|
try {
|
|
6437
6888
|
const stat = statSync8(full);
|
|
6438
6889
|
if (stat.isDirectory()) {
|
|
@@ -6498,9 +6949,9 @@ var restoreSubCommand = defineCommand10({
|
|
|
6498
6949
|
async run({ args }) {
|
|
6499
6950
|
const ts = args.timestamp;
|
|
6500
6951
|
const cwd = resolve18(args.cwd ?? process.cwd());
|
|
6501
|
-
const backupDir =
|
|
6952
|
+
const backupDir = join18(backupRoot(), ts);
|
|
6502
6953
|
p10.intro(brand(`backup restore ${accent(ts)}`));
|
|
6503
|
-
if (!
|
|
6954
|
+
if (!existsSync25(backupDir)) {
|
|
6504
6955
|
p10.cancel(`Backup not found: ${backupDir}`);
|
|
6505
6956
|
process.exit(1);
|
|
6506
6957
|
}
|
|
@@ -6522,8 +6973,8 @@ var restoreSubCommand = defineCommand10({
|
|
|
6522
6973
|
}
|
|
6523
6974
|
}
|
|
6524
6975
|
for (const rel of files) {
|
|
6525
|
-
const src =
|
|
6526
|
-
const dest =
|
|
6976
|
+
const src = join18(backupDir, rel);
|
|
6977
|
+
const dest = join18(cwd, rel);
|
|
6527
6978
|
mkdirSync7(dirname7(dest), { recursive: true });
|
|
6528
6979
|
copyFileSync4(src, dest);
|
|
6529
6980
|
}
|
|
@@ -6554,19 +7005,19 @@ var backupCommand = defineCommand10({
|
|
|
6554
7005
|
// src/commands/migrations.ts
|
|
6555
7006
|
import { defineCommand as defineCommand11 } from "citty";
|
|
6556
7007
|
import * as p11 from "@clack/prompts";
|
|
6557
|
-
import { existsSync as
|
|
6558
|
-
import { join as
|
|
7008
|
+
import { existsSync as existsSync26, readdirSync as readdirSync11, statSync as statSync9, copyFileSync as copyFileSync5, mkdirSync as mkdirSync8 } from "fs";
|
|
7009
|
+
import { join as join19, relative as relative6, resolve as resolve19, dirname as dirname8 } from "path";
|
|
6559
7010
|
function listMigrations() {
|
|
6560
7011
|
const root = migrationsRoot();
|
|
6561
|
-
if (!
|
|
7012
|
+
if (!existsSync26(root)) return [];
|
|
6562
7013
|
const entries = [];
|
|
6563
7014
|
for (const ts of readdirSync11(root)) {
|
|
6564
|
-
const tsDir =
|
|
7015
|
+
const tsDir = join19(root, ts);
|
|
6565
7016
|
try {
|
|
6566
7017
|
const stat = statSync9(tsDir);
|
|
6567
7018
|
if (!stat.isDirectory()) continue;
|
|
6568
7019
|
for (const repoName of readdirSync11(tsDir)) {
|
|
6569
|
-
const repoDir =
|
|
7020
|
+
const repoDir = join19(tsDir, repoName);
|
|
6570
7021
|
try {
|
|
6571
7022
|
const repoStat = statSync9(repoDir);
|
|
6572
7023
|
if (!repoStat.isDirectory()) continue;
|
|
@@ -6584,7 +7035,7 @@ function listMigrations() {
|
|
|
6584
7035
|
function collectFiles2(root, dir) {
|
|
6585
7036
|
const out = [];
|
|
6586
7037
|
for (const entry of readdirSync11(dir)) {
|
|
6587
|
-
const full =
|
|
7038
|
+
const full = join19(dir, entry);
|
|
6588
7039
|
try {
|
|
6589
7040
|
const stat = statSync9(full);
|
|
6590
7041
|
if (stat.isDirectory()) {
|
|
@@ -6653,9 +7104,9 @@ var restoreSubCommand2 = defineCommand11({
|
|
|
6653
7104
|
const ts = args.timestamp;
|
|
6654
7105
|
const repoName = args.repo;
|
|
6655
7106
|
const cwd = resolve19(args.cwd ?? process.cwd());
|
|
6656
|
-
const migrationDir =
|
|
7107
|
+
const migrationDir = join19(migrationsRoot(), ts, repoName);
|
|
6657
7108
|
p11.intro(brand(`migrations restore ${accent(`${ts}/${repoName}`)}`));
|
|
6658
|
-
if (!
|
|
7109
|
+
if (!existsSync26(migrationDir)) {
|
|
6659
7110
|
p11.cancel(`Migration not found: ${migrationDir}`);
|
|
6660
7111
|
process.exit(1);
|
|
6661
7112
|
}
|
|
@@ -6678,8 +7129,8 @@ var restoreSubCommand2 = defineCommand11({
|
|
|
6678
7129
|
}
|
|
6679
7130
|
}
|
|
6680
7131
|
for (const rel of files) {
|
|
6681
|
-
const src =
|
|
6682
|
-
const dest =
|
|
7132
|
+
const src = join19(migrationDir, rel);
|
|
7133
|
+
const dest = join19(cwd, rel);
|
|
6683
7134
|
mkdirSync8(dirname8(dest), { recursive: true });
|
|
6684
7135
|
copyFileSync5(src, dest);
|
|
6685
7136
|
}
|
|
@@ -6711,8 +7162,8 @@ var migrationsCommand = defineCommand11({
|
|
|
6711
7162
|
// src/commands/preset.ts
|
|
6712
7163
|
import { defineCommand as defineCommand12 } from "citty";
|
|
6713
7164
|
import * as p12 from "@clack/prompts";
|
|
6714
|
-
import { existsSync as
|
|
6715
|
-
import { join as
|
|
7165
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync9 } from "fs";
|
|
7166
|
+
import { join as join20, resolve as resolve20 } from "path";
|
|
6716
7167
|
var PRESET_ID_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
6717
7168
|
function stackTemplate(id) {
|
|
6718
7169
|
return [
|
|
@@ -6780,7 +7231,7 @@ var initSubCommand2 = defineCommand12({
|
|
|
6780
7231
|
process.exit(1);
|
|
6781
7232
|
}
|
|
6782
7233
|
const presetDir = resolve20(cwd, ".navori/presets", id);
|
|
6783
|
-
if (
|
|
7234
|
+
if (existsSync27(presetDir)) {
|
|
6784
7235
|
p12.cancel(
|
|
6785
7236
|
`Ya existe .navori/presets/${id}/ \u2014 b\xF3rralo o usa otro id si quieres regenerarlo.`
|
|
6786
7237
|
);
|
|
@@ -6806,11 +7257,11 @@ var initSubCommand2 = defineCommand12({
|
|
|
6806
7257
|
},
|
|
6807
7258
|
invariants: []
|
|
6808
7259
|
};
|
|
6809
|
-
mkdirSync9(
|
|
6810
|
-
mkdirSync9(
|
|
6811
|
-
writeFileAtomic(
|
|
6812
|
-
writeFileAtomic(
|
|
6813
|
-
writeFileAtomic(
|
|
7260
|
+
mkdirSync9(join20(presetDir, "managed"), { recursive: true });
|
|
7261
|
+
mkdirSync9(join20(presetDir, "skills"), { recursive: true });
|
|
7262
|
+
writeFileAtomic(join20(presetDir, `${id}.json`), JSON.stringify(manifest, null, 2) + "\n");
|
|
7263
|
+
writeFileAtomic(join20(presetDir, "managed", "stack.md"), stackTemplate(id));
|
|
7264
|
+
writeFileAtomic(join20(presetDir, "skills", `${skillId}.md`), skillTemplate(skillId));
|
|
6814
7265
|
p12.log.success(`Creado .navori/presets/${id}/`);
|
|
6815
7266
|
p12.log.message(
|
|
6816
7267
|
[
|
|
@@ -6819,8 +7270,8 @@ var initSubCommand2 = defineCommand12({
|
|
|
6819
7270
|
` ${dim("\xB7")} skills/${skillId}.md`
|
|
6820
7271
|
].join("\n")
|
|
6821
7272
|
);
|
|
6822
|
-
const configPath =
|
|
6823
|
-
if (
|
|
7273
|
+
const configPath = join20(cwd, "navori.config.json");
|
|
7274
|
+
if (existsSync27(configPath)) {
|
|
6824
7275
|
const config = readConfig(configPath);
|
|
6825
7276
|
writeConfig(configPath, { ...config, preset: id });
|
|
6826
7277
|
p12.log.success(`navori.config.json \u2192 preset: ${accent(id)}`);
|
|
@@ -6847,10 +7298,10 @@ var presetCommand = defineCommand12({
|
|
|
6847
7298
|
import { defineCommand as defineCommand13 } from "citty";
|
|
6848
7299
|
import * as p13 from "@clack/prompts";
|
|
6849
7300
|
import { resolve as resolve21 } from "path";
|
|
6850
|
-
import { existsSync as
|
|
7301
|
+
import { existsSync as existsSync28 } from "fs";
|
|
6851
7302
|
function runScan(opts) {
|
|
6852
7303
|
const configPath = resolve21(opts.cwd, "navori.config.json");
|
|
6853
|
-
if (!
|
|
7304
|
+
if (!existsSync28(configPath)) {
|
|
6854
7305
|
return { kind: "no-config", configPath };
|
|
6855
7306
|
}
|
|
6856
7307
|
const config = readConfig(configPath);
|
|
@@ -7021,7 +7472,7 @@ async function collectPresetOverrides(added) {
|
|
|
7021
7472
|
// src/commands/status.ts
|
|
7022
7473
|
import { defineCommand as defineCommand14 } from "citty";
|
|
7023
7474
|
import * as p14 from "@clack/prompts";
|
|
7024
|
-
import { existsSync as
|
|
7475
|
+
import { existsSync as existsSync29 } from "fs";
|
|
7025
7476
|
import { resolve as resolve22 } from "path";
|
|
7026
7477
|
var statusCommand = defineCommand14({
|
|
7027
7478
|
meta: {
|
|
@@ -7035,7 +7486,7 @@ var statusCommand = defineCommand14({
|
|
|
7035
7486
|
async run({ args }) {
|
|
7036
7487
|
const cwd = resolve22(args.cwd ?? process.cwd());
|
|
7037
7488
|
const configPath = `${cwd}/navori.config.json`;
|
|
7038
|
-
if (!
|
|
7489
|
+
if (!existsSync29(configPath)) {
|
|
7039
7490
|
if (args.json) {
|
|
7040
7491
|
console.log(JSON.stringify({ ok: false, error: "config-missing", configPath }));
|
|
7041
7492
|
} else {
|
|
@@ -7059,7 +7510,7 @@ var statusCommand = defineCommand14({
|
|
|
7059
7510
|
}
|
|
7060
7511
|
throw err;
|
|
7061
7512
|
}
|
|
7062
|
-
const claudeMdExists =
|
|
7513
|
+
const claudeMdExists = existsSync29(`${cwd}/CLAUDE.md`);
|
|
7063
7514
|
const missingPlugins = collectMissingPlugins(config);
|
|
7064
7515
|
const drifts = scanManagedDrift(cwd, config);
|
|
7065
7516
|
const enabledPlugins = Object.entries(config.plugins ?? {}).filter(([, v]) => v.enabled === true).map(([k]) => k);
|
|
@@ -7107,7 +7558,7 @@ var statusCommand = defineCommand14({
|
|
|
7107
7558
|
import { defineCommand as defineCommand15 } from "citty";
|
|
7108
7559
|
import * as p15 from "@clack/prompts";
|
|
7109
7560
|
import { performance as performance2 } from "perf_hooks";
|
|
7110
|
-
import { existsSync as
|
|
7561
|
+
import { existsSync as existsSync30 } from "fs";
|
|
7111
7562
|
import { resolve as resolve23 } from "path";
|
|
7112
7563
|
var benchCommand = defineCommand15({
|
|
7113
7564
|
meta: {
|
|
@@ -7121,7 +7572,7 @@ var benchCommand = defineCommand15({
|
|
|
7121
7572
|
async run({ args }) {
|
|
7122
7573
|
const cwd = resolve23(args.cwd ?? process.cwd());
|
|
7123
7574
|
p15.intro(brand("bench"));
|
|
7124
|
-
if (!
|
|
7575
|
+
if (!existsSync30(`${cwd}/navori.config.json`)) {
|
|
7125
7576
|
p15.cancel(`No navori.config.json at ${cwd}. Run 'navori init' first.`);
|
|
7126
7577
|
process.exit(1);
|
|
7127
7578
|
}
|