@lnai/core 0.6.5 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -283,7 +283,7 @@ declare const claudeCodePlugin: Plugin;
283
283
  * Output structure:
284
284
  * - AGENTS.md (symlink -> .ai/AGENTS.md) [at project root]
285
285
  * - <dir>/AGENTS.md (generated from .ai/rules/*.md, per glob directory)
286
- * - .codex/skills/<name>/ (symlink -> ../../.ai/skills/<name>)
286
+ * - .agents/skills/<name>/ (symlink -> ../../.ai/skills/<name>)
287
287
  * - .codex/config.toml (generated from settings.mcpServers)
288
288
  * - .codex/<path> (symlink -> ../.ai/.codex/<path>) for override files
289
289
  */
@@ -295,7 +295,7 @@ declare const codexPlugin: Plugin;
295
295
  * Output structure:
296
296
  * - AGENTS.md (symlink -> .ai/AGENTS.md) [at project root]
297
297
  * - .opencode/rules/ (symlink -> ../.ai/rules)
298
- * - .opencode/skills/<name>/ (symlink -> ../../.ai/skills/<name>)
298
+ * - .agents/skills/<name>/ (symlink -> ../../.ai/skills/<name>)
299
299
  * - opencode.json (generated config merged with .ai/.opencode/opencode.json)
300
300
  * - .opencode/<path> (symlink -> ../.ai/.opencode/<path>) for other override files
301
301
  */
@@ -343,7 +343,7 @@ declare function writeFiles(files: OutputFile[], options: WriterOptions): Promis
343
343
  /**
344
344
  * Update .gitignore with paths that should not be version controlled.
345
345
  * Manages a dedicated "lnai-generated" section to avoid conflicts with user entries.
346
- * Merges new paths with existing ones to support partial syncs (e.g., syncing one tool).
346
+ * Replaces the managed section on each run so stale paths are removed.
347
347
  */
348
348
  declare function updateGitignore(rootDir: string, paths: string[]): Promise<void>;
349
349
 
package/dist/index.js CHANGED
@@ -413,6 +413,39 @@ function validateToolIds(tools) {
413
413
  }
414
414
  return { valid: true, errors: [], warnings: [], skipped: [] };
415
415
  }
416
+
417
+ // src/utils/agents.ts
418
+ function createRootAgentsMdSymlink(state) {
419
+ if (!state.agents) {
420
+ return null;
421
+ }
422
+ return {
423
+ path: "AGENTS.md",
424
+ type: "symlink",
425
+ target: `${UNIFIED_DIR}/AGENTS.md`
426
+ };
427
+ }
428
+ function createSkillSymlinks(state, outputDir) {
429
+ const depth = outputDir.split("/").length + 1;
430
+ const prefix = "../".repeat(depth);
431
+ return state.skills.map((skill) => ({
432
+ path: `${outputDir}/skills/${skill.path}`,
433
+ type: "symlink",
434
+ target: `${prefix}${UNIFIED_DIR}/skills/${skill.path}`
435
+ }));
436
+ }
437
+ function createNoAgentsMdWarning(outputDescription) {
438
+ return {
439
+ path: ["AGENTS.md"],
440
+ message: `No AGENTS.md found - ${outputDescription} will not be created`
441
+ };
442
+ }
443
+ function hasPermissionsConfigured(permissions) {
444
+ if (!permissions) {
445
+ return false;
446
+ }
447
+ return (permissions.allow?.length ?? 0) > 0 || (permissions.ask?.length ?? 0) > 0 || (permissions.deny?.length ?? 0) > 0;
448
+ }
416
449
  async function scanOverrideDirectory(rootDir, toolId) {
417
450
  const overrideDir = path.join(rootDir, UNIFIED_DIR, OVERRIDE_DIRS[toolId]);
418
451
  try {
@@ -457,6 +490,7 @@ async function applyFileOverrides(files, rootDir, toolId) {
457
490
  }
458
491
 
459
492
  // src/plugins/claude-code/index.ts
493
+ var OUTPUT_DIR = TOOL_OUTPUT_DIRS.claudeCode;
460
494
  var claudeCodePlugin = {
461
495
  id: "claudeCode",
462
496
  name: "Claude Code",
@@ -468,28 +502,21 @@ var claudeCodePlugin = {
468
502
  },
469
503
  async export(state, rootDir) {
470
504
  const files = [];
471
- const outputDir = TOOL_OUTPUT_DIRS.claudeCode;
472
505
  if (state.agents) {
473
506
  files.push({
474
- path: `${outputDir}/CLAUDE.md`,
507
+ path: `${OUTPUT_DIR}/CLAUDE.md`,
475
508
  type: "symlink",
476
509
  target: `../${UNIFIED_DIR}/AGENTS.md`
477
510
  });
478
511
  }
479
512
  if (state.rules.length > 0) {
480
513
  files.push({
481
- path: `${outputDir}/rules`,
514
+ path: `${OUTPUT_DIR}/rules`,
482
515
  type: "symlink",
483
516
  target: `../${UNIFIED_DIR}/rules`
484
517
  });
485
518
  }
486
- for (const skill of state.skills) {
487
- files.push({
488
- path: `${outputDir}/skills/${skill.path}`,
489
- type: "symlink",
490
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
491
- });
492
- }
519
+ files.push(...createSkillSymlinks(state, OUTPUT_DIR));
493
520
  const settings = {};
494
521
  if (state.settings?.permissions) {
495
522
  settings["permissions"] = state.settings.permissions;
@@ -499,7 +526,7 @@ var claudeCodePlugin = {
499
526
  }
500
527
  if (Object.keys(settings).length > 0) {
501
528
  files.push({
502
- path: `${outputDir}/settings.json`,
529
+ path: `${OUTPUT_DIR}/settings.json`,
503
530
  type: "json",
504
531
  content: settings
505
532
  });
@@ -509,10 +536,7 @@ var claudeCodePlugin = {
509
536
  validate(state) {
510
537
  const warnings = [];
511
538
  if (!state.agents) {
512
- warnings.push({
513
- path: ["AGENTS.md"],
514
- message: "No AGENTS.md found - .claude/CLAUDE.md will not be created"
515
- });
539
+ warnings.push(createNoAgentsMdWarning(".claude/CLAUDE.md"));
516
540
  }
517
541
  return { valid: true, errors: [], warnings, skipped: [] };
518
542
  }
@@ -554,6 +578,8 @@ ${rule.content}
554
578
  }
555
579
 
556
580
  // src/plugins/codex/index.ts
581
+ var OUTPUT_DIR2 = TOOL_OUTPUT_DIRS.codex;
582
+ var SKILLS_DIR = ".agents";
557
583
  var codexPlugin = {
558
584
  id: "codex",
559
585
  name: "Codex",
@@ -565,13 +591,9 @@ var codexPlugin = {
565
591
  },
566
592
  async export(state, rootDir) {
567
593
  const files = [];
568
- const outputDir = TOOL_OUTPUT_DIRS.codex;
569
- if (state.agents) {
570
- files.push({
571
- path: "AGENTS.md",
572
- type: "symlink",
573
- target: `${UNIFIED_DIR}/AGENTS.md`
574
- });
594
+ const agentsSymlink = createRootAgentsMdSymlink(state);
595
+ if (agentsSymlink) {
596
+ files.push(agentsSymlink);
575
597
  }
576
598
  const rulesMap = groupRulesByDirectory(state.rules);
577
599
  for (const [dir, contents] of rulesMap.entries()) {
@@ -585,17 +607,11 @@ var codexPlugin = {
585
607
  content: combinedContent
586
608
  });
587
609
  }
588
- for (const skill of state.skills) {
589
- files.push({
590
- path: `${outputDir}/skills/${skill.path}`,
591
- type: "symlink",
592
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
593
- });
594
- }
610
+ files.push(...createSkillSymlinks(state, SKILLS_DIR));
595
611
  const configToml = buildCodexConfigToml(state.settings?.mcpServers);
596
612
  if (configToml) {
597
613
  files.push({
598
- path: `${outputDir}/config.toml`,
614
+ path: `${OUTPUT_DIR2}/config.toml`,
599
615
  type: "text",
600
616
  content: configToml
601
617
  });
@@ -606,10 +622,7 @@ var codexPlugin = {
606
622
  const warnings = [];
607
623
  const skipped = [];
608
624
  if (!state.agents) {
609
- warnings.push({
610
- path: ["AGENTS.md"],
611
- message: "No AGENTS.md found - root AGENTS.md will not be created"
612
- });
625
+ warnings.push(createNoAgentsMdWarning("root AGENTS.md"));
613
626
  }
614
627
  const rulesMap = groupRulesByDirectory(state.rules);
615
628
  if (rulesMap.has(".")) {
@@ -629,14 +642,11 @@ var codexPlugin = {
629
642
  }
630
643
  }
631
644
  }
632
- if (state.settings?.permissions) {
633
- const hasPermissions = (state.settings.permissions.allow?.length ?? 0) > 0 || (state.settings.permissions.ask?.length ?? 0) > 0 || (state.settings.permissions.deny?.length ?? 0) > 0;
634
- if (hasPermissions) {
635
- skipped.push({
636
- feature: "permissions",
637
- reason: "Codex rules are not generated from LNAI permissions"
638
- });
639
- }
645
+ if (hasPermissionsConfigured(state.settings?.permissions)) {
646
+ skipped.push({
647
+ feature: "permissions",
648
+ reason: "Codex rules are not generated from LNAI permissions"
649
+ });
640
650
  }
641
651
  return { valid: true, errors: [], warnings, skipped };
642
652
  }
@@ -826,6 +836,7 @@ function transformMcpToCopilot(servers) {
826
836
  }
827
837
 
828
838
  // src/plugins/copilot/index.ts
839
+ var OUTPUT_DIR3 = TOOL_OUTPUT_DIRS.copilot;
829
840
  var copilotPlugin = {
830
841
  id: "copilot",
831
842
  name: "GitHub Copilot",
@@ -837,12 +848,9 @@ var copilotPlugin = {
837
848
  },
838
849
  async export(state, rootDir) {
839
850
  const files = [];
840
- if (state.agents) {
841
- files.push({
842
- path: ".github/copilot-instructions.md",
843
- type: "symlink",
844
- target: `../${UNIFIED_DIR}/AGENTS.md`
845
- });
851
+ const agentsSymlink = createRootAgentsMdSymlink(state);
852
+ if (agentsSymlink) {
853
+ files.push(agentsSymlink);
846
854
  }
847
855
  for (const rule of state.rules) {
848
856
  const transformed = transformRuleToCopilot(rule);
@@ -852,18 +860,12 @@ var copilotPlugin = {
852
860
  );
853
861
  const outputFilename = rule.path.replace(/\.md$/, ".instructions.md");
854
862
  files.push({
855
- path: `.github/instructions/${outputFilename}`,
863
+ path: `${OUTPUT_DIR3}/instructions/${outputFilename}`,
856
864
  type: "text",
857
865
  content: ruleContent
858
866
  });
859
867
  }
860
- for (const skill of state.skills) {
861
- files.push({
862
- path: `.github/skills/${skill.path}`,
863
- type: "symlink",
864
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
865
- });
866
- }
868
+ files.push(...createSkillSymlinks(state, OUTPUT_DIR3));
867
869
  const mcpConfig = transformMcpToCopilot(state.settings?.mcpServers);
868
870
  if (mcpConfig) {
869
871
  files.push({
@@ -878,14 +880,9 @@ var copilotPlugin = {
878
880
  const warnings = [];
879
881
  const skipped = [];
880
882
  if (!state.agents) {
881
- warnings.push({
882
- path: ["AGENTS.md"],
883
- message: "No AGENTS.md found - .github/copilot-instructions.md will not be created"
884
- });
883
+ warnings.push(createNoAgentsMdWarning("root AGENTS.md"));
885
884
  }
886
- const permissions = state.settings?.permissions;
887
- const hasPermissions = permissions && (permissions.allow && permissions.allow.length > 0 || permissions.ask && permissions.ask.length > 0 || permissions.deny && permissions.deny.length > 0);
888
- if (hasPermissions) {
885
+ if (hasPermissionsConfigured(state.settings?.permissions)) {
889
886
  skipped.push({
890
887
  feature: "permissions",
891
888
  reason: "GitHub Copilot does not support declarative permissions"
@@ -1017,6 +1014,7 @@ function transformPermissionRule(rule) {
1017
1014
  }
1018
1015
 
1019
1016
  // src/plugins/cursor/index.ts
1017
+ var OUTPUT_DIR4 = TOOL_OUTPUT_DIRS.cursor;
1020
1018
  var cursorPlugin = {
1021
1019
  id: "cursor",
1022
1020
  name: "Cursor",
@@ -1028,13 +1026,9 @@ var cursorPlugin = {
1028
1026
  },
1029
1027
  async export(state, rootDir) {
1030
1028
  const files = [];
1031
- const outputDir = TOOL_OUTPUT_DIRS.cursor;
1032
- if (state.agents) {
1033
- files.push({
1034
- path: "AGENTS.md",
1035
- type: "symlink",
1036
- target: `${UNIFIED_DIR}/AGENTS.md`
1037
- });
1029
+ const agentsSymlink = createRootAgentsMdSymlink(state);
1030
+ if (agentsSymlink) {
1031
+ files.push(agentsSymlink);
1038
1032
  }
1039
1033
  for (const rule of state.rules) {
1040
1034
  const transformed = transformRuleToCursor(rule);
@@ -1044,22 +1038,16 @@ var cursorPlugin = {
1044
1038
  );
1045
1039
  const outputFilename = rule.path.replace(/\.md$/, ".mdc");
1046
1040
  files.push({
1047
- path: `${outputDir}/rules/${outputFilename}`,
1041
+ path: `${OUTPUT_DIR4}/rules/${outputFilename}`,
1048
1042
  type: "text",
1049
1043
  content: ruleContent
1050
1044
  });
1051
1045
  }
1052
- for (const skill of state.skills) {
1053
- files.push({
1054
- path: `${outputDir}/skills/${skill.path}`,
1055
- type: "symlink",
1056
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
1057
- });
1058
- }
1046
+ files.push(...createSkillSymlinks(state, OUTPUT_DIR4));
1059
1047
  const mcpServers = transformMcpToCursor(state.settings?.mcpServers);
1060
1048
  if (mcpServers) {
1061
1049
  files.push({
1062
- path: `${outputDir}/mcp.json`,
1050
+ path: `${OUTPUT_DIR4}/mcp.json`,
1063
1051
  type: "json",
1064
1052
  content: { mcpServers }
1065
1053
  });
@@ -1067,7 +1055,7 @@ var cursorPlugin = {
1067
1055
  const cliContent = buildCliContent(state.settings?.permissions);
1068
1056
  if (cliContent) {
1069
1057
  files.push({
1070
- path: `${outputDir}/cli.json`,
1058
+ path: `${OUTPUT_DIR4}/cli.json`,
1071
1059
  type: "json",
1072
1060
  content: cliContent
1073
1061
  });
@@ -1077,10 +1065,7 @@ var cursorPlugin = {
1077
1065
  validate(state) {
1078
1066
  const warnings = [];
1079
1067
  if (!state.agents) {
1080
- warnings.push({
1081
- path: ["AGENTS.md"],
1082
- message: "No AGENTS.md found - root AGENTS.md will not be created"
1083
- });
1068
+ warnings.push(createNoAgentsMdWarning("root AGENTS.md"));
1084
1069
  }
1085
1070
  const permissionsResult = transformPermissionsToCursor(
1086
1071
  state.settings?.permissions
@@ -1131,6 +1116,7 @@ function transformMcpToGemini(mcpServers) {
1131
1116
  }
1132
1117
 
1133
1118
  // src/plugins/gemini/index.ts
1119
+ var OUTPUT_DIR5 = TOOL_OUTPUT_DIRS.gemini;
1134
1120
  var geminiPlugin = {
1135
1121
  id: "gemini",
1136
1122
  name: "Gemini CLI",
@@ -1142,13 +1128,9 @@ var geminiPlugin = {
1142
1128
  },
1143
1129
  async export(state, rootDir) {
1144
1130
  const files = [];
1145
- const outputDir = TOOL_OUTPUT_DIRS.gemini;
1146
- if (state.agents) {
1147
- files.push({
1148
- path: `${outputDir}/GEMINI.md`,
1149
- type: "symlink",
1150
- target: `../${UNIFIED_DIR}/AGENTS.md`
1151
- });
1131
+ const agentsSymlink = createRootAgentsMdSymlink(state);
1132
+ if (agentsSymlink) {
1133
+ files.push(agentsSymlink);
1152
1134
  }
1153
1135
  const rulesMap = groupRulesByDirectory(state.rules);
1154
1136
  for (const [dir, contents] of rulesMap.entries()) {
@@ -1160,19 +1142,21 @@ var geminiPlugin = {
1160
1142
  content: combinedContent
1161
1143
  });
1162
1144
  }
1163
- for (const skill of state.skills) {
1164
- files.push({
1165
- path: `${outputDir}/skills/${skill.path}`,
1166
- type: "symlink",
1167
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
1168
- });
1169
- }
1145
+ files.push(...createSkillSymlinks(state, OUTPUT_DIR5));
1170
1146
  const mcpServers = transformMcpToGemini(state.settings?.mcpServers);
1171
- if (mcpServers) {
1147
+ const hasAgents = !!state.agents;
1148
+ if (mcpServers || hasAgents) {
1149
+ const settingsContent = {};
1150
+ if (hasAgents) {
1151
+ settingsContent["context"] = { fileName: ["AGENTS.md"] };
1152
+ }
1153
+ if (mcpServers) {
1154
+ settingsContent["mcpServers"] = mcpServers;
1155
+ }
1172
1156
  files.push({
1173
- path: `${outputDir}/settings.json`,
1157
+ path: `${OUTPUT_DIR5}/settings.json`,
1174
1158
  type: "json",
1175
- content: { mcpServers }
1159
+ content: settingsContent
1176
1160
  });
1177
1161
  }
1178
1162
  return applyFileOverrides(files, rootDir, "gemini");
@@ -1181,19 +1165,13 @@ var geminiPlugin = {
1181
1165
  const warnings = [];
1182
1166
  const skipped = [];
1183
1167
  if (!state.agents) {
1184
- warnings.push({
1185
- path: ["AGENTS.md"],
1186
- message: "No AGENTS.md found - GEMINI.md will not be created"
1187
- });
1168
+ warnings.push(createNoAgentsMdWarning("root AGENTS.md"));
1188
1169
  }
1189
- if (state.settings?.permissions) {
1190
- const hasPermissions = (state.settings.permissions.allow?.length ?? 0) > 0 || (state.settings.permissions.ask?.length ?? 0) > 0 || (state.settings.permissions.deny?.length ?? 0) > 0;
1191
- if (hasPermissions) {
1192
- skipped.push({
1193
- feature: "permissions",
1194
- reason: "Gemini CLI does not support declarative permissions - permissions must be granted interactively"
1195
- });
1196
- }
1170
+ if (hasPermissionsConfigured(state.settings?.permissions)) {
1171
+ skipped.push({
1172
+ feature: "permissions",
1173
+ reason: "Gemini CLI does not support declarative permissions - permissions must be granted interactively"
1174
+ });
1197
1175
  }
1198
1176
  if (state.rules.length > 0) {
1199
1177
  warnings.push({
@@ -1278,6 +1256,8 @@ function parsePermissionRuleForOpenCode(rule) {
1278
1256
  }
1279
1257
 
1280
1258
  // src/plugins/opencode/index.ts
1259
+ var OUTPUT_DIR6 = TOOL_OUTPUT_DIRS.opencode;
1260
+ var SKILLS_DIR2 = ".agents";
1281
1261
  var opencodePlugin = {
1282
1262
  id: "opencode",
1283
1263
  name: "OpenCode",
@@ -1289,33 +1269,23 @@ var opencodePlugin = {
1289
1269
  },
1290
1270
  async export(state, rootDir) {
1291
1271
  const files = [];
1292
- const outputDir = TOOL_OUTPUT_DIRS.opencode;
1293
- if (state.agents) {
1294
- files.push({
1295
- path: "AGENTS.md",
1296
- type: "symlink",
1297
- target: `${UNIFIED_DIR}/AGENTS.md`
1298
- });
1272
+ const agentsSymlink = createRootAgentsMdSymlink(state);
1273
+ if (agentsSymlink) {
1274
+ files.push(agentsSymlink);
1299
1275
  }
1300
1276
  if (state.rules.length > 0) {
1301
1277
  files.push({
1302
- path: `${outputDir}/rules`,
1278
+ path: `${OUTPUT_DIR6}/rules`,
1303
1279
  type: "symlink",
1304
1280
  target: `../${UNIFIED_DIR}/rules`
1305
1281
  });
1306
1282
  }
1307
- for (const skill of state.skills) {
1308
- files.push({
1309
- path: `${outputDir}/skills/${skill.path}`,
1310
- type: "symlink",
1311
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
1312
- });
1313
- }
1283
+ files.push(...createSkillSymlinks(state, SKILLS_DIR2));
1314
1284
  const config = {
1315
1285
  $schema: "https://opencode.ai/config.json"
1316
1286
  };
1317
1287
  if (state.rules.length > 0) {
1318
- config["instructions"] = [`${outputDir}/rules/*.md`];
1288
+ config["instructions"] = [`${OUTPUT_DIR6}/rules/*.md`];
1319
1289
  }
1320
1290
  const mcp = transformMcpToOpenCode(state.settings?.mcpServers);
1321
1291
  if (mcp) {
@@ -1337,10 +1307,7 @@ var opencodePlugin = {
1337
1307
  validate(state) {
1338
1308
  const warnings = [];
1339
1309
  if (!state.agents) {
1340
- warnings.push({
1341
- path: ["AGENTS.md"],
1342
- message: "No AGENTS.md found - root AGENTS.md will not be created"
1343
- });
1310
+ warnings.push(createNoAgentsMdWarning("root AGENTS.md"));
1344
1311
  }
1345
1312
  warnings.push(
1346
1313
  ...validateMcpServers(state.settings?.mcpServers, [
@@ -1403,6 +1370,7 @@ function serializeWindsurfRule(frontmatter, content) {
1403
1370
  }
1404
1371
 
1405
1372
  // src/plugins/windsurf/index.ts
1373
+ var OUTPUT_DIR7 = TOOL_OUTPUT_DIRS.windsurf;
1406
1374
  var windsurfPlugin = {
1407
1375
  id: "windsurf",
1408
1376
  name: "Windsurf",
@@ -1414,13 +1382,9 @@ var windsurfPlugin = {
1414
1382
  },
1415
1383
  async export(state, rootDir) {
1416
1384
  const files = [];
1417
- const outputDir = TOOL_OUTPUT_DIRS.windsurf;
1418
- if (state.agents) {
1419
- files.push({
1420
- path: "AGENTS.md",
1421
- type: "symlink",
1422
- target: `${UNIFIED_DIR}/AGENTS.md`
1423
- });
1385
+ const agentsSymlink = createRootAgentsMdSymlink(state);
1386
+ if (agentsSymlink) {
1387
+ files.push(agentsSymlink);
1424
1388
  }
1425
1389
  for (const rule of state.rules) {
1426
1390
  const transformed = transformRuleToWindsurf(rule);
@@ -1429,28 +1393,19 @@ var windsurfPlugin = {
1429
1393
  transformed.content
1430
1394
  );
1431
1395
  files.push({
1432
- path: `${outputDir}/rules/${rule.path}`,
1396
+ path: `${OUTPUT_DIR7}/rules/${rule.path}`,
1433
1397
  type: "text",
1434
1398
  content: ruleContent
1435
1399
  });
1436
1400
  }
1437
- for (const skill of state.skills) {
1438
- files.push({
1439
- path: `${outputDir}/skills/${skill.path}`,
1440
- type: "symlink",
1441
- target: `../../${UNIFIED_DIR}/skills/${skill.path}`
1442
- });
1443
- }
1401
+ files.push(...createSkillSymlinks(state, OUTPUT_DIR7));
1444
1402
  return applyFileOverrides(files, rootDir, "windsurf");
1445
1403
  },
1446
1404
  validate(state) {
1447
1405
  const warnings = [];
1448
1406
  const skipped = [];
1449
1407
  if (!state.agents) {
1450
- warnings.push({
1451
- path: ["AGENTS.md"],
1452
- message: "No AGENTS.md found - root AGENTS.md will not be created"
1453
- });
1408
+ warnings.push(createNoAgentsMdWarning("root AGENTS.md"));
1454
1409
  }
1455
1410
  if (state.rules.length > 0) {
1456
1411
  warnings.push({
@@ -1464,14 +1419,11 @@ var windsurfPlugin = {
1464
1419
  reason: "Windsurf uses global MCP config at ~/.codeium/windsurf/mcp_config.json - project-level MCP servers are not exported"
1465
1420
  });
1466
1421
  }
1467
- if (state.settings?.permissions) {
1468
- const hasPermissions = (state.settings.permissions.allow?.length ?? 0) > 0 || (state.settings.permissions.ask?.length ?? 0) > 0 || (state.settings.permissions.deny?.length ?? 0) > 0;
1469
- if (hasPermissions) {
1470
- skipped.push({
1471
- feature: "permissions",
1472
- reason: "Windsurf does not support declarative permissions"
1473
- });
1474
- }
1422
+ if (hasPermissionsConfigured(state.settings?.permissions)) {
1423
+ skipped.push({
1424
+ feature: "permissions",
1425
+ reason: "Windsurf does not support declarative permissions"
1426
+ });
1475
1427
  }
1476
1428
  return { valid: true, errors: [], warnings, skipped };
1477
1429
  }
@@ -1642,23 +1594,37 @@ async function writeFiles(files, options) {
1642
1594
  async function updateGitignore(rootDir, paths) {
1643
1595
  const gitignorePath = path.join(rootDir, ".gitignore");
1644
1596
  let content = "";
1597
+ let hasExistingFile = true;
1645
1598
  try {
1646
1599
  content = await fs4.readFile(gitignorePath, "utf-8");
1647
1600
  } catch {
1601
+ hasExistingFile = false;
1648
1602
  }
1649
1603
  const marker = "# lnai-generated";
1650
1604
  const endMarker = "# end lnai-generated";
1651
- const extractRegex = new RegExp(`${marker}\\n([\\s\\S]*?)${endMarker}`);
1652
- const match = extractRegex.exec(content);
1653
- const existingSection = match?.[1] ?? "";
1654
- const existingPaths = existingSection.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
1655
1605
  const markerRegex = new RegExp(`${marker}[\\s\\S]*?${endMarker}\\n?`, "g");
1606
+ const hasManagedSection = new RegExp(`${marker}[\\s\\S]*?${endMarker}`).test(
1607
+ content
1608
+ );
1656
1609
  content = content.replace(markerRegex, "");
1657
- content = content.trimEnd();
1658
- const uniquePaths = [.../* @__PURE__ */ new Set([...existingPaths, ...paths])].sort();
1659
- const newSection = ["", marker, ...uniquePaths, endMarker, ""].join("\n");
1660
- content = content + newSection;
1661
- await fs4.writeFile(gitignorePath, content, "utf-8");
1610
+ const baseContent = content.trimEnd();
1611
+ const uniquePaths = [...new Set(paths)].sort();
1612
+ if (uniquePaths.length === 0) {
1613
+ if (!hasManagedSection && !hasExistingFile) {
1614
+ return;
1615
+ }
1616
+ const cleanedContent = baseContent.length > 0 ? `${baseContent}
1617
+ ` : baseContent;
1618
+ await fs4.writeFile(gitignorePath, cleanedContent, "utf-8");
1619
+ return;
1620
+ }
1621
+ const managedSection = [marker, ...uniquePaths, endMarker].join("\n");
1622
+ const nextContent = baseContent.length > 0 ? `${baseContent}
1623
+
1624
+ ${managedSection}
1625
+ ` : `${managedSection}
1626
+ `;
1627
+ await fs4.writeFile(gitignorePath, nextContent, "utf-8");
1662
1628
  }
1663
1629
 
1664
1630
  // src/manifest/index.ts
@@ -1777,7 +1743,6 @@ async function runSyncPipeline(options) {
1777
1743
  }
1778
1744
  let manifest = await readManifest(rootDir) ?? createEmptyManifest();
1779
1745
  const results = [];
1780
- const pathsToIgnore = [];
1781
1746
  for (const toolId of toolsToSync) {
1782
1747
  const plugin = pluginRegistry.get(toolId);
1783
1748
  if (!plugin) {
@@ -1803,15 +1768,12 @@ async function runSyncPipeline(options) {
1803
1768
  if (!dryRun) {
1804
1769
  manifest = updateToolManifest(manifest, toolId, outputFiles);
1805
1770
  }
1806
- const toolConfig = state.config.tools?.[toolId];
1807
- if (!toolConfig?.versionControl) {
1808
- pathsToIgnore.push(...outputFiles.map((f) => f.path));
1809
- }
1810
1771
  }
1811
1772
  if (!dryRun) {
1812
1773
  await writeManifest(rootDir, manifest);
1813
- }
1814
- if (pathsToIgnore.length > 0 && !dryRun) {
1774
+ const pathsToIgnore = Object.entries(manifest.tools).flatMap(
1775
+ ([toolId, toolManifest]) => !state.config.tools?.[toolId]?.versionControl && toolManifest?.files ? toolManifest.files.map((file) => file.path) : []
1776
+ );
1815
1777
  await updateGitignore(rootDir, pathsToIgnore);
1816
1778
  }
1817
1779
  return results;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lnai/core",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Core library for LNAI - unified AI config management",
5
5
  "type": "module",
6
6
  "license": "MIT",