gitnexus 1.6.11-rc.25 → 1.6.11-rc.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli/ai-context.js +86 -13
- package/dist/cli/i18n/en.d.ts +1 -1
- package/dist/cli/i18n/en.js +1 -1
- package/dist/cli/i18n/resources.d.ts +1 -1
- package/dist/cli/i18n/zh-CN.js +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/setup.js +37 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,7 +240,7 @@ gitnexus analyze --force # Full rebuild: re-parse + graph rebuild + FTS
|
|
|
240
240
|
gitnexus analyze --embeddings # Enable embedding generation (slower, better search)
|
|
241
241
|
gitnexus embeddings install # Fetch the optional local embedding stack on demand (--cuda, --force)
|
|
242
242
|
gitnexus analyze --skills # Generate repo-specific skill files from detected communities
|
|
243
|
-
gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits
|
|
243
|
+
gitnexus analyze --skip-agents-md # Preserve custom AGENTS.md/CLAUDE.md gitnexus section edits (does not skip standard skills; use --skip-skills; community --skills files are unaffected)
|
|
244
244
|
gitnexus analyze --skip-skills # Skip installing standard .claude/skills/gitnexus-* skill files
|
|
245
245
|
gitnexus analyze --skip-git # Index folders that are not Git repositories
|
|
246
246
|
gitnexus analyze --workers <n> # Parse worker pool size (>=1; default: cores-1, capped at 16)
|
package/dist/cli/ai-context.js
CHANGED
|
@@ -9,6 +9,7 @@ import fs from 'fs/promises';
|
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import { STANDARD_SKILL_CATALOG } from './standard-skills.js';
|
|
12
|
+
import { isEnoent } from './editor-targets.js';
|
|
12
13
|
import { logger } from '../core/logger.js';
|
|
13
14
|
// ESM equivalent of __dirname
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -145,8 +146,7 @@ This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${s
|
|
|
145
146
|
- **MUST analyze graph changes before committing.** Use \`detect_changes({scope: "all"})\` (MCP) or \`${runner} detect-changes --scope all --repo .\` (CLI fallback). \`partial: true\` or \`truncated: true\` is not a clean check — a zero means unseen, not unaffected; re-run it. For regression review: \`detect_changes({scope: "compare", base_ref: ${JSON.stringify(markdownSafeBranch(defaultBranch))}})\` or \`${runner} detect-changes --scope compare --base-ref ${JSON.stringify(markdownSafeBranch(defaultBranch))} --repo .\`.
|
|
146
147
|
- MUST warn on HIGH/CRITICAL \`risk\` pre-edit; never use \`riskSharedAxes\` to waive a HIGH/CRITICAL \`risk\` warning. Compare File/symbol: MCP File omits axes; Graph-RAG expands File.
|
|
147
148
|
- **MUST treat \`risk: UNKNOWN\` as unresolved, not as low.** An empty caller set is not evidence the symbol is unused — it can also mean the callers are not resolvable by the index (plain-object property access, dynamic dispatch, cross-language calls). \`impact\` pairs \`UNKNOWN\` with a \`riskNote\` saying so. Confirm with a text search before treating the symbol as safe to change or delete; do not proceed on the strength of a zero.
|
|
148
|
-
-
|
|
149
|
-
- Use \`context({name: "symbolName"})\` for callers, callees, and flows.${hasSpringActuator
|
|
149
|
+
- **MUST use \`query({search_query: "concept"})\` for concepts/flows, \`context({name: "symbolName"})\` for a named symbol, or \`impact\` for blast radius, on read-only callers, dependencies, imports, or execution flow.** Graph first; text search only for empty/\`UNKNOWN\`/literals.${hasSpringActuator
|
|
150
150
|
? '\n- Spring Actuator runtime evidence is enabled. A Route is authoritative only when `runtimeConfirmed === true`; `runtimeSource` is provenance and may also describe conflicts. Snapshot values are never persisted.'
|
|
151
151
|
: ''}
|
|
152
152
|
- For security review, \`explain({target: "fileOrSymbol"})\` lists taint findings (source→sink flows; needs \`analyze --pdg\`).${hasPdg
|
|
@@ -322,6 +322,55 @@ export async function shouldMirrorSkillsToAgents(repoPath) {
|
|
|
322
322
|
return false;
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
|
+
const SKILL_PRESERVE_HINT = 'delete the file to refresh from the bundled template, or pass --skip-skills to skip skill install';
|
|
326
|
+
async function readUtf8IfPresent(filePath) {
|
|
327
|
+
try {
|
|
328
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
if (isEnoent(err))
|
|
332
|
+
return null;
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function skillBytesDiverge(existing, bundled) {
|
|
337
|
+
return existing !== null && existing !== bundled;
|
|
338
|
+
}
|
|
339
|
+
/** Write bundled skill bytes unless an existing file already differs. */
|
|
340
|
+
async function writeSkillUnlessDivergent(filePath, content) {
|
|
341
|
+
const existing = await readUtf8IfPresent(filePath);
|
|
342
|
+
if (skillBytesDiverge(existing, content)) {
|
|
343
|
+
logger.warn(`Preserved customized skill ${filePath}; ${SKILL_PRESERVE_HINT}.`);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
347
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
async function inspectLegacySkillDir(legacyDir) {
|
|
351
|
+
let entries;
|
|
352
|
+
try {
|
|
353
|
+
entries = await fs.readdir(legacyDir);
|
|
354
|
+
}
|
|
355
|
+
catch (err) {
|
|
356
|
+
if (isEnoent(err))
|
|
357
|
+
return null;
|
|
358
|
+
throw err;
|
|
359
|
+
}
|
|
360
|
+
const nestedExisting = entries.includes('SKILL.md')
|
|
361
|
+
? await fs.readFile(path.join(legacyDir, 'SKILL.md'), 'utf-8')
|
|
362
|
+
: null;
|
|
363
|
+
return {
|
|
364
|
+
nestedExisting,
|
|
365
|
+
hasSiblings: entries.some((entry) => entry !== 'SKILL.md'),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
function formatSkillInstallLine(prefix, total, preserved, allWrittenSuffix, partialSuffix) {
|
|
369
|
+
if (preserved > 0) {
|
|
370
|
+
return `${prefix} (${total - preserved} written, ${preserved} ${partialSuffix})`;
|
|
371
|
+
}
|
|
372
|
+
return `${prefix} (${total} ${allWrittenSuffix})`;
|
|
373
|
+
}
|
|
325
374
|
/**
|
|
326
375
|
* Install GitNexus skills as direct children of .claude/skills/
|
|
327
376
|
* Works natively with Claude Code, Cursor, and GitHub Copilot.
|
|
@@ -331,13 +380,14 @@ async function installSkills(repoPath) {
|
|
|
331
380
|
const skillsDir = path.join(repoPath, '.claude', 'skills');
|
|
332
381
|
const legacySkillsDir = path.join(skillsDir, 'gitnexus');
|
|
333
382
|
const installedSkills = [];
|
|
383
|
+
let claudePreserved = 0;
|
|
384
|
+
let agentsPreserved = 0;
|
|
385
|
+
let legacyPreserved = 0;
|
|
334
386
|
const agentsMirror = await shouldMirrorSkillsToAgents(repoPath);
|
|
335
387
|
for (const skill of STANDARD_SKILL_CATALOG.filter((entry) => entry.distributions.project && entry.distributions.npm)) {
|
|
336
388
|
const skillDir = path.join(skillsDir, skill.name);
|
|
337
389
|
const skillPath = path.join(skillDir, 'SKILL.md');
|
|
338
390
|
try {
|
|
339
|
-
// Create skill directory
|
|
340
|
-
await fs.mkdir(skillDir, { recursive: true });
|
|
341
391
|
// Try to read from package skills directory
|
|
342
392
|
const packageSkillPath = path.join(__dirname, '..', '..', 'skills', `${skill.name}.md`);
|
|
343
393
|
let skillContent;
|
|
@@ -358,13 +408,14 @@ ${skill.fallbackDescription}
|
|
|
358
408
|
Use GitNexus tools to accomplish this task.
|
|
359
409
|
`;
|
|
360
410
|
}
|
|
361
|
-
await
|
|
411
|
+
if (await writeSkillUnlessDivergent(skillPath, skillContent))
|
|
412
|
+
claudePreserved += 1;
|
|
362
413
|
// Mirror to .agents/skills/ for agents that read repo-local skills
|
|
363
414
|
if (agentsMirror) {
|
|
364
415
|
try {
|
|
365
|
-
const
|
|
366
|
-
await
|
|
367
|
-
|
|
416
|
+
const agentsSkillPath = path.join(repoPath, '.agents', 'skills', skill.name, 'SKILL.md');
|
|
417
|
+
if (await writeSkillUnlessDivergent(agentsSkillPath, skillContent))
|
|
418
|
+
agentsPreserved += 1;
|
|
368
419
|
}
|
|
369
420
|
catch (err) {
|
|
370
421
|
logger.warn({ err }, `Warning: Could not mirror skill ${skill.name} to .agents/skills:`);
|
|
@@ -375,7 +426,20 @@ Use GitNexus tools to accomplish this task.
|
|
|
375
426
|
// deep. Remove only the child owned by this installer; unknown siblings
|
|
376
427
|
// under the legacy grouping directory may be user-authored and survive.
|
|
377
428
|
try {
|
|
378
|
-
|
|
429
|
+
const legacyDir = path.join(legacySkillsDir, skill.name);
|
|
430
|
+
const nestedSkill = path.join(legacyDir, 'SKILL.md');
|
|
431
|
+
const leftover = await inspectLegacySkillDir(legacyDir);
|
|
432
|
+
if (leftover !== null && skillBytesDiverge(leftover.nestedExisting, skillContent)) {
|
|
433
|
+
logger.warn(`Preserved customized skill ${nestedSkill}; ${SKILL_PRESERVE_HINT}.`);
|
|
434
|
+
legacyPreserved += 1;
|
|
435
|
+
}
|
|
436
|
+
else if (leftover?.hasSiblings) {
|
|
437
|
+
logger.warn(`Preserved legacy skill directory ${legacyDir} because it contains operator-owned files.`);
|
|
438
|
+
legacyPreserved += 1;
|
|
439
|
+
}
|
|
440
|
+
else if (leftover !== null) {
|
|
441
|
+
await fs.rm(legacyDir, { recursive: true, force: true });
|
|
442
|
+
}
|
|
379
443
|
}
|
|
380
444
|
catch (err) {
|
|
381
445
|
logger.warn({ err }, `Warning: Could not remove legacy skill ${skill.name}:`);
|
|
@@ -386,7 +450,13 @@ Use GitNexus tools to accomplish this task.
|
|
|
386
450
|
logger.warn({ err }, `Warning: Could not install skill ${skill.name}:`);
|
|
387
451
|
}
|
|
388
452
|
}
|
|
389
|
-
return {
|
|
453
|
+
return {
|
|
454
|
+
skills: installedSkills,
|
|
455
|
+
agentsMirror,
|
|
456
|
+
claudePreserved,
|
|
457
|
+
agentsPreserved,
|
|
458
|
+
legacyPreserved,
|
|
459
|
+
};
|
|
390
460
|
}
|
|
391
461
|
/**
|
|
392
462
|
* Generate AI context files after indexing
|
|
@@ -435,11 +505,14 @@ export async function generateAIContextFiles(repoPath, storagePath, projectName,
|
|
|
435
505
|
}
|
|
436
506
|
// Install standard skills directly under .claude/skills/ (unless --skip-skills)
|
|
437
507
|
if (!options?.skipSkills) {
|
|
438
|
-
const { skills: installedSkills, agentsMirror } = await installSkills(repoPath);
|
|
508
|
+
const { skills: installedSkills, agentsMirror, claudePreserved, agentsPreserved, legacyPreserved, } = await installSkills(repoPath);
|
|
439
509
|
if (installedSkills.length > 0) {
|
|
440
|
-
createdFiles.push(
|
|
510
|
+
createdFiles.push(formatSkillInstallLine('.claude/skills/gitnexus-*/', installedSkills.length, claudePreserved, 'skills', 'preserved'));
|
|
441
511
|
if (agentsMirror) {
|
|
442
|
-
createdFiles.push(
|
|
512
|
+
createdFiles.push(formatSkillInstallLine('.agents/skills/gitnexus-*/', installedSkills.length, agentsPreserved, 'skills mirrored for .agents', 'preserved for .agents'));
|
|
513
|
+
}
|
|
514
|
+
if (legacyPreserved > 0) {
|
|
515
|
+
createdFiles.push(`.claude/skills/gitnexus/<name>/ (legacy directories preserved: ${legacyPreserved})`);
|
|
443
516
|
}
|
|
444
517
|
}
|
|
445
518
|
}
|
package/dist/cli/i18n/en.d.ts
CHANGED
|
@@ -162,7 +162,7 @@ export declare const en: {
|
|
|
162
162
|
readonly 'help.option.analyze.embeddings': "Enable embedding generation for semantic search (off by default). Optional [limit] overrides the 50,000-node safety cap; pass 0 to disable the cap entirely.";
|
|
163
163
|
readonly 'help.option.analyze.dropEmbeddings': "Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves any embeddings already present in the index.";
|
|
164
164
|
readonly 'help.option.analyze.skills': "Generate repo-specific skill files from detected communities (no-op when --index-only is also set).";
|
|
165
|
-
readonly 'help.option.analyze.skipAgentsMd': "Skip updating the gitnexus section in AGENTS.md and CLAUDE.md";
|
|
165
|
+
readonly 'help.option.analyze.skipAgentsMd': "Skip updating the gitnexus section in AGENTS.md and CLAUDE.md. Does not skip standard skills in .claude/skills or .agents/skills; use --skip-skills for those. Community skills from --skills are unaffected.";
|
|
166
166
|
readonly 'help.option.analyze.noStats': "Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md";
|
|
167
167
|
readonly 'help.option.analyze.selfCommit': "Auto-commit AGENTS.md/CLAUDE.md changes after analyze (opt-in, off by default). Scoped to only those two files (never `git add -A`); no-ops if neither exists, neither changed, or the repo has no git identity configured.";
|
|
168
168
|
readonly 'help.option.analyze.skipSkills': "Skip installing standard GitNexus skill files directly under .claude/skills/ and .agents/skills/. Does not suppress community skills from --skills (those use .claude/skills/gitnexus-area-*). Use --index-only to skip all AI-context file injection.";
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -164,7 +164,7 @@ export const en = {
|
|
|
164
164
|
'help.option.analyze.embeddings': 'Enable embedding generation for semantic search (off by default). Optional [limit] overrides the 50,000-node safety cap; pass 0 to disable the cap entirely.',
|
|
165
165
|
'help.option.analyze.dropEmbeddings': 'Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves any embeddings already present in the index.',
|
|
166
166
|
'help.option.analyze.skills': 'Generate repo-specific skill files from detected communities (no-op when --index-only is also set).',
|
|
167
|
-
'help.option.analyze.skipAgentsMd': 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md',
|
|
167
|
+
'help.option.analyze.skipAgentsMd': 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md. Does not skip standard skills in .claude/skills or .agents/skills; use --skip-skills for those. Community skills from --skills are unaffected.',
|
|
168
168
|
'help.option.analyze.noStats': 'Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md',
|
|
169
169
|
'help.option.analyze.selfCommit': 'Auto-commit AGENTS.md/CLAUDE.md changes after analyze (opt-in, off by default). Scoped to only those two files (never `git add -A`); no-ops if neither exists, neither changed, or the repo has no git identity configured.',
|
|
170
170
|
'help.option.analyze.skipSkills': 'Skip installing standard GitNexus skill files directly under .claude/skills/ and .agents/skills/. Does not suppress community skills from --skills (those use .claude/skills/gitnexus-area-*). Use --index-only to skip all AI-context file injection.',
|
|
@@ -163,7 +163,7 @@ export declare const cliResources: {
|
|
|
163
163
|
readonly 'help.option.analyze.embeddings': "Enable embedding generation for semantic search (off by default). Optional [limit] overrides the 50,000-node safety cap; pass 0 to disable the cap entirely.";
|
|
164
164
|
readonly 'help.option.analyze.dropEmbeddings': "Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves any embeddings already present in the index.";
|
|
165
165
|
readonly 'help.option.analyze.skills': "Generate repo-specific skill files from detected communities (no-op when --index-only is also set).";
|
|
166
|
-
readonly 'help.option.analyze.skipAgentsMd': "Skip updating the gitnexus section in AGENTS.md and CLAUDE.md";
|
|
166
|
+
readonly 'help.option.analyze.skipAgentsMd': "Skip updating the gitnexus section in AGENTS.md and CLAUDE.md. Does not skip standard skills in .claude/skills or .agents/skills; use --skip-skills for those. Community skills from --skills are unaffected.";
|
|
167
167
|
readonly 'help.option.analyze.noStats': "Omit volatile file/symbol counts from AGENTS.md and CLAUDE.md";
|
|
168
168
|
readonly 'help.option.analyze.selfCommit': "Auto-commit AGENTS.md/CLAUDE.md changes after analyze (opt-in, off by default). Scoped to only those two files (never `git add -A`); no-ops if neither exists, neither changed, or the repo has no git identity configured.";
|
|
169
169
|
readonly 'help.option.analyze.skipSkills': "Skip installing standard GitNexus skill files directly under .claude/skills/ and .agents/skills/. Does not suppress community skills from --skills (those use .claude/skills/gitnexus-area-*). Use --index-only to skip all AI-context file injection.";
|
package/dist/cli/i18n/zh-CN.js
CHANGED
|
@@ -162,7 +162,7 @@ export const zhCN = {
|
|
|
162
162
|
'help.option.analyze.embeddings': '启用语义搜索的嵌入生成(默认关闭)。可选 [limit] 覆盖 50,000 节点安全上限;传 0 可完全禁用上限。',
|
|
163
163
|
'help.option.analyze.dropEmbeddings': '重建时删除现有嵌入。默认情况下,未传 `--embeddings` 的 `analyze` 会保留索引中已有嵌入。',
|
|
164
164
|
'help.option.analyze.skills': '根据检测到的社区生成仓库专属 skill 文件(同时设置 --index-only 时无效)。',
|
|
165
|
-
'help.option.analyze.skipAgentsMd': '跳过更新 AGENTS.md 和 CLAUDE.md 中的 gitnexus
|
|
165
|
+
'help.option.analyze.skipAgentsMd': '跳过更新 AGENTS.md 和 CLAUDE.md 中的 gitnexus 区块。不会跳过 .claude/skills 或 .agents/skills 下的标准 skill;如需跳过那些请使用 --skip-skills。--skills 生成的社区 skill 不受影响。',
|
|
166
166
|
'help.option.analyze.noStats': '从 AGENTS.md 和 CLAUDE.md 中省略易变的文件/符号计数',
|
|
167
167
|
'help.option.analyze.selfCommit': '在 analyze 后自动提交 AGENTS.md/CLAUDE.md 的变更(默认关闭,需显式开启)。仅限这两个文件(绝不使用 `git add -A`);若两者均不存在、均未变更,或仓库未配置 git 身份,则不执行任何操作。',
|
|
168
168
|
'help.option.analyze.skipSkills': '跳过直接安装在 .claude/skills/ 和 .agents/skills/ 下的标准 GitNexus skill 文件。不抑制 --skills 生成的社区 skill(位于 .claude/skills/gitnexus-area-*)。使用 --index-only 可跳过所有 AI 上下文文件注入。',
|
package/dist/cli/index.js
CHANGED
|
@@ -46,7 +46,7 @@ program
|
|
|
46
46
|
'preserves any embeddings already present in the index.')
|
|
47
47
|
.option('--skills', 'Generate repo-specific skill files from detected communities ' +
|
|
48
48
|
'(no-op when --index-only is also set).')
|
|
49
|
-
.option('--skip-agents-md', 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md')
|
|
49
|
+
.option('--skip-agents-md', 'Skip updating the gitnexus section in AGENTS.md and CLAUDE.md. Does not skip standard skills in .claude/skills or .agents/skills; use --skip-skills for those. Community skills from --skills are unaffected.')
|
|
50
50
|
.option('--pdg', 'Build the control-flow-graph / PDG substrate (BasicBlock nodes + CFG edges) ' +
|
|
51
51
|
'for supported languages. Opt-in; off by default. (#2081 M1)')
|
|
52
52
|
.option('--default-branch <branch>', 'Default branch used in the generated regression-compare example (base_ref). ' +
|
package/dist/cli/setup.js
CHANGED
|
@@ -947,15 +947,29 @@ async function installSkillsTo(targetDir) {
|
|
|
947
947
|
for (const [skillName, source] of skillSources) {
|
|
948
948
|
const skillDir = path.join(targetDir, skillName);
|
|
949
949
|
try {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
950
|
+
const sourceSkillPath = source.isDirectory
|
|
951
|
+
? path.join(skillsRoot, skillName, 'SKILL.md')
|
|
952
|
+
: path.join(skillsRoot, `${skillName}.md`);
|
|
953
|
+
const destinationSkillPath = path.join(skillDir, 'SKILL.md');
|
|
954
|
+
const [sourceSkillContent, destinationSkillContent] = await Promise.all([
|
|
955
|
+
fs.readFile(sourceSkillPath, 'utf-8'),
|
|
956
|
+
fs.readFile(destinationSkillPath, 'utf-8').catch((err) => {
|
|
957
|
+
if (!isEnoent(err))
|
|
958
|
+
throw err;
|
|
959
|
+
return null;
|
|
960
|
+
}),
|
|
961
|
+
]);
|
|
962
|
+
const preserved = destinationSkillContent !== null && destinationSkillContent !== sourceSkillContent;
|
|
963
|
+
if (preserved && !source.isDirectory) {
|
|
964
|
+
console.log(`[gitnexus] preserved customized skill ${destinationSkillPath}; ` +
|
|
965
|
+
'delete the file and rerun setup to refresh it.');
|
|
953
966
|
}
|
|
954
|
-
else {
|
|
955
|
-
|
|
956
|
-
|
|
967
|
+
else if (source.isDirectory) {
|
|
968
|
+
await copyDirRecursive(path.join(skillsRoot, skillName), skillDir);
|
|
969
|
+
}
|
|
970
|
+
else if (!preserved) {
|
|
957
971
|
await fs.mkdir(skillDir, { recursive: true });
|
|
958
|
-
await fs.writeFile(
|
|
972
|
+
await fs.writeFile(destinationSkillPath, sourceSkillContent, 'utf-8');
|
|
959
973
|
}
|
|
960
974
|
// A directory superseded by a shipped rename is warned about, never
|
|
961
975
|
// deleted: the installer cannot prove it owns the contents (users
|
|
@@ -968,7 +982,8 @@ async function installSkillsTo(targetDir) {
|
|
|
968
982
|
`left ${legacyDir} in place — delete it manually if you have not customized it.`);
|
|
969
983
|
}
|
|
970
984
|
}
|
|
971
|
-
|
|
985
|
+
if (!preserved)
|
|
986
|
+
installed.push(skillName);
|
|
972
987
|
}
|
|
973
988
|
catch {
|
|
974
989
|
// Source skill not found — skip
|
|
@@ -987,10 +1002,22 @@ async function copyDirRecursive(src, dest) {
|
|
|
987
1002
|
const destPath = path.join(dest, entry.name);
|
|
988
1003
|
if (entry.isDirectory()) {
|
|
989
1004
|
await copyDirRecursive(srcPath, destPath);
|
|
1005
|
+
continue;
|
|
990
1006
|
}
|
|
991
|
-
|
|
992
|
-
|
|
1007
|
+
const [srcBuf, destBuf] = await Promise.all([
|
|
1008
|
+
fs.readFile(srcPath),
|
|
1009
|
+
fs.readFile(destPath).catch((err) => {
|
|
1010
|
+
if (!isEnoent(err))
|
|
1011
|
+
throw err;
|
|
1012
|
+
return null;
|
|
1013
|
+
}),
|
|
1014
|
+
]);
|
|
1015
|
+
if (destBuf !== null && !destBuf.equals(srcBuf)) {
|
|
1016
|
+
console.log(`[gitnexus] preserved customized skill ${destPath}; ` +
|
|
1017
|
+
'delete the file and rerun setup to refresh it.');
|
|
1018
|
+
continue;
|
|
993
1019
|
}
|
|
1020
|
+
await fs.writeFile(destPath, srcBuf);
|
|
994
1021
|
}
|
|
995
1022
|
}
|
|
996
1023
|
/**
|
package/package.json
CHANGED