agentsmesh 0.21.0 → 0.23.0

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/engine.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { stringify, parse } from 'yaml';
3
3
  import { basename, join, dirname, relative, win32, posix, sep, resolve, extname } from 'path';
4
- import { readFile, rm, mkdir, readdir, stat, lstat, unlink, writeFile, rename, chmod, access, realpath, mkdtemp, cp } from 'fs/promises';
5
- import { constants, existsSync, realpathSync, statSync, readFileSync } from 'fs';
4
+ import { readFile, rm, mkdir, readdir, stat, lstat, unlink, writeFile, rename, chmod, realpath, access, mkdtemp, cp } from 'fs/promises';
5
+ import { setTimeout } from 'timers/promises';
6
+ import { existsSync, readFileSync, constants, readdirSync, realpathSync, statSync } from 'fs';
6
7
  import { parse as parse$1 } from 'smol-toml';
7
8
  import { Buffer } from 'buffer';
8
9
  import { homedir, tmpdir } from 'os';
@@ -11,6 +12,7 @@ import { execFile } from 'child_process';
11
12
  import { fileURLToPath, pathToFileURL, URL } from 'url';
12
13
  import { promisify } from 'util';
13
14
  import * as tar from 'tar';
15
+ import picomatch from 'picomatch';
14
16
  import { createTwoFilesPatch } from 'diff';
15
17
 
16
18
  var __defProp = Object.defineProperty;
@@ -45,7 +47,7 @@ function validateCapabilityImplementations(descriptor31, capabilities17, ctx, pa
45
47
  function validateDescriptor(value) {
46
48
  return targetDescriptorSchema.parse(value);
47
49
  }
48
- var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
50
+ var capabilityLevelSchema, capabilitiesSchema, generatorsSchema, pathResolversSchema, layoutSchema, globalSupportSchema, legacyGlobalKeys, generatorRequirements, settingsBackedFeatures, conversionDefaultsSchema, metadataSchema, nativePickStrategySchema, nativeInstallSchema, targetDescriptorSchemaBase, targetDescriptorSchema;
49
51
  var init_target_descriptor_schema = __esm({
50
52
  "src/targets/catalog/target-descriptor.schema.ts"() {
51
53
  capabilityLevelSchema = z.union([
@@ -119,6 +121,22 @@ var init_target_descriptor_schema = __esm({
119
121
  officialUrl: z.string().min(1),
120
122
  shortDescription: z.string().min(1)
121
123
  }).passthrough();
124
+ nativePickStrategySchema = z.discriminatedUnion("kind", [
125
+ z.object({ kind: z.literal("basename"), suffix: z.string().min(1) }),
126
+ z.object({ kind: z.literal("skillDir") }),
127
+ z.object({ kind: z.literal("firstSegment") })
128
+ ]);
129
+ nativeInstallSchema = z.object({
130
+ pickPaths: z.array(
131
+ z.object({
132
+ prefix: z.string().min(1),
133
+ feature: z.enum(["commands", "rules", "agents", "skills"]),
134
+ strategy: nativePickStrategySchema
135
+ })
136
+ ).optional(),
137
+ inferPick: z.function().optional(),
138
+ dialectHints: z.array(z.object({ frontmatterKey: z.string().min(1) })).optional()
139
+ }).strict();
122
140
  targetDescriptorSchemaBase = z.object({
123
141
  id: z.string().regex(/^[a-z][a-z0-9-]*$/, "Target id must be lowercase with hyphens"),
124
142
  metadata: metadataSchema,
@@ -130,6 +148,7 @@ var init_target_descriptor_schema = __esm({
130
148
  globalSupport: globalSupportSchema.optional(),
131
149
  buildImportPaths: z.function(),
132
150
  detectionPaths: z.array(z.string()),
151
+ nativeInstall: nativeInstallSchema.optional(),
133
152
  excludeFromStarterInit: z.boolean().optional(),
134
153
  conversionDefaults: conversionDefaultsSchema.optional(),
135
154
  emitScopedSettings: z.function().optional(),
@@ -630,15 +649,19 @@ var init_errors = __esm({
630
649
  LockAcquisitionError = class extends AgentsMeshError {
631
650
  lockPath;
632
651
  holder;
652
+ /** Human-readable lock name surfaced in the message, e.g. "lessons lock". */
653
+ label;
633
654
  constructor(lockPath, holder, options) {
655
+ const label = options?.label ?? "lock";
634
656
  super(
635
657
  "AM_LOCK_ACQUISITION_FAILED",
636
- `Could not acquire generate lock at ${lockPath}: currently held by ${holder}. Wait for the other process to finish, or remove ${lockPath} manually if you are sure no agentsmesh process is running.`,
658
+ `Could not acquire ${label} at ${lockPath}: currently held by ${holder}. Wait for the other process to finish, or remove ${lockPath} manually if you are sure no agentsmesh process is running.`,
637
659
  options
638
660
  );
639
661
  this.name = "LockAcquisitionError";
640
662
  this.lockPath = lockPath;
641
663
  this.holder = holder;
664
+ this.label = label;
642
665
  }
643
666
  };
644
667
  FileSystemError = class extends AgentsMeshError {
@@ -803,6 +826,27 @@ var init_fs_traverse = __esm({
803
826
  MAX_SEGMENT_REPETITIONS = 3;
804
827
  }
805
828
  });
829
+ async function renameWithRetry(from, to, options = {}) {
830
+ const attempts = options.attempts ?? 5;
831
+ const delayMs = options.delayMs ?? 50;
832
+ for (let attempt = 0; ; attempt++) {
833
+ try {
834
+ await rename(from, to);
835
+ return;
836
+ } catch (err) {
837
+ const code = err.code;
838
+ const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
839
+ if (!transient || attempt >= attempts - 1) throw err;
840
+ await setTimeout(delayMs * 2 ** attempt);
841
+ }
842
+ }
843
+ }
844
+ var TRANSIENT_RENAME_CODES;
845
+ var init_rename_retry = __esm({
846
+ "src/utils/filesystem/rename-retry.ts"() {
847
+ TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "ENOTEMPTY", "EEXIST"]);
848
+ }
849
+ });
806
850
  async function readFileSafe(path) {
807
851
  try {
808
852
  const data = await readFile(path, "utf-8");
@@ -889,6 +933,7 @@ var init_fs = __esm({
889
933
  init_fs_text_encoding();
890
934
  init_fs_traverse();
891
935
  init_fs_text_encoding();
936
+ init_rename_retry();
892
937
  }
893
938
  });
894
939
  function escapeRegExp(value) {
@@ -897,19 +942,24 @@ function escapeRegExp(value) {
897
942
  function managedBlockPattern(start, end) {
898
943
  return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}`, "g");
899
944
  }
900
- function replaceManagedBlock(content, start, end, block) {
901
- const pattern = managedBlockPattern(start, end);
902
- if (pattern.test(content)) {
903
- return content.replace(pattern, block).trim();
904
- }
905
- const trimmed = content.trim();
906
- return trimmed ? `${trimmed}
907
-
908
- ${block}` : block;
909
- }
910
945
  function stripManagedBlock(content, start, end) {
911
946
  return content.replace(managedBlockPattern(start, end), "").trim();
912
947
  }
948
+ function splitFrontmatterPrefix(content) {
949
+ if (content.indexOf("---") !== 0) return { prefix: "", body: content.trim() };
950
+ const close = content.indexOf("---", 3);
951
+ if (close === -1) return { prefix: "", body: content.trim() };
952
+ return { prefix: content.slice(0, close + 3), body: content.slice(close + 3).trim() };
953
+ }
954
+ function insertAtBodyTop(content, block) {
955
+ const { prefix, body } = splitFrontmatterPrefix(content);
956
+ const placed = body ? `${block}
957
+
958
+ ${body}` : block;
959
+ return prefix ? `${prefix}
960
+
961
+ ${placed}` : placed;
962
+ }
913
963
  function ruleSource(source) {
914
964
  const normalized = source.replace(/\\/g, "/");
915
965
  const meshIndex = normalized.lastIndexOf(".agentsmesh/");
@@ -1011,31 +1061,9 @@ var init_managed_blocks = __esm({
1011
1061
  });
1012
1062
 
1013
1063
  // src/targets/projection/root-instruction-paragraph.ts
1014
- function normalizeWhitespace(value) {
1015
- return value.replace(/\s+/g, " ").trim();
1016
- }
1017
1064
  function appendAgentsmeshRootInstructionParagraph(content) {
1018
- const trimmed = content.trim();
1019
- if (trimmed.includes(ROOT_CONTRACT_START) && trimmed.includes(ROOT_CONTRACT_END)) {
1020
- return replaceManagedBlock(
1021
- trimmed,
1022
- ROOT_CONTRACT_START,
1023
- ROOT_CONTRACT_END,
1024
- AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH
1025
- );
1026
- }
1027
- const norm = normalizeWhitespace(trimmed);
1028
- if (norm.includes(normalizeWhitespace(AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH))) {
1029
- return trimmed;
1030
- }
1031
- for (const legacy of LEGACY_FORMS) {
1032
- if (norm.includes(normalizeWhitespace(legacy))) {
1033
- return trimmed.replace(legacy, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
1034
- }
1035
- }
1036
- return trimmed ? `${trimmed}
1037
-
1038
- ${AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH}` : AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH;
1065
+ const withoutPrior = stripAgentsmeshRootInstructionParagraph(content);
1066
+ return insertAtBodyTop(withoutPrior, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH);
1039
1067
  }
1040
1068
  function stripAgentsmeshRootInstructionParagraph(content) {
1041
1069
  let result = stripManagedBlock(content, ROOT_CONTRACT_START, ROOT_CONTRACT_END);
@@ -1049,7 +1077,7 @@ ${legacy}`, "");
1049
1077
  }
1050
1078
  return result.trim();
1051
1079
  }
1052
- var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
1080
+ var ROOT_INSTRUCTION_BODY_V1, ROOT_INSTRUCTION_BODY_V2, ROOT_INSTRUCTION_BODY_V3, ROOT_INSTRUCTION_BODY_V4, ROOT_INSTRUCTION_BODY_V5, ROOT_INSTRUCTION_BODY_V6, ROOT_INSTRUCTION_BODY_V7, ROOT_INSTRUCTION_BODY_V8, ROOT_INSTRUCTION_BODY_V9, ROOT_INSTRUCTION_BODY_V10, ROOT_INSTRUCTION_BODY, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION, AGENTSMESH_CONTRACT_WITH_V1_BODY, AGENTSMESH_CONTRACT_WITH_V2_BODY, AGENTSMESH_CONTRACT_WITH_V3_BODY, AGENTSMESH_CONTRACT_WITH_V4_BODY, AGENTSMESH_CONTRACT_WITH_V5_BODY, AGENTSMESH_CONTRACT_WITH_V6_BODY, AGENTSMESH_CONTRACT_WITH_V7_BODY, AGENTSMESH_CONTRACT_WITH_V8_BODY, AGENTSMESH_CONTRACT_WITH_V9_BODY, AGENTSMESH_CONTRACT_WITH_V10_BODY, AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH, LEGACY_FORMS;
1053
1081
  var init_root_instruction_paragraph = __esm({
1054
1082
  "src/targets/projection/root-instruction-paragraph.ts"() {
1055
1083
  init_managed_blocks();
@@ -1061,7 +1089,9 @@ var init_root_instruction_paragraph = __esm({
1061
1089
  ROOT_INSTRUCTION_BODY_V6 = "Create canonical files in `.agentsmesh`: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Then run `agentsmesh generate`.";
1062
1090
  ROOT_INSTRUCTION_BODY_V7 = "`.agentsmesh` is the only folder you edit or add these files in: `rules/_root.md` and `rules/*.md` are Markdown rules; `commands/*.md`, `agents/*.md`, and `skills/*/SKILL.md` plus supporting files use Claude-style frontmatter Markdown; `mcp.json` is MCP JSON; `hooks.yaml` and `permissions.yaml` are YAML; `ignore` is gitignore-style text. Do not edit generated tool files; run `agentsmesh generate`.";
1063
1091
  ROOT_INSTRUCTION_BODY_V8 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, and `merge` as needed; never edit generated tool files.";
1064
- ROOT_INSTRUCTION_BODY = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
1092
+ ROOT_INSTRUCTION_BODY_V9 = "`agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
1093
+ ROOT_INSTRUCTION_BODY_V10 = "**MUST follow when changing any rule, agent, command, skill, hook, MCP server, permission, or ignore pattern.** `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally), and `.agentsmesh` is the only place to add or edit canonical items: `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed; never edit generated tool files.";
1094
+ ROOT_INSTRUCTION_BODY = "**NEVER edit generated files** (`.claude/`, `.cursor/`, `AGENTS.md`, `.github/copilot-instructions.md`, and similar target outputs) \u2014 `agentsmesh generate` overwrites them. **All changes MUST go through `.agentsmesh` first**: edit `rules/_root.md`, `rules/*.md`, `commands/*.md`, `agents/*.md`, `skills/*/SKILL.md` plus supporting files, `mcp.json`, `hooks.yaml`, `permissions.yaml`, and `ignore`; `agentsmesh.yaml` selects targets/features (`agentsmesh.local.yaml` overrides locally); if missing run `agentsmesh init`, use `agentsmesh import --from <tool>` for native configs, `agentsmesh install <source>` or `install --sync` for reusable packs, then run `agentsmesh generate`. Use `diff`, `lint`, `check`, `watch`, `matrix`, `merge`, and `refresh` as needed.";
1065
1095
  LEGACY_AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = ROOT_INSTRUCTION_BODY_V1;
1066
1096
  LEGACY_AGENTSMESH_ROOT_INSTRUCTION_SECTION = `## Project-Specific Rules
1067
1097
 
@@ -1090,12 +1120,20 @@ ${ROOT_INSTRUCTION_BODY_V7}`;
1090
1120
  AGENTSMESH_CONTRACT_WITH_V8_BODY = `## AgentsMesh Generation Contract
1091
1121
 
1092
1122
  ${ROOT_INSTRUCTION_BODY_V8}`;
1123
+ AGENTSMESH_CONTRACT_WITH_V9_BODY = `## AgentsMesh Generation Contract
1124
+
1125
+ ${ROOT_INSTRUCTION_BODY_V9}`;
1126
+ AGENTSMESH_CONTRACT_WITH_V10_BODY = `## AgentsMesh Generation Contract
1127
+
1128
+ ${ROOT_INSTRUCTION_BODY_V10}`;
1093
1129
  AGENTSMESH_ROOT_INSTRUCTION_PARAGRAPH = `${ROOT_CONTRACT_START}
1094
1130
  ## AgentsMesh Generation Contract
1095
1131
 
1096
1132
  ${ROOT_INSTRUCTION_BODY}
1097
1133
  ${ROOT_CONTRACT_END}`;
1098
1134
  LEGACY_FORMS = [
1135
+ AGENTSMESH_CONTRACT_WITH_V10_BODY,
1136
+ AGENTSMESH_CONTRACT_WITH_V9_BODY,
1099
1137
  AGENTSMESH_CONTRACT_WITH_V8_BODY,
1100
1138
  AGENTSMESH_CONTRACT_WITH_V7_BODY,
1101
1139
  AGENTSMESH_CONTRACT_WITH_V6_BODY,
@@ -1999,9 +2037,8 @@ function shouldRewritePathToken(fullContent, start, end, matchText, rewriteBareP
1999
2037
  const before = fullContent[start - 1];
2000
2038
  const after = fullContent[end];
2001
2039
  if (isMarkdownReferenceDefinitionDestination(fullContent, start, candidateEnd)) return true;
2002
- if (before === "'" && after === "'" || before === '"' && after === '"' || before === "`" && after === "`") {
2003
- return true;
2004
- }
2040
+ if (before === "`" && after === "`") return true;
2041
+ if (before === "'" && after === "'" || before === '"' && after === '"') return false;
2005
2042
  if (before === "<" && after === ">") return true;
2006
2043
  if (before === "[" && after === "]") {
2007
2044
  if (!rewriteBarePathTokens && !isRootRelativePathToken(normalizedCandidate) && markdownBracketLabelDuplicatesDestination(fullContent, start, matchText)) {
@@ -3328,12 +3365,12 @@ var init_augment_code = __esm({
3328
3365
  });
3329
3366
 
3330
3367
  // src/targets/claude-code/constants.ts
3331
- var CLAUDE_CODE_TARGET, CLAUDE_ROOT, CLAUDE_LEGACY_ROOT, CLAUDE_RULES_DIR, CLAUDE_COMMANDS_DIR, CLAUDE_AGENTS_DIR, CLAUDE_SKILLS_DIR, CLAUDE_SETTINGS, CLAUDE_HOOKS_JSON, CLAUDE_OUTPUT_STYLES_DIR, CLAUDE_IGNORE, CLAUDE_MCP_JSON, CLAUDE_GLOBAL_MCP_JSON, CLAUDE_CANONICAL_RULES_DIR, CLAUDE_CANONICAL_COMMANDS_DIR, CLAUDE_CANONICAL_AGENTS_DIR, CLAUDE_CANONICAL_SKILLS_DIR, CLAUDE_CANONICAL_MCP, CLAUDE_CANONICAL_PERMISSIONS, CLAUDE_CANONICAL_HOOKS, CLAUDE_CANONICAL_IGNORE;
3368
+ var CLAUDE_CODE_TARGET, CLAUDE_ROOT, CLAUDE_NESTED_ROOT, CLAUDE_RULES_DIR, CLAUDE_COMMANDS_DIR, CLAUDE_AGENTS_DIR, CLAUDE_SKILLS_DIR, CLAUDE_SETTINGS, CLAUDE_HOOKS_JSON, CLAUDE_OUTPUT_STYLES_DIR, CLAUDE_IGNORE, CLAUDE_MCP_JSON, CLAUDE_GLOBAL_MCP_JSON, CLAUDE_CANONICAL_RULES_DIR, CLAUDE_CANONICAL_COMMANDS_DIR, CLAUDE_CANONICAL_AGENTS_DIR, CLAUDE_CANONICAL_SKILLS_DIR, CLAUDE_CANONICAL_MCP, CLAUDE_CANONICAL_PERMISSIONS, CLAUDE_CANONICAL_HOOKS, CLAUDE_CANONICAL_IGNORE;
3332
3369
  var init_constants7 = __esm({
3333
3370
  "src/targets/claude-code/constants.ts"() {
3334
3371
  CLAUDE_CODE_TARGET = "claude-code";
3335
- CLAUDE_ROOT = ".claude/CLAUDE.md";
3336
- CLAUDE_LEGACY_ROOT = "CLAUDE.md";
3372
+ CLAUDE_ROOT = "CLAUDE.md";
3373
+ CLAUDE_NESTED_ROOT = ".claude/CLAUDE.md";
3337
3374
  CLAUDE_RULES_DIR = ".claude/rules";
3338
3375
  CLAUDE_COMMANDS_DIR = ".claude/commands";
3339
3376
  CLAUDE_AGENTS_DIR = ".claude/agents";
@@ -5195,7 +5232,8 @@ var init_amp2 = __esm({
5195
5232
  markAsRoot: true
5196
5233
  }
5197
5234
  },
5198
- emitScopedSettings(canonical, _scope) {
5235
+ emitScopedSettings(canonical, _scope, enabledFeatures) {
5236
+ if (!enabledFeatures.has("mcp")) return [];
5199
5237
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
5200
5238
  return [
5201
5239
  {
@@ -5947,12 +5985,12 @@ function mergeAugmentSettings(existing, newContent) {
5947
5985
  if (overlay.hooks !== void 0) base.hooks = overlay.hooks;
5948
5986
  return JSON.stringify(base, null, 2);
5949
5987
  }
5950
- function buildSettingsContent(canonical) {
5988
+ function buildSettingsContent(canonical, enabledFeatures) {
5951
5989
  const settings = {};
5952
- if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
5990
+ if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
5953
5991
  settings.mcpServers = canonical.mcp.mcpServers;
5954
5992
  }
5955
- if (canonical.hooks && Object.keys(canonical.hooks).length > 0) {
5993
+ if (enabledFeatures.has("hooks") && canonical.hooks && Object.keys(canonical.hooks).length > 0) {
5956
5994
  settings.hooks = serializeHooksForSettings(canonical.hooks);
5957
5995
  }
5958
5996
  if (Object.keys(settings).length === 0) return null;
@@ -6081,8 +6119,8 @@ var init_augment_code2 = __esm({
6081
6119
  ],
6082
6120
  layout: globalLayout5
6083
6121
  },
6084
- emitScopedSettings(canonical) {
6085
- const content = buildSettingsContent(canonical);
6122
+ emitScopedSettings(canonical, _scope, enabledFeatures) {
6123
+ const content = buildSettingsContent(canonical, enabledFeatures);
6086
6124
  if (content === null) return [];
6087
6125
  return [{ path: AUGMENT_CODE_SETTINGS_FILE, content }];
6088
6126
  },
@@ -6656,7 +6694,10 @@ var init_claude_code2 = __esm({
6656
6694
  skillDir: ".claude/skills",
6657
6695
  managedOutputs: {
6658
6696
  dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
6659
- files: [".claude/CLAUDE.md", ".claude/settings.json", ".claudeignore", ".mcp.json"]
6697
+ // CLAUDE_NESTED_ROOT is the pre-migration project location; listing it here lets
6698
+ // `cleanupStaleGeneratedOutputs` evict a leftover `.claude/CLAUDE.md` once generation
6699
+ // writes the root `CLAUDE.md`, so Claude Code never concatenates both into context.
6700
+ files: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/settings.json", ".claudeignore", ".mcp.json"]
6660
6701
  },
6661
6702
  paths: {
6662
6703
  rulePath(slug, _rule) {
@@ -6671,7 +6712,7 @@ var init_claude_code2 = __esm({
6671
6712
  }
6672
6713
  };
6673
6714
  globalLayout6 = {
6674
- rootInstructionPath: CLAUDE_ROOT,
6715
+ rootInstructionPath: CLAUDE_NESTED_ROOT,
6675
6716
  skillDir: ".claude/skills",
6676
6717
  renderPrimaryRootInstruction: renderClaudeGlobalPrimaryInstructions,
6677
6718
  managedOutputs: {
@@ -6684,7 +6725,7 @@ var init_claude_code2 = __esm({
6684
6725
  ".agents/skills"
6685
6726
  ],
6686
6727
  files: [
6687
- ".claude/CLAUDE.md",
6728
+ CLAUDE_NESTED_ROOT,
6688
6729
  ".claude/settings.json",
6689
6730
  CLAUDE_GLOBAL_MCP_JSON,
6690
6731
  CLAUDE_HOOKS_JSON,
@@ -6692,6 +6733,7 @@ var init_claude_code2 = __esm({
6692
6733
  ]
6693
6734
  },
6694
6735
  rewriteGeneratedPath(path) {
6736
+ if (path === CLAUDE_ROOT) return CLAUDE_NESTED_ROOT;
6695
6737
  if (path === CLAUDE_MCP_JSON) return CLAUDE_GLOBAL_MCP_JSON;
6696
6738
  return path;
6697
6739
  },
@@ -6755,10 +6797,11 @@ var init_claude_code2 = __esm({
6755
6797
  importer: {
6756
6798
  rules: [
6757
6799
  {
6758
- // Root rule: prefer .claude/CLAUDE.md, fall back to legacy CLAUDE.md (project only).
6800
+ // Root rule: project prefers root CLAUDE.md, falls back to nested .claude/CLAUDE.md;
6801
+ // global reads the nested .claude/CLAUDE.md.
6759
6802
  feature: "rules",
6760
6803
  mode: "singleFile",
6761
- source: { project: [CLAUDE_ROOT, CLAUDE_LEGACY_ROOT], global: [CLAUDE_ROOT] },
6804
+ source: { project: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT], global: [CLAUDE_NESTED_ROOT] },
6762
6805
  canonicalDir: CLAUDE_CANONICAL_RULES_DIR,
6763
6806
  canonicalRootFilename: "_root.md",
6764
6807
  markAsRoot: true
@@ -6804,7 +6847,23 @@ var init_claude_code2 = __esm({
6804
6847
  }
6805
6848
  },
6806
6849
  buildImportPaths: buildClaudeCodeImportPaths,
6807
- detectionPaths: ["CLAUDE.md", ".claude/rules", ".claude/commands"]
6850
+ detectionPaths: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claude/rules", ".claude/commands"],
6851
+ nativeInstall: {
6852
+ pickPaths: [
6853
+ {
6854
+ prefix: ".claude/commands",
6855
+ feature: "commands",
6856
+ strategy: { kind: "basename", suffix: ".md" }
6857
+ },
6858
+ { prefix: ".claude/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
6859
+ {
6860
+ prefix: ".claude/agents",
6861
+ feature: "agents",
6862
+ strategy: { kind: "basename", suffix: ".md" }
6863
+ },
6864
+ { prefix: ".claude/skills/", feature: "skills", strategy: { kind: "firstSegment" } }
6865
+ ]
6866
+ }
6808
6867
  };
6809
6868
  }
6810
6869
  });
@@ -7683,6 +7742,16 @@ var init_cline2 = __esm({
7683
7742
  },
7684
7743
  buildImportPaths: buildClineImportPaths,
7685
7744
  detectionPaths: [".clinerules", ".cline"],
7745
+ nativeInstall: {
7746
+ pickPaths: [
7747
+ { prefix: CLINE_SKILLS_DIR, feature: "skills", strategy: { kind: "skillDir" } },
7748
+ {
7749
+ prefix: CLINE_WORKFLOWS_DIR,
7750
+ feature: "commands",
7751
+ strategy: { kind: "basename", suffix: ".md" }
7752
+ }
7753
+ ]
7754
+ },
7686
7755
  conversionDefaults: { agentsToSkills: true }
7687
7756
  };
7688
7757
  }
@@ -8601,6 +8670,11 @@ var init_codex_cli2 = __esm({
8601
8670
  ".codex/agents",
8602
8671
  ".codex/rules"
8603
8672
  ],
8673
+ nativeInstall: {
8674
+ pickPaths: [
8675
+ { prefix: ".codex", feature: "rules", strategy: { kind: "basename", suffix: ".md" } }
8676
+ ]
8677
+ },
8604
8678
  excludeFromStarterInit: true,
8605
8679
  conversionDefaults: { commandsToSkills: true, agentsToSkills: false }
8606
8680
  };
@@ -9104,6 +9178,21 @@ var init_continue2 = __esm({
9104
9178
  },
9105
9179
  buildImportPaths: buildContinueImportPaths,
9106
9180
  detectionPaths: [".continue/rules", ".continue/skills", ".continue/mcpServers"],
9181
+ nativeInstall: {
9182
+ pickPaths: [
9183
+ {
9184
+ prefix: ".continue/rules",
9185
+ feature: "rules",
9186
+ strategy: { kind: "basename", suffix: ".md" }
9187
+ },
9188
+ {
9189
+ prefix: ".continue/prompts",
9190
+ feature: "commands",
9191
+ strategy: { kind: "basename", suffix: ".md" }
9192
+ },
9193
+ { prefix: ".continue/skills", feature: "skills", strategy: { kind: "skillDir" } }
9194
+ ]
9195
+ },
9107
9196
  conversionDefaults: { agentsToSkills: true }
9108
9197
  };
9109
9198
  }
@@ -9471,6 +9560,80 @@ var init_importer10 = __esm({
9471
9560
  init_copilot2();
9472
9561
  }
9473
9562
  });
9563
+ async function skillNamesFromNativeSkillDir(scanRoot) {
9564
+ const files = await readDirRecursive(scanRoot);
9565
+ const names = /* @__PURE__ */ new Set();
9566
+ for (const f of files) {
9567
+ if (basename(f) === "SKILL.md") {
9568
+ names.add(basename(dirname(f)));
9569
+ continue;
9570
+ }
9571
+ const rel2 = relative(scanRoot, f).replace(/\\/g, "/");
9572
+ if (!rel2.includes("/") && f.toLowerCase().endsWith(".md")) {
9573
+ names.add(basename(f, ".md"));
9574
+ }
9575
+ }
9576
+ return [...names].filter(Boolean).sort();
9577
+ }
9578
+ var init_native_skill_scan = __esm({
9579
+ "src/install/native/native-skill-scan.ts"() {
9580
+ init_fs();
9581
+ }
9582
+ });
9583
+ async function inferCopilotPickFromPath(repoRoot, posixPath) {
9584
+ const scan = join(repoRoot, ...posixPath.split("/"));
9585
+ if (posixPath.startsWith(COPILOT_PROMPTS_DIR)) {
9586
+ const files = await readDirRecursive(scan);
9587
+ const commands = [
9588
+ ...new Set(
9589
+ files.filter((f) => f.toLowerCase().endsWith(".prompt.md")).map((f) => basename(f, ".prompt.md"))
9590
+ )
9591
+ ].sort();
9592
+ return commands.length ? { commands } : {};
9593
+ }
9594
+ if (posixPath.startsWith(".github/copilot") && !posixPath.includes("copilot-instructions.md")) {
9595
+ const files = await readDirRecursive(scan);
9596
+ const rules = [
9597
+ ...new Set(
9598
+ files.filter((f) => f.includes(".instructions.md")).map((f) => basename(f).replace(/\.instructions\.md$/i, ""))
9599
+ )
9600
+ ].sort();
9601
+ return rules.length ? { rules } : {};
9602
+ }
9603
+ if (posixPath.startsWith(".github/instructions")) {
9604
+ const files = await readDirRecursive(scan);
9605
+ const names = /* @__PURE__ */ new Set();
9606
+ for (const f of files) {
9607
+ const b = basename(f);
9608
+ if (b.toLowerCase().endsWith(".instructions.md"))
9609
+ names.add(b.replace(/\.instructions\.md$/i, ""));
9610
+ else if (b.toLowerCase().endsWith(".md")) names.add(basename(f, ".md"));
9611
+ }
9612
+ const rules = [...names].sort();
9613
+ return rules.length ? { rules } : {};
9614
+ }
9615
+ if (posixPath.startsWith(".github/skills")) {
9616
+ const skills = await skillNamesFromNativeSkillDir(scan);
9617
+ return skills.length ? { skills } : {};
9618
+ }
9619
+ if (posixPath.startsWith(".github/agents")) {
9620
+ const files = await readDirRecursive(scan);
9621
+ const agents = [
9622
+ ...new Set(
9623
+ files.filter((f) => f.toLowerCase().endsWith(".agent.md")).map((f) => basename(f, ".agent.md"))
9624
+ )
9625
+ ].sort();
9626
+ return agents.length ? { agents } : {};
9627
+ }
9628
+ return {};
9629
+ }
9630
+ var init_native_path_pick_infer_copilot = __esm({
9631
+ "src/install/native/native-path-pick-infer-copilot.ts"() {
9632
+ init_fs();
9633
+ init_constants29();
9634
+ init_native_skill_scan();
9635
+ }
9636
+ });
9474
9637
  function pruneUndefined3(record) {
9475
9638
  for (const key of Object.keys(record)) {
9476
9639
  if (record[key] === void 0) delete record[key];
@@ -9760,6 +9923,7 @@ var init_copilot2 = __esm({
9760
9923
  init_generator11();
9761
9924
  init_constants29();
9762
9925
  init_importer10();
9926
+ init_native_path_pick_infer_copilot();
9763
9927
  init_import_mappers4();
9764
9928
  init_linter10();
9765
9929
  init_import_map_builders();
@@ -9974,7 +10138,8 @@ var init_copilot2 = __esm({
9974
10138
  ".github/skills",
9975
10139
  ".github/agents",
9976
10140
  ".github/hooks"
9977
- ]
10141
+ ],
10142
+ nativeInstall: { inferPick: inferCopilotPickFromPath }
9978
10143
  };
9979
10144
  }
9980
10145
  });
@@ -11487,6 +11652,23 @@ var init_cursor2 = __esm({
11487
11652
  },
11488
11653
  buildImportPaths: buildCursorImportPaths,
11489
11654
  detectionPaths: [".cursor/rules", ".cursor/mcp.json"],
11655
+ nativeInstall: {
11656
+ pickPaths: [
11657
+ { prefix: ".cursor/rules", feature: "rules", strategy: { kind: "basename", suffix: ".mdc" } },
11658
+ {
11659
+ prefix: ".cursor/commands",
11660
+ feature: "commands",
11661
+ strategy: { kind: "basename", suffix: ".md" }
11662
+ },
11663
+ {
11664
+ prefix: ".cursor/agents",
11665
+ feature: "agents",
11666
+ strategy: { kind: "basename", suffix: ".md" }
11667
+ },
11668
+ { prefix: ".cursor/skills", feature: "skills", strategy: { kind: "skillDir" } }
11669
+ ],
11670
+ dialectHints: [{ frontmatterKey: "alwaysApply" }]
11671
+ },
11490
11672
  preservesManualActivation: true
11491
11673
  };
11492
11674
  }
@@ -12248,18 +12430,18 @@ function mapHookEvent2(event) {
12248
12430
  return null;
12249
12431
  }
12250
12432
  }
12251
- function generateGeminiSettingsFiles(canonical) {
12433
+ function generateGeminiSettingsFiles(canonical, enabledFeatures) {
12252
12434
  const settings = {};
12253
12435
  let hasAnyNativeSettings = false;
12254
- if (canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
12436
+ if (enabledFeatures.has("mcp") && canonical.mcp && Object.keys(canonical.mcp.mcpServers).length > 0) {
12255
12437
  settings.mcpServers = canonical.mcp.mcpServers;
12256
12438
  hasAnyNativeSettings = true;
12257
12439
  }
12258
- if (canonical.agents.length > 0) {
12440
+ if (enabledFeatures.has("agents") && canonical.agents.length > 0) {
12259
12441
  settings.experimental = { enableAgents: true };
12260
12442
  hasAnyNativeSettings = true;
12261
12443
  }
12262
- if (canonical.hooks) {
12444
+ if (enabledFeatures.has("hooks") && canonical.hooks) {
12263
12445
  const hookEntries = Object.entries(canonical.hooks).flatMap(([event, entries]) => {
12264
12446
  const mappedEvent = mapHookEvent2(event);
12265
12447
  if (!mappedEvent || !Array.isArray(entries)) return [];
@@ -12875,6 +13057,36 @@ var init_importer15 = __esm({
12875
13057
  init_importer_skills_agents();
12876
13058
  }
12877
13059
  });
13060
+ function isUnderGeminiCommands(pathInRepoPosix) {
13061
+ const p = pathInRepoPosix.replace(/^\/+|\/+$/g, "");
13062
+ return p === ".gemini/commands" || p.startsWith(".gemini/commands/");
13063
+ }
13064
+ async function inferGeminiCommandNamesFromFiles(repoRoot, pathInRepoPosix) {
13065
+ const commandsRoot = join(repoRoot, ...GEMINI_COMMANDS_DIR.split("/"));
13066
+ const scanDir = join(repoRoot, ...pathInRepoPosix.split("/"));
13067
+ const files = await readDirRecursive(scanDir);
13068
+ const names = [];
13069
+ for (const f of files) {
13070
+ if (!/\.(toml|md)$/i.test(f)) continue;
13071
+ const rel2 = relative(commandsRoot, f).replace(/\\/g, "/");
13072
+ if (rel2.startsWith("..") || rel2 === "") continue;
13073
+ const noExt = rel2.replace(/\.(toml|md)$/i, "");
13074
+ const name = noExt.split("/").filter(Boolean).join(":");
13075
+ if (name) names.push(name);
13076
+ }
13077
+ return [...new Set(names)].sort();
13078
+ }
13079
+ async function inferGeminiPick(repoRoot, posixPath) {
13080
+ if (!isUnderGeminiCommands(posixPath)) return {};
13081
+ const commands = await inferGeminiCommandNamesFromFiles(repoRoot, posixPath);
13082
+ return commands.length ? { commands } : {};
13083
+ }
13084
+ var init_gemini_install_commands = __esm({
13085
+ "src/install/native/gemini-install-commands.ts"() {
13086
+ init_fs();
13087
+ init_constants30();
13088
+ }
13089
+ });
12878
13090
  async function mapGeminiRuleFile(relativePath, destDir, normalizeTo) {
12879
13091
  const relativeMdPath = relativePath.replace(/\\/g, "/");
12880
13092
  const destPath = join(destDir, relativeMdPath);
@@ -12986,12 +13198,12 @@ var init_lint12 = __esm({
12986
13198
  });
12987
13199
 
12988
13200
  // src/targets/gemini-cli/scoped-settings-emit.ts
12989
- function emitScopedGeminiSettings(canonical, scope) {
13201
+ function emitScopedGeminiSettings(canonical, scope, enabledFeatures) {
12990
13202
  if (scope === "project") {
12991
13203
  const caps = getTargetCapabilities("gemini-cli", scope);
12992
13204
  if (caps?.ignore.flavor !== "settings-embedded") return [];
12993
13205
  }
12994
- return generateGeminiSettingsFiles(canonical);
13206
+ return generateGeminiSettingsFiles(canonical, enabledFeatures);
12995
13207
  }
12996
13208
  var init_scoped_settings_emit = __esm({
12997
13209
  "src/targets/gemini-cli/scoped-settings-emit.ts"() {
@@ -13009,6 +13221,7 @@ var init_gemini_cli2 = __esm({
13009
13221
  init_policies_generator();
13010
13222
  init_constants30();
13011
13223
  init_importer15();
13224
+ init_gemini_install_commands();
13012
13225
  init_import_mappers6();
13013
13226
  init_linter15();
13014
13227
  init_import_map_builders();
@@ -13204,6 +13417,7 @@ var init_gemini_cli2 = __esm({
13204
13417
  },
13205
13418
  buildImportPaths: buildGeminiCliImportPaths,
13206
13419
  detectionPaths: ["GEMINI.md", ".gemini"],
13420
+ nativeInstall: { inferPick: inferGeminiPick },
13207
13421
  conversionDefaults: { agentsToSkills: false }
13208
13422
  };
13209
13423
  }
@@ -14045,7 +14259,23 @@ var init_junie2 = __esm({
14045
14259
  ".junie/skills",
14046
14260
  ".junie/mcp/mcp.json",
14047
14261
  ".aiignore"
14048
- ]
14262
+ ],
14263
+ nativeInstall: {
14264
+ pickPaths: [
14265
+ {
14266
+ prefix: ".junie/commands",
14267
+ feature: "commands",
14268
+ strategy: { kind: "basename", suffix: ".md" }
14269
+ },
14270
+ { prefix: ".junie/rules", feature: "rules", strategy: { kind: "basename", suffix: ".md" } },
14271
+ {
14272
+ prefix: ".junie/agents",
14273
+ feature: "agents",
14274
+ strategy: { kind: "basename", suffix: ".md" }
14275
+ },
14276
+ { prefix: ".junie/skills", feature: "skills", strategy: { kind: "skillDir" } }
14277
+ ]
14278
+ }
14049
14279
  };
14050
14280
  }
14051
14281
  });
@@ -15900,9 +16130,18 @@ function generateIgnore12(canonical) {
15900
16130
  if (!canonical.ignore || canonical.ignore.length === 0) return [];
15901
16131
  return [{ path: QWEN_IGNORE, content: canonical.ignore.join("\n") }];
15902
16132
  }
16133
+ function renderQwenGlobalInstructions(canonical) {
16134
+ const root = canonical.rules.find((rule) => rule.root);
16135
+ const nonRootRules = canonical.rules.filter((rule) => {
16136
+ if (rule.root) return false;
16137
+ return rule.targets.length === 0 || rule.targets.includes(QWEN_CODE_TARGET);
16138
+ });
16139
+ return appendEmbeddedRulesBlock(root?.body.trim() ?? "", nonRootRules);
16140
+ }
15903
16141
  var init_generator26 = __esm({
15904
16142
  "src/targets/qwen-code/generator.ts"() {
15905
16143
  init_markdown();
16144
+ init_managed_blocks();
15906
16145
  init_constants21();
15907
16146
  }
15908
16147
  });
@@ -15985,6 +16224,7 @@ var init_qwen_code2 = __esm({
15985
16224
  };
15986
16225
  globalLayout22 = {
15987
16226
  rootInstructionPath: QWEN_GLOBAL_ROOT,
16227
+ renderPrimaryRootInstruction: renderQwenGlobalInstructions,
15988
16228
  skillDir: QWEN_GLOBAL_SKILLS_DIR,
15989
16229
  managedOutputs: {
15990
16230
  dirs: [QWEN_GLOBAL_COMMANDS_DIR, QWEN_GLOBAL_AGENTS_DIR, QWEN_GLOBAL_SKILLS_DIR],
@@ -18385,6 +18625,16 @@ var init_windsurf2 = __esm({
18385
18625
  },
18386
18626
  buildImportPaths: buildWindsurfImportPaths,
18387
18627
  detectionPaths: [".windsurfrules", ".windsurf"],
18628
+ nativeInstall: {
18629
+ pickPaths: [
18630
+ {
18631
+ prefix: ".windsurf/rules",
18632
+ feature: "rules",
18633
+ strategy: { kind: "basename", suffix: ".md" }
18634
+ }
18635
+ ],
18636
+ dialectHints: [{ frontmatterKey: "trigger" }]
18637
+ },
18388
18638
  conversionDefaults: { agentsToSkills: true }
18389
18639
  };
18390
18640
  }
@@ -18659,7 +18909,8 @@ var init_zed2 = __esm({
18659
18909
  markAsRoot: true
18660
18910
  }
18661
18911
  },
18662
- emitScopedSettings(canonical, _scope) {
18912
+ emitScopedSettings(canonical, _scope, enabledFeatures) {
18913
+ if (!enabledFeatures.has("mcp")) return [];
18663
18914
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
18664
18915
  const contextServers = {};
18665
18916
  for (const [name, server] of Object.entries(canonical.mcp.mcpServers)) {
@@ -18714,7 +18965,10 @@ function getBuiltinTargetDefinition(target31) {
18714
18965
  function getTargetCapabilities(target31, scope = "project") {
18715
18966
  const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
18716
18967
  if (!descriptor31) return void 0;
18717
- const raw = scope === "global" ? descriptor31.globalSupport?.capabilities ?? descriptor31.capabilities : descriptor31.capabilities;
18968
+ if (scope === "global" && !descriptor31.globalSupport) {
18969
+ return normalizeTargetCapabilities(ALL_NONE_CAPABILITIES);
18970
+ }
18971
+ const raw = scope === "global" ? descriptor31.globalSupport.capabilities : descriptor31.capabilities;
18718
18972
  return normalizeTargetCapabilities(raw);
18719
18973
  }
18720
18974
  function getTargetDetectionPaths(target31, scope = "project") {
@@ -18770,6 +19024,7 @@ function isConversionUpgrading(descriptor31, feature, config, scope) {
18770
19024
  function getEffectiveTargetSupportLevel(target31, feature, config, scope = "project") {
18771
19025
  const baseLevel = getTargetCapabilities(target31, scope)?.[feature]?.level ?? "none";
18772
19026
  const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
19027
+ if (scope === "global" && descriptor31 && !descriptor31.globalSupport) return "none";
18773
19028
  if (baseLevel === "none" && isConversionUpgrading(descriptor31, feature, config, scope)) {
18774
19029
  return "embedded";
18775
19030
  }
@@ -18783,7 +19038,7 @@ function resolveTargetFeatureGenerator(target31, feature, config, scope = "proje
18783
19038
  const pick = PICK_FEATURE_GENERATOR[feature];
18784
19039
  return pick === null ? void 0 : pick(descriptor31.generators);
18785
19040
  }
18786
- var BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
19041
+ var ALL_NONE_CAPABILITIES, BUILTIN_TARGETS, _builtinTargetsMap, PICK_FEATURE_GENERATOR;
18787
19042
  var init_builtin_targets = __esm({
18788
19043
  "src/targets/catalog/builtin-targets.ts"() {
18789
19044
  init_conversions();
@@ -18821,6 +19076,17 @@ var init_builtin_targets = __esm({
18821
19076
  init_warp2();
18822
19077
  init_windsurf2();
18823
19078
  init_zed2();
19079
+ ALL_NONE_CAPABILITIES = {
19080
+ rules: "none",
19081
+ additionalRules: "none",
19082
+ commands: "none",
19083
+ agents: "none",
19084
+ skills: "none",
19085
+ mcp: "none",
19086
+ hooks: "none",
19087
+ ignore: "none",
19088
+ permissions: "none"
19089
+ };
18824
19090
  BUILTIN_TARGETS = [
18825
19091
  descriptor,
18826
19092
  descriptor2,
@@ -19251,6 +19517,23 @@ function rewriteGeneratedReferences(results, canonical, config, projectRoot, sco
19251
19517
  init_path_helpers();
19252
19518
  init_link_rebaser_helpers();
19253
19519
 
19520
+ // src/utils/output/color.ts
19521
+ function noColorRequested() {
19522
+ const value = process.env.NO_COLOR;
19523
+ return value !== void 0 && value !== "";
19524
+ }
19525
+ function forceColorRequested() {
19526
+ const value = process.env.FORCE_COLOR;
19527
+ if (value === void 0) return void 0;
19528
+ return value !== "0" && value !== "false";
19529
+ }
19530
+ function colorEnabled(stream = process.stdout) {
19531
+ const forced = forceColorRequested();
19532
+ if (forced !== void 0) return forced;
19533
+ if (noColorRequested()) return false;
19534
+ return stream.isTTY === true;
19535
+ }
19536
+
19254
19537
  // src/utils/output/logger.ts
19255
19538
  var C = {
19256
19539
  green: "\x1B[32m",
@@ -19259,16 +19542,14 @@ var C = {
19259
19542
  cyan: "\x1B[36m",
19260
19543
  reset: "\x1B[0m"
19261
19544
  };
19262
- function out(text) {
19263
- {
19264
- process.stdout.write(text);
19265
- }
19545
+ function outStream() {
19546
+ return process.stdout;
19266
19547
  }
19267
- function noColor() {
19268
- return process.env.NO_COLOR !== void 0 && process.env.NO_COLOR !== "";
19548
+ function out(text) {
19549
+ outStream().write(text);
19269
19550
  }
19270
- function c(code, text) {
19271
- return noColor() ? text : `${code}${text}${C.reset}`;
19551
+ function c(code, text, stream) {
19552
+ return colorEnabled(stream) ? `${code}${text}${C.reset}` : text;
19272
19553
  }
19273
19554
  function pad(str, width) {
19274
19555
  const len = [...str].length;
@@ -19276,20 +19557,20 @@ function pad(str, width) {
19276
19557
  }
19277
19558
  var logger = {
19278
19559
  info(msg) {
19279
- out(c(C.cyan, msg) + "\n");
19560
+ out(c(C.cyan, msg, outStream()) + "\n");
19280
19561
  },
19281
19562
  warn(msg) {
19282
- process.stderr.write(c(C.yellow, "\u26A0 ") + msg + "\n");
19563
+ process.stderr.write(c(C.yellow, "\u26A0 ", process.stderr) + msg + "\n");
19283
19564
  },
19284
19565
  error(msg) {
19285
- process.stderr.write(c(C.red, "\u2717 ") + msg + "\n");
19566
+ process.stderr.write(c(C.red, "\u2717 ", process.stderr) + msg + "\n");
19286
19567
  },
19287
19568
  success(msg) {
19288
- out(c(C.green, "\u2713 ") + msg + "\n");
19569
+ out(c(C.green, "\u2713 ", outStream()) + msg + "\n");
19289
19570
  },
19290
19571
  debug(msg) {
19291
19572
  if (process.env.AGENTSMESH_DEBUG === "1") {
19292
- out(c(C.cyan, "[debug] ") + msg + "\n");
19573
+ out(c(C.cyan, "[debug] ", outStream()) + msg + "\n");
19293
19574
  }
19294
19575
  },
19295
19576
  table(rows) {
@@ -19784,12 +20065,12 @@ async function generateHooksFeature(results, targets, canonical, projectRoot, sc
19784
20065
  }
19785
20066
  }
19786
20067
  }
19787
- async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope) {
20068
+ async function generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope, enabledFeatures) {
19788
20069
  for (const target31 of targets) {
19789
20070
  const descriptor31 = getBuiltinTargetDefinition(target31) ?? getDescriptor(target31);
19790
20071
  const emit = descriptor31?.emitScopedSettings;
19791
20072
  if (!emit) continue;
19792
- const outputs = emit(canonical, scope);
20073
+ const outputs = emit(canonical, scope, enabledFeatures);
19793
20074
  if (outputs.length === 0) continue;
19794
20075
  for (const out2 of outputs) {
19795
20076
  await emitGeneratedOutput(results, target31, out2, projectRoot, scope, {
@@ -19890,7 +20171,14 @@ async function generate(ctx) {
19890
20171
  }
19891
20172
  }
19892
20173
  if (hasMcp || hasIgnore || hasHooks || hasAgents || hasPermissions) {
19893
- await generateScopedSettingsFeature(results, targets, canonical, projectRoot, scope);
20174
+ await generateScopedSettingsFeature(
20175
+ results,
20176
+ targets,
20177
+ canonical,
20178
+ projectRoot,
20179
+ scope,
20180
+ enabledFeatures
20181
+ );
19894
20182
  }
19895
20183
  const decoratedResults = decoratePrimaryRootInstructions(results, canonical, scope);
19896
20184
  const sharedPaths = computeSharedRootInstructionPaths(decoratedResults, scope);
@@ -20214,7 +20502,7 @@ async function fetchGitRemoteExtend(parsed, extendName, options, cacheDir, build
20214
20502
  await cloneRepo(resolveCloneUrl(parsed), stagedRepoDir);
20215
20503
  if (parsed.ref) await checkoutRef(stagedRepoDir, parsed.ref);
20216
20504
  await rm(cacheRoot, { recursive: true, force: true });
20217
- await rename(stagedRoot, cacheRoot);
20505
+ await renameWithRetry(stagedRoot, cacheRoot);
20218
20506
  return readCachedRepo(cacheRepoDir);
20219
20507
  } catch (err) {
20220
20508
  await rm(stagedRoot, { recursive: true, force: true });
@@ -22565,6 +22853,981 @@ function lintRuleScopeInversion(input) {
22565
22853
  }
22566
22854
  return out2;
22567
22855
  }
22856
+ var CURRENT_GRAPH_VERSION = 1;
22857
+ var IdSchema = z.string().regex(/^[a-z0-9-]+$/, "id must be kebab-case");
22858
+ var DateSchema = z.string().regex(
22859
+ /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z?)?$/,
22860
+ "createdAt must be ISO-8601 date or datetime"
22861
+ );
22862
+ var TopicSchema = z.object({
22863
+ summary: z.string().min(1, "topic summary must not be empty")
22864
+ }).strict();
22865
+ var TriggerKindSchema = z.enum(["file_glob", "command_pattern", "keyword"]);
22866
+ var TriggerSchema = z.object({
22867
+ kind: TriggerKindSchema,
22868
+ pattern: z.string().min(1, "trigger pattern must not be empty")
22869
+ }).strict();
22870
+ var LessonStatusSchema = z.enum(["active", "deprecated", "superseded"]);
22871
+ var LessonSchema = z.object({
22872
+ rule: z.string().min(1, "lesson rule must not be empty"),
22873
+ rationale: z.string().min(1).optional(),
22874
+ topics: z.array(IdSchema).min(1, "lesson must reference at least one topic"),
22875
+ triggers: z.array(IdSchema),
22876
+ evidence: z.array(z.string().min(1)),
22877
+ status: LessonStatusSchema,
22878
+ supersededBy: IdSchema.optional(),
22879
+ createdAt: DateSchema
22880
+ }).strict();
22881
+ var LessonsGraphSchema = z.object({
22882
+ version: z.literal(CURRENT_GRAPH_VERSION),
22883
+ lessons: z.record(IdSchema, LessonSchema),
22884
+ topics: z.record(IdSchema, TopicSchema),
22885
+ triggers: z.record(IdSchema, TriggerSchema)
22886
+ }).strict();
22887
+ function parseGraph(raw) {
22888
+ return LessonsGraphSchema.parse(raw);
22889
+ }
22890
+
22891
+ // src/lessons/graph-store.ts
22892
+ var GRAPH_REL_PATH = ".agentsmesh/lessons/lessons.json";
22893
+ function graphFilePath(projectRoot) {
22894
+ return resolve(projectRoot, GRAPH_REL_PATH);
22895
+ }
22896
+ function loadLessonsGraph(projectRoot) {
22897
+ const raw = readFileSync(graphFilePath(projectRoot), "utf8");
22898
+ return parseGraph(JSON.parse(raw));
22899
+ }
22900
+ var BASE_REL = ".agentsmesh/lessons";
22901
+ function lessonsPaths(projectRoot) {
22902
+ const base = join(projectRoot, BASE_REL);
22903
+ return {
22904
+ base,
22905
+ graph: join(base, "lessons.json"),
22906
+ config: join(base, "config.json"),
22907
+ journal: join(base, "journal.md"),
22908
+ index: join(base, "index.yaml"),
22909
+ topicsDir: join(base, "topics")
22910
+ };
22911
+ }
22912
+ function toRelPath(projectRoot, absolute) {
22913
+ return relative(projectRoot, absolute).split(sep).join("/");
22914
+ }
22915
+ var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
22916
+ var MAX_FILES = 2e5;
22917
+ function listProjectFiles(projectRoot) {
22918
+ const out2 = /* @__PURE__ */ new Set();
22919
+ try {
22920
+ const stack = [projectRoot];
22921
+ while (stack.length > 0) {
22922
+ const dir = stack.pop();
22923
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
22924
+ if (entry.isDirectory()) {
22925
+ if (!SKIP_DIRS.has(entry.name)) stack.push(join(dir, entry.name));
22926
+ } else if (entry.isFile()) {
22927
+ out2.add(toRelPath(projectRoot, join(dir, entry.name)));
22928
+ if (out2.size > MAX_FILES) return out2;
22929
+ }
22930
+ }
22931
+ }
22932
+ } catch {
22933
+ return null;
22934
+ }
22935
+ return out2;
22936
+ }
22937
+
22938
+ // src/lessons/validate-checks.ts
22939
+ function collectDanglingRefs(graph, findings) {
22940
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
22941
+ for (const topicId of lesson.topics) {
22942
+ if (graph.topics[topicId] === void 0) {
22943
+ findings.push({
22944
+ level: "error",
22945
+ code: "DANGLING_TOPIC",
22946
+ message: `Lesson "${lessonId}" references unknown topic "${topicId}".`,
22947
+ lessonId,
22948
+ topicId
22949
+ });
22950
+ }
22951
+ }
22952
+ for (const triggerId of lesson.triggers) {
22953
+ if (graph.triggers[triggerId] === void 0) {
22954
+ findings.push({
22955
+ level: "error",
22956
+ code: "DANGLING_TRIGGER",
22957
+ message: `Lesson "${lessonId}" references unknown trigger "${triggerId}".`,
22958
+ lessonId,
22959
+ triggerId
22960
+ });
22961
+ }
22962
+ }
22963
+ if (lesson.supersededBy !== void 0 && graph.lessons[lesson.supersededBy] === void 0) {
22964
+ findings.push({
22965
+ level: "error",
22966
+ code: "DANGLING_SUPERSEDER",
22967
+ message: `Lesson "${lessonId}" supersededBy unknown lesson "${lesson.supersededBy}".`,
22968
+ lessonId
22969
+ });
22970
+ }
22971
+ }
22972
+ }
22973
+ function collectDuplicateRefs(graph, findings) {
22974
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
22975
+ for (const topicId of firstDuplicates(lesson.topics)) {
22976
+ findings.push({
22977
+ level: "error",
22978
+ code: "DUPLICATE_TOPIC_REF",
22979
+ message: `Lesson "${lessonId}" references topic "${topicId}" more than once.`,
22980
+ lessonId,
22981
+ topicId
22982
+ });
22983
+ }
22984
+ for (const triggerId of firstDuplicates(lesson.triggers)) {
22985
+ findings.push({
22986
+ level: "error",
22987
+ code: "DUPLICATE_TRIGGER_REF",
22988
+ message: `Lesson "${lessonId}" references trigger "${triggerId}" more than once.`,
22989
+ lessonId,
22990
+ triggerId
22991
+ });
22992
+ }
22993
+ }
22994
+ }
22995
+ function firstDuplicates(ids) {
22996
+ const seen = /* @__PURE__ */ new Set();
22997
+ const dup = /* @__PURE__ */ new Set();
22998
+ for (const id of ids) {
22999
+ if (seen.has(id)) dup.add(id);
23000
+ else seen.add(id);
23001
+ }
23002
+ return [...dup];
23003
+ }
23004
+ function collectStatusInvariants(graph, findings) {
23005
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23006
+ if (lesson.status === "superseded" && lesson.supersededBy === void 0) {
23007
+ findings.push({
23008
+ level: "error",
23009
+ code: "SUPERSEDED_WITHOUT_TARGET",
23010
+ message: `Lesson "${lessonId}" has status "superseded" but no supersededBy target.`,
23011
+ lessonId
23012
+ });
23013
+ }
23014
+ if (lesson.status === "active" && lesson.supersededBy !== void 0) {
23015
+ findings.push({
23016
+ level: "error",
23017
+ code: "ACTIVE_WITH_SUPERSEDER",
23018
+ message: `Lesson "${lessonId}" has status "active" but declares supersededBy.`,
23019
+ lessonId
23020
+ });
23021
+ }
23022
+ }
23023
+ }
23024
+ function collectLifecycleInvariants(graph, findings) {
23025
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23026
+ if (lesson.supersededBy === void 0) continue;
23027
+ if (lesson.supersededBy === lessonId) {
23028
+ findings.push({
23029
+ level: "error",
23030
+ code: "SELF_SUPERSEDED",
23031
+ message: `Lesson "${lessonId}" is superseded by itself.`,
23032
+ lessonId
23033
+ });
23034
+ continue;
23035
+ }
23036
+ const target31 = graph.lessons[lesson.supersededBy];
23037
+ if (target31 !== void 0 && target31.status !== "active") {
23038
+ findings.push({
23039
+ level: "error",
23040
+ code: "INACTIVE_SUPERSEDER",
23041
+ message: `Lesson "${lessonId}" is superseded by "${lesson.supersededBy}", which is itself ${target31.status} \u2014 the chain dead-ends with no live replacement.`,
23042
+ lessonId
23043
+ });
23044
+ }
23045
+ }
23046
+ collectSupersedeCycles(graph, findings);
23047
+ }
23048
+ function collectSupersedeCycles(graph, findings) {
23049
+ const reported = /* @__PURE__ */ new Set();
23050
+ for (const startId of Object.keys(graph.lessons)) {
23051
+ const seen = /* @__PURE__ */ new Set();
23052
+ let cur = startId;
23053
+ while (cur !== void 0) {
23054
+ if (seen.has(cur)) {
23055
+ if (cur !== startId || reported.has(cur)) break;
23056
+ reported.add(cur);
23057
+ findings.push({
23058
+ level: "error",
23059
+ code: "SUPERSEDE_CYCLE",
23060
+ message: `Lesson "${startId}" is part of a supersededBy cycle.`,
23061
+ lessonId: startId
23062
+ });
23063
+ break;
23064
+ }
23065
+ seen.add(cur);
23066
+ const next = graph.lessons[cur]?.supersededBy;
23067
+ if (next === cur) break;
23068
+ cur = next;
23069
+ }
23070
+ }
23071
+ }
23072
+ function collectReachability(graph, findings) {
23073
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23074
+ if (lesson.status === "active" && lesson.triggers.length === 0) {
23075
+ findings.push({
23076
+ level: "warning",
23077
+ code: "UNREACHABLE_LESSON",
23078
+ message: `Active lesson "${lessonId}" has no triggers and can never be recalled.`,
23079
+ lessonId
23080
+ });
23081
+ }
23082
+ }
23083
+ }
23084
+ function collectOrphans(graph, findings) {
23085
+ const referencedTopics = /* @__PURE__ */ new Set();
23086
+ const referencedTriggers = /* @__PURE__ */ new Set();
23087
+ for (const lesson of Object.values(graph.lessons)) {
23088
+ for (const t of lesson.topics) referencedTopics.add(t);
23089
+ for (const t of lesson.triggers) referencedTriggers.add(t);
23090
+ }
23091
+ for (const topicId of Object.keys(graph.topics)) {
23092
+ if (!referencedTopics.has(topicId)) {
23093
+ findings.push({
23094
+ level: "warning",
23095
+ code: "ORPHAN_TOPIC",
23096
+ message: `Topic "${topicId}" is not referenced by any lesson.`,
23097
+ topicId
23098
+ });
23099
+ }
23100
+ }
23101
+ for (const triggerId of Object.keys(graph.triggers)) {
23102
+ if (!referencedTriggers.has(triggerId)) {
23103
+ findings.push({
23104
+ level: "warning",
23105
+ code: "ORPHAN_TRIGGER",
23106
+ message: `Trigger "${triggerId}" is not referenced by any lesson.`,
23107
+ triggerId
23108
+ });
23109
+ }
23110
+ }
23111
+ }
23112
+
23113
+ // src/lessons/ranking-text.ts
23114
+ var STOP = /* @__PURE__ */ new Set([
23115
+ "the",
23116
+ "a",
23117
+ "an",
23118
+ "to",
23119
+ "of",
23120
+ "in",
23121
+ "and",
23122
+ "or",
23123
+ "for",
23124
+ "is",
23125
+ "on",
23126
+ "at",
23127
+ "with",
23128
+ "be",
23129
+ "as",
23130
+ "it",
23131
+ "that",
23132
+ "this",
23133
+ "its",
23134
+ "must"
23135
+ ]);
23136
+ function tokenize(text) {
23137
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
23138
+ }
23139
+
23140
+ // src/lessons/keyword-signal.ts
23141
+ var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
23142
+ function isLowSignalKeyword(pattern) {
23143
+ return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
23144
+ }
23145
+ function splitRawTokens(pattern) {
23146
+ return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
23147
+ }
23148
+ function keywordNeedleLosesTokens(pattern) {
23149
+ const raw = splitRawTokens(pattern);
23150
+ if (raw.length < 2) return false;
23151
+ return tokenize(pattern).length !== raw.length;
23152
+ }
23153
+
23154
+ // src/lessons/regex-linear/nfa-compile.ts
23155
+ var MAX_NFA_STATES = 2e3;
23156
+ var Builder = class {
23157
+ states = [];
23158
+ alloc() {
23159
+ if (this.states.length >= MAX_NFA_STATES) {
23160
+ throw new Error(`NFA state limit exceeded (${MAX_NFA_STATES}); pattern expands too large`);
23161
+ }
23162
+ this.states.push({ eps: [], asserts: [], chars: [] });
23163
+ return this.states.length - 1;
23164
+ }
23165
+ };
23166
+ function isNonLineTerminator(c2) {
23167
+ return c2 !== "\n" && c2 !== "\r" && c2 !== "\u2028" && c2 !== "\u2029";
23168
+ }
23169
+ function compileNode(b, node) {
23170
+ switch (node.k) {
23171
+ case "empty":
23172
+ case "assert": {
23173
+ const s = b.alloc();
23174
+ const e = b.alloc();
23175
+ if (node.k === "assert") b.states[s].asserts.push({ kind: node.kind, target: e });
23176
+ else b.states[s].eps.push(e);
23177
+ return { start: s, end: e };
23178
+ }
23179
+ case "char":
23180
+ case "any":
23181
+ case "class": {
23182
+ const s = b.alloc();
23183
+ const e = b.alloc();
23184
+ const test = node.k === "char" ? (c2) => c2 === node.ch : node.k === "any" ? isNonLineTerminator : node.test;
23185
+ b.states[s].chars.push({ test, target: e });
23186
+ return { start: s, end: e };
23187
+ }
23188
+ case "concat": {
23189
+ if (node.items.length === 0) return compileNode(b, { k: "empty" });
23190
+ let first = null;
23191
+ let prevEnd = -1;
23192
+ for (const item of node.items) {
23193
+ const frag = compileNode(b, item);
23194
+ if (first === null) first = frag;
23195
+ else b.states[prevEnd].eps.push(frag.start);
23196
+ prevEnd = frag.end;
23197
+ }
23198
+ return { start: first.start, end: prevEnd };
23199
+ }
23200
+ case "alt": {
23201
+ const s = b.alloc();
23202
+ const e = b.alloc();
23203
+ for (const opt of node.opts) {
23204
+ const frag = compileNode(b, opt);
23205
+ b.states[s].eps.push(frag.start);
23206
+ b.states[frag.end].eps.push(e);
23207
+ }
23208
+ return { start: s, end: e };
23209
+ }
23210
+ case "opt": {
23211
+ const s = b.alloc();
23212
+ const e = b.alloc();
23213
+ const frag = compileNode(b, node.node);
23214
+ b.states[s].eps.push(frag.start, e);
23215
+ b.states[frag.end].eps.push(e);
23216
+ return { start: s, end: e };
23217
+ }
23218
+ case "star": {
23219
+ const s = b.alloc();
23220
+ const e = b.alloc();
23221
+ const frag = compileNode(b, node.node);
23222
+ b.states[s].eps.push(frag.start, e);
23223
+ b.states[frag.end].eps.push(frag.start, e);
23224
+ return { start: s, end: e };
23225
+ }
23226
+ case "plus": {
23227
+ const e = b.alloc();
23228
+ const frag = compileNode(b, node.node);
23229
+ b.states[frag.end].eps.push(frag.start, e);
23230
+ return { start: frag.start, end: e };
23231
+ }
23232
+ }
23233
+ }
23234
+ function compileNfa(ast) {
23235
+ const b = new Builder();
23236
+ const { start, end } = compileNode(b, ast);
23237
+ return { states: b.states, start, accept: end };
23238
+ }
23239
+
23240
+ // src/lessons/regex-linear/nfa.ts
23241
+ function wordBoundary(input, pos) {
23242
+ const before = pos > 0 && /[A-Za-z0-9_]/.test(input[pos - 1]);
23243
+ const after = pos < input.length && /[A-Za-z0-9_]/.test(input[pos]);
23244
+ return before !== after;
23245
+ }
23246
+ function assertHolds(kind, input, pos) {
23247
+ switch (kind) {
23248
+ case "start":
23249
+ return pos === 0;
23250
+ case "end":
23251
+ return pos === input.length;
23252
+ case "wordB":
23253
+ return wordBoundary(input, pos);
23254
+ case "nonWordB":
23255
+ return !wordBoundary(input, pos);
23256
+ }
23257
+ }
23258
+ function buildMatcher(ast) {
23259
+ const { states, start, accept } = compileNfa(ast);
23260
+ const closure = (set, idx, input, pos, budget) => {
23261
+ const stack = [idx];
23262
+ while (stack.length > 0) {
23263
+ if (budget.remaining <= 0) return;
23264
+ const cur = stack.pop();
23265
+ if (set.has(cur)) continue;
23266
+ set.add(cur);
23267
+ budget.remaining -= 1;
23268
+ for (const t of states[cur].eps) if (!set.has(t)) stack.push(t);
23269
+ for (const a of states[cur].asserts) {
23270
+ if (assertHolds(a.kind, input, pos) && !set.has(a.target)) stack.push(a.target);
23271
+ }
23272
+ }
23273
+ };
23274
+ return {
23275
+ // The input is matched in full (no truncation — truncation would miss suffix
23276
+ // matches and let `$` falsely match an invented endpoint). Work is bounded by
23277
+ // the shared budget instead: when it runs out we report a safe non-match.
23278
+ test(input, budget) {
23279
+ const b = budget ?? { remaining: Number.POSITIVE_INFINITY };
23280
+ if (b.remaining <= 0) return false;
23281
+ let current = /* @__PURE__ */ new Set();
23282
+ for (let pos = 0; pos <= input.length; pos += 1) {
23283
+ closure(current, start, input, pos, b);
23284
+ if (current.has(accept)) return true;
23285
+ if (b.remaining <= 0) return false;
23286
+ if (pos === input.length) break;
23287
+ const ch = input[pos];
23288
+ const next = /* @__PURE__ */ new Set();
23289
+ for (const s of current) {
23290
+ b.remaining -= 1;
23291
+ for (const t of states[s].chars) {
23292
+ if (t.test(ch)) closure(next, t.target, input, pos + 1, b);
23293
+ }
23294
+ }
23295
+ current = next;
23296
+ }
23297
+ return current.has(accept);
23298
+ }
23299
+ };
23300
+ }
23301
+
23302
+ // src/lessons/regex-linear/ast.ts
23303
+ var UnsupportedRegexError = class extends Error {
23304
+ constructor(message) {
23305
+ super(message);
23306
+ this.name = "UnsupportedRegexError";
23307
+ }
23308
+ };
23309
+
23310
+ // src/lessons/regex-linear/parse-helpers.ts
23311
+ var MAX_REPEAT = 1e3;
23312
+ var isWord = (c2) => /[A-Za-z0-9_]/.test(c2);
23313
+ function expandRepeat(atom, min, max) {
23314
+ const items = [];
23315
+ for (let k = 0; k < min; k += 1) items.push(atom);
23316
+ if (max === Infinity) {
23317
+ items.push({ k: "star", node: atom });
23318
+ } else {
23319
+ for (let k = min; k < max; k += 1) items.push({ k: "opt", node: atom });
23320
+ }
23321
+ if (items.length === 0) return { k: "empty" };
23322
+ return items.length === 1 ? items[0] : { k: "concat", items };
23323
+ }
23324
+ function escapeClass(c2) {
23325
+ switch (c2) {
23326
+ case "d":
23327
+ return (x) => x >= "0" && x <= "9";
23328
+ case "D":
23329
+ return (x) => !(x >= "0" && x <= "9");
23330
+ case "w":
23331
+ return isWord;
23332
+ case "W":
23333
+ return (x) => !isWord(x);
23334
+ case "s":
23335
+ return (x) => /\s/.test(x);
23336
+ case "S":
23337
+ return (x) => !/\s/.test(x);
23338
+ default:
23339
+ return null;
23340
+ }
23341
+ }
23342
+ var HEX2 = /^[0-9a-fA-F]{2}$/;
23343
+ var HEX4 = /^[0-9a-fA-F]{4}$/;
23344
+ function readUnicodeEscape(src, i, c2) {
23345
+ if (c2 === "x") {
23346
+ const hex2 = src.slice(i, i + 2);
23347
+ return HEX2.test(hex2) ? { ch: String.fromCharCode(parseInt(hex2, 16)), len: 2 } : { ch: "x", len: 0 };
23348
+ }
23349
+ const hex = src.slice(i, i + 4);
23350
+ return HEX4.test(hex) ? { ch: String.fromCharCode(parseInt(hex, 16)), len: 4 } : { ch: "u", len: 0 };
23351
+ }
23352
+ function readControlEscape(src, i) {
23353
+ const x = src[i];
23354
+ if (x === void 0 || !/[A-Za-z]/.test(x)) {
23355
+ throw new UnsupportedRegexError("\\c must be followed by a letter");
23356
+ }
23357
+ return { ch: String.fromCharCode(x.charCodeAt(0) & 31), len: 1 };
23358
+ }
23359
+ function classEscapeChar(src, i, e) {
23360
+ if (e === "b") return { ch: "\b", len: 0 };
23361
+ if (e === "c") return readControlEscape(src, i);
23362
+ if (e === "x" || e === "u") return readUnicodeEscape(src, i, e);
23363
+ return { ch: escapeLiteral(e), len: 0 };
23364
+ }
23365
+ function escapeLiteral(c2) {
23366
+ switch (c2) {
23367
+ case "t":
23368
+ return " ";
23369
+ case "n":
23370
+ return "\n";
23371
+ case "r":
23372
+ return "\r";
23373
+ case "f":
23374
+ return "\f";
23375
+ case "v":
23376
+ return "\v";
23377
+ case "0":
23378
+ return "\0";
23379
+ default:
23380
+ return c2;
23381
+ }
23382
+ }
23383
+
23384
+ // src/lessons/regex-linear/parse.ts
23385
+ function parseRegex(src) {
23386
+ let i = 0;
23387
+ const peek = () => src[i];
23388
+ const eat = () => src[i++];
23389
+ function parseAlt() {
23390
+ const opts = [parseConcat()];
23391
+ while (peek() === "|") {
23392
+ i += 1;
23393
+ opts.push(parseConcat());
23394
+ }
23395
+ return opts.length === 1 ? opts[0] : { k: "alt", opts };
23396
+ }
23397
+ function parseConcat() {
23398
+ const items = [];
23399
+ while (i < src.length && peek() !== "|" && peek() !== ")") {
23400
+ items.push(parseQuantified());
23401
+ }
23402
+ if (items.length === 0) return { k: "empty" };
23403
+ return items.length === 1 ? items[0] : { k: "concat", items };
23404
+ }
23405
+ function parseQuantified() {
23406
+ const atom = parseAtom();
23407
+ const q = peek();
23408
+ if (q === "*" || q === "+" || q === "?") {
23409
+ i += 1;
23410
+ if (peek() === "?") i += 1;
23411
+ return q === "*" ? { k: "star", node: atom } : q === "+" ? { k: "plus", node: atom } : { k: "opt", node: atom };
23412
+ }
23413
+ if (q === "{") {
23414
+ const repeat = tryParseBrace();
23415
+ if (repeat !== null) return expandRepeat(atom, repeat.min, repeat.max);
23416
+ }
23417
+ return atom;
23418
+ }
23419
+ function tryParseBrace() {
23420
+ const m = /^\{(\d+)(,(\d*)?)?\}/.exec(src.slice(i));
23421
+ if (m === null) return null;
23422
+ i += m[0].length;
23423
+ if (peek() === "?") i += 1;
23424
+ const min = Number(m[1]);
23425
+ const max = m[2] === void 0 ? min : m[3] === "" || m[3] === void 0 ? Infinity : Number(m[3]);
23426
+ if (min > MAX_REPEAT || max !== Infinity && max > MAX_REPEAT) {
23427
+ throw new UnsupportedRegexError(`Repeat count over ${MAX_REPEAT} not supported: {${m[1]}\u2026}`);
23428
+ }
23429
+ return { min, max };
23430
+ }
23431
+ function parseAtom() {
23432
+ const c2 = peek();
23433
+ if (c2 === "(") return parseGroup();
23434
+ if (c2 === "[") return parseClass();
23435
+ if (c2 === "\\") return parseEscape();
23436
+ if (c2 === ".") {
23437
+ i += 1;
23438
+ return { k: "any" };
23439
+ }
23440
+ if (c2 === "^") {
23441
+ i += 1;
23442
+ return { k: "assert", kind: "start" };
23443
+ }
23444
+ if (c2 === "$") {
23445
+ i += 1;
23446
+ return { k: "assert", kind: "end" };
23447
+ }
23448
+ if (c2 === void 0 || c2 === "*" || c2 === "+" || c2 === "?" || c2 === ")") {
23449
+ throw new UnsupportedRegexError(`Unexpected '${c2 ?? "<end>"}' in pattern`);
23450
+ }
23451
+ i += 1;
23452
+ return { k: "char", ch: c2 };
23453
+ }
23454
+ function parseGroup() {
23455
+ i += 1;
23456
+ if (peek() === "?") {
23457
+ const c2 = src[i + 1];
23458
+ if (c2 === "=" || c2 === "!" || c2 === "<") {
23459
+ if (!(c2 === "<" && /[A-Za-z]/.test(src[i + 2] ?? ""))) {
23460
+ throw new UnsupportedRegexError("Lookaround assertions are not supported");
23461
+ }
23462
+ }
23463
+ if (c2 === ":") i += 2;
23464
+ else if (c2 === "<") {
23465
+ i += 2;
23466
+ while (i < src.length && src[i] !== ">") i += 1;
23467
+ i += 1;
23468
+ }
23469
+ }
23470
+ const inner = parseAlt();
23471
+ if (peek() !== ")") throw new UnsupportedRegexError("Unbalanced group");
23472
+ i += 1;
23473
+ return inner;
23474
+ }
23475
+ function parseEscape() {
23476
+ i += 1;
23477
+ const c2 = peek();
23478
+ if (c2 === void 0) throw new UnsupportedRegexError("Trailing backslash");
23479
+ if (/[1-9]/.test(c2) || c2 === "k")
23480
+ throw new UnsupportedRegexError("Backreferences are not supported");
23481
+ i += 1;
23482
+ if (c2 === "b") return { k: "assert", kind: "wordB" };
23483
+ if (c2 === "B") return { k: "assert", kind: "nonWordB" };
23484
+ const cls = escapeClass(c2);
23485
+ if (cls !== null) return { k: "class", test: cls };
23486
+ if (c2 === "x" || c2 === "u" || c2 === "c") {
23487
+ const { ch, len } = c2 === "c" ? readControlEscape(src, i) : readUnicodeEscape(src, i, c2);
23488
+ i += len;
23489
+ return { k: "char", ch };
23490
+ }
23491
+ return { k: "char", ch: escapeLiteral(c2) };
23492
+ }
23493
+ function parseClass() {
23494
+ i += 1;
23495
+ const negate = peek() === "^";
23496
+ if (negate) i += 1;
23497
+ const tests = [];
23498
+ while (i < src.length && peek() !== "]") {
23499
+ tests.push(parseClassMember());
23500
+ }
23501
+ if (peek() !== "]") throw new UnsupportedRegexError("Unterminated character class");
23502
+ i += 1;
23503
+ const base = (c2) => tests.some((t) => t(c2));
23504
+ return { k: "class", test: negate ? (c2) => !base(c2) : base };
23505
+ }
23506
+ function parseClassMember() {
23507
+ let lo;
23508
+ if (peek() === "\\") {
23509
+ i += 1;
23510
+ const e = eat();
23511
+ const cls = escapeClass(e);
23512
+ if (cls !== null) return cls;
23513
+ const r = classEscapeChar(src, i, e);
23514
+ i += r.len;
23515
+ lo = r.ch;
23516
+ } else {
23517
+ lo = eat();
23518
+ }
23519
+ if (peek() === "-" && src[i + 1] !== void 0 && src[i + 1] !== "]") {
23520
+ i += 1;
23521
+ let hi;
23522
+ if (peek() === "\\") {
23523
+ i += 1;
23524
+ const e2 = eat();
23525
+ const r = classEscapeChar(src, i, e2);
23526
+ i += r.len;
23527
+ hi = r.ch;
23528
+ } else {
23529
+ hi = eat();
23530
+ }
23531
+ const a = lo.codePointAt(0);
23532
+ const b = hi.codePointAt(0);
23533
+ return (c2) => {
23534
+ const p = c2.codePointAt(0);
23535
+ return p >= a && p <= b;
23536
+ };
23537
+ }
23538
+ return (c2) => c2 === lo;
23539
+ }
23540
+ const ast = parseAlt();
23541
+ if (i !== src.length) throw new UnsupportedRegexError(`Unexpected '${peek()}' at ${i}`);
23542
+ return ast;
23543
+ }
23544
+
23545
+ // src/lessons/regex-linear/index.ts
23546
+ var cache = /* @__PURE__ */ new Map();
23547
+ function compileLinearMatcher(pattern) {
23548
+ const hit = cache.get(pattern);
23549
+ if (hit !== void 0 || cache.has(pattern)) return hit ?? null;
23550
+ let matcher;
23551
+ try {
23552
+ matcher = buildMatcher(parseRegex(pattern));
23553
+ } catch {
23554
+ matcher = null;
23555
+ }
23556
+ cache.set(pattern, matcher);
23557
+ return matcher;
23558
+ }
23559
+
23560
+ // src/lessons/regex-safety.ts
23561
+ var MAX_PATTERN_LENGTH = 1e3;
23562
+ function isSafeRegexPattern(pattern) {
23563
+ if (pattern.length > MAX_PATTERN_LENGTH) return false;
23564
+ return compileLinearMatcher(pattern) !== null;
23565
+ }
23566
+
23567
+ // src/lessons/validate-quality.ts
23568
+ function collectDuplicateRules(graph, findings) {
23569
+ const byKey = /* @__PURE__ */ new Map();
23570
+ for (const [lessonId, lesson] of Object.entries(graph.lessons)) {
23571
+ if (lesson.status !== "active") continue;
23572
+ const key = normalizeRule(lesson.rule);
23573
+ const bucket = byKey.get(key) ?? [];
23574
+ bucket.push(lessonId);
23575
+ byKey.set(key, bucket);
23576
+ }
23577
+ for (const [key, ids] of byKey) {
23578
+ if (ids.length < 2) continue;
23579
+ const sorted = [...ids].sort();
23580
+ for (const lessonId of sorted) {
23581
+ const others = sorted.filter((other) => other !== lessonId);
23582
+ findings.push({
23583
+ level: "error",
23584
+ code: "DUPLICATE_RULE",
23585
+ message: `Lesson "${lessonId}" duplicates rule text of: ${others.join(", ")} (normalized key: "${key.slice(0, 60)}").`,
23586
+ lessonId
23587
+ });
23588
+ }
23589
+ }
23590
+ }
23591
+ function collectInvalidTriggerPatterns(graph, findings) {
23592
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23593
+ if (trigger.kind !== "command_pattern") continue;
23594
+ try {
23595
+ new RegExp(trigger.pattern);
23596
+ } catch (err) {
23597
+ findings.push({
23598
+ level: "error",
23599
+ code: "INVALID_TRIGGER_PATTERN",
23600
+ message: `Trigger "${triggerId}" has an invalid command_pattern regex (${trigger.pattern}): ${err instanceof Error ? err.message : String(err)}.`,
23601
+ triggerId
23602
+ });
23603
+ continue;
23604
+ }
23605
+ if (!isSafeRegexPattern(trigger.pattern)) {
23606
+ findings.push({
23607
+ level: "error",
23608
+ code: "UNSAFE_TRIGGER_PATTERN",
23609
+ message: `Trigger "${triggerId}" has a command_pattern regex outside the provably-linear subset (${trigger.pattern}): it can backtrack catastrophically (e.g. a quantified group like (a+)+ or (a|aa)+, adjacent repetition like a+a+, or a backreference/lookaround). Rewrite using a linear pattern.`,
23610
+ triggerId
23611
+ });
23612
+ }
23613
+ }
23614
+ }
23615
+ function collectBackslashGlobPatterns(graph, findings) {
23616
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23617
+ if (trigger.kind !== "file_glob") continue;
23618
+ if (!trigger.pattern.includes("\\")) continue;
23619
+ findings.push({
23620
+ level: "error",
23621
+ code: "BACKSLASH_GLOB_PATTERN",
23622
+ message: `Trigger "${triggerId}" has a file_glob pattern with a backslash (${trigger.pattern}); recall normalizes paths to forward slashes, so it never fires. Replace \\ with /.`,
23623
+ triggerId
23624
+ });
23625
+ }
23626
+ }
23627
+ function collectDuplicateTriggers(graph, findings) {
23628
+ const byKey = /* @__PURE__ */ new Map();
23629
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23630
+ const key = `${trigger.kind}|${trigger.pattern}`;
23631
+ const bucket = byKey.get(key) ?? [];
23632
+ bucket.push(triggerId);
23633
+ byKey.set(key, bucket);
23634
+ }
23635
+ for (const [key, ids] of byKey) {
23636
+ if (ids.length < 2) continue;
23637
+ const sorted = [...ids].sort();
23638
+ for (const triggerId of sorted) {
23639
+ const others = sorted.filter((other) => other !== triggerId);
23640
+ findings.push({
23641
+ level: "error",
23642
+ code: "DUPLICATE_TRIGGER",
23643
+ message: `Trigger "${triggerId}" duplicates (kind, pattern) of: ${others.join(", ")} (key: "${key}").`,
23644
+ triggerId
23645
+ });
23646
+ }
23647
+ }
23648
+ }
23649
+ var HIGH_FANOUT_THRESHOLD = 10;
23650
+ function collectFanout(graph, findings) {
23651
+ const fanout = /* @__PURE__ */ new Map();
23652
+ for (const lesson of Object.values(graph.lessons)) {
23653
+ if (lesson.status !== "active") continue;
23654
+ for (const t of lesson.triggers) fanout.set(t, (fanout.get(t) ?? 0) + 1);
23655
+ }
23656
+ let over = 0;
23657
+ let max = 0;
23658
+ for (const n of fanout.values()) {
23659
+ if (n > HIGH_FANOUT_THRESHOLD) over += 1;
23660
+ if (n > max) max = n;
23661
+ }
23662
+ if (over > 0) {
23663
+ findings.push({
23664
+ level: "warning",
23665
+ code: "HIGH_FANOUT_TRIGGERS",
23666
+ message: `${over} trigger(s) each match more than ${HIGH_FANOUT_THRESHOLD} active lessons (max ${max}); recall returns the ranked top by default \u2014 consider per-lesson trigger refinement to improve precision.`
23667
+ });
23668
+ }
23669
+ }
23670
+ function collectLowSignalKeywords(graph, findings) {
23671
+ const activeTriggerIds2 = /* @__PURE__ */ new Set();
23672
+ for (const lesson of Object.values(graph.lessons)) {
23673
+ if (lesson.status !== "active") continue;
23674
+ for (const t of lesson.triggers) activeTriggerIds2.add(t);
23675
+ }
23676
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23677
+ if (trigger.kind !== "keyword") continue;
23678
+ if (!activeTriggerIds2.has(triggerId)) continue;
23679
+ if (!isLowSignalKeyword(trigger.pattern)) continue;
23680
+ findings.push({
23681
+ level: "warning",
23682
+ code: "LOW_SIGNAL_KEYWORD",
23683
+ message: `Keyword trigger "${triggerId}" carries more than ${MAX_RECOMMENDED_KEYWORD_TOKENS} tokens (${trigger.pattern}); recall matches a keyword only as a substring of --keyword or a contiguous token-run in the file/command, so it rarely fires \u2014 use a short distinctive phrase.`,
23684
+ triggerId
23685
+ });
23686
+ }
23687
+ }
23688
+ function collectStopwordKeywords(graph, findings) {
23689
+ const activeTriggerIds2 = /* @__PURE__ */ new Set();
23690
+ for (const lesson of Object.values(graph.lessons)) {
23691
+ if (lesson.status !== "active") continue;
23692
+ for (const t of lesson.triggers) activeTriggerIds2.add(t);
23693
+ }
23694
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23695
+ if (trigger.kind !== "keyword") continue;
23696
+ if (!activeTriggerIds2.has(triggerId)) continue;
23697
+ if (tokenize(trigger.pattern).length !== 0 && !keywordNeedleLosesTokens(trigger.pattern)) {
23698
+ continue;
23699
+ }
23700
+ findings.push({
23701
+ level: "warning",
23702
+ code: "STOPWORD_KEYWORD",
23703
+ message: `Keyword trigger "${triggerId}" (${trigger.pattern}) loses tokens to stopword filtering, so its needle can never appear as a contiguous run on the mandatory --file/--cmd recall path \u2014 drop the stopwords (e.g. "state art" instead of "state of the art"), or detach it with \`lessons untrigger\`.`,
23704
+ triggerId
23705
+ });
23706
+ }
23707
+ }
23708
+ function normalizeRule(rule) {
23709
+ return rule.trim().replace(/\s+/g, " ").toLowerCase();
23710
+ }
23711
+ function activeTriggerIds(graph) {
23712
+ const ids = /* @__PURE__ */ new Set();
23713
+ for (const lesson of Object.values(graph.lessons)) {
23714
+ if (lesson.status !== "active") continue;
23715
+ for (const t of lesson.triggers) ids.add(t);
23716
+ }
23717
+ return ids;
23718
+ }
23719
+ function deadFileGlobIds(graph, knownPaths) {
23720
+ const active = activeTriggerIds(graph);
23721
+ const paths = [...knownPaths];
23722
+ const dead = /* @__PURE__ */ new Set();
23723
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23724
+ if (trigger.kind !== "file_glob") continue;
23725
+ if (!active.has(triggerId)) continue;
23726
+ const isMatch = picomatch(trigger.pattern, { dot: true });
23727
+ if (!paths.some((p) => isMatch(p))) dead.add(triggerId);
23728
+ }
23729
+ return dead;
23730
+ }
23731
+ function collectDeadFileGlobs(graph, findings, knownPaths) {
23732
+ for (const triggerId of deadFileGlobIds(graph, knownPaths)) {
23733
+ findings.push({
23734
+ level: "warning",
23735
+ code: "DEAD_FILE_GLOB",
23736
+ message: `file_glob trigger "${triggerId}" (${graph.triggers[triggerId]?.pattern ?? ""}) matches no file in the working tree \u2014 the lesson is unreachable via this trigger (a rename likely moved the path). Re-point it at the current path, or detach it with \`lessons untrigger\`, or run \`lessons prune --apply\`.`,
23737
+ triggerId
23738
+ });
23739
+ }
23740
+ }
23741
+ var RUNNER_ANCHOR = /^\^(pnpm|npm|npx|yarn|bun)\b/;
23742
+ function collectRunnerAnchoredPatterns(graph, findings) {
23743
+ const active = activeTriggerIds(graph);
23744
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
23745
+ if (trigger.kind !== "command_pattern") continue;
23746
+ if (!active.has(triggerId)) continue;
23747
+ if (!RUNNER_ANCHOR.test(trigger.pattern)) continue;
23748
+ findings.push({
23749
+ level: "warning",
23750
+ code: "RUNNER_ANCHORED_PATTERN",
23751
+ message: `command_pattern trigger "${triggerId}" (${trigger.pattern}) is anchored to one runner \u2014 it won't fire for the same task via another runner (e.g. \`npx\` vs \`pnpm\`). Drop the \`^<runner>\` anchor and key on the task (e.g. \`\\bvitest\\b\`).`,
23752
+ triggerId
23753
+ });
23754
+ }
23755
+ }
23756
+
23757
+ // src/lessons/validate.ts
23758
+ function validateLessonsGraph(graph, options = {}) {
23759
+ const findings = [];
23760
+ const schemaResult = LessonsGraphSchema.safeParse(graph);
23761
+ if (!schemaResult.success) {
23762
+ findings.push({
23763
+ level: "error",
23764
+ code: "SCHEMA_INVALID",
23765
+ message: schemaResult.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
23766
+ });
23767
+ return { ok: false, findings };
23768
+ }
23769
+ collectDanglingRefs(graph, findings);
23770
+ collectDuplicateRefs(graph, findings);
23771
+ collectStatusInvariants(graph, findings);
23772
+ collectLifecycleInvariants(graph, findings);
23773
+ collectDuplicateRules(graph, findings);
23774
+ collectReachability(graph, findings);
23775
+ collectInvalidTriggerPatterns(graph, findings);
23776
+ collectBackslashGlobPatterns(graph, findings);
23777
+ collectDuplicateTriggers(graph, findings);
23778
+ collectOrphans(graph, findings);
23779
+ collectFanout(graph, findings);
23780
+ collectLowSignalKeywords(graph, findings);
23781
+ collectStopwordKeywords(graph, findings);
23782
+ collectRunnerAnchoredPatterns(graph, findings);
23783
+ if (options.knownPaths !== void 0) collectDeadFileGlobs(graph, findings, options.knownPaths);
23784
+ const ok = findings.every((f) => f.level !== "error");
23785
+ return { ok, findings };
23786
+ }
23787
+
23788
+ // src/core/lint/shared/lessons.ts
23789
+ var LESSONS_TARGET = "lessons";
23790
+ var GRAPH_REL = ".agentsmesh/lessons/lessons.json";
23791
+ var ROOT_RULE_REL = ".agentsmesh/rules/_root.md";
23792
+ var LESSONS_HEADING = /^## Lessons \(/m;
23793
+ function lintLessonsSubsystem(projectRoot, scope) {
23794
+ if (scope === "global") return [];
23795
+ const paths = lessonsPaths(projectRoot);
23796
+ if (!existsSync(paths.graph)) return [];
23797
+ const out2 = [];
23798
+ let graph;
23799
+ try {
23800
+ graph = loadLessonsGraph(projectRoot);
23801
+ } catch (err) {
23802
+ return [
23803
+ diag(
23804
+ "error",
23805
+ GRAPH_REL,
23806
+ `lessons.json failed to load: ${err instanceof Error ? err.message : String(err)}`
23807
+ )
23808
+ ];
23809
+ }
23810
+ const knownPaths = listProjectFiles(projectRoot) ?? void 0;
23811
+ const report = validateLessonsGraph(graph, { knownPaths });
23812
+ for (const finding of report.findings) {
23813
+ out2.push(diag(finding.level, GRAPH_REL, `[${finding.code}] ${finding.message}`));
23814
+ }
23815
+ const rootRuleAbs = join(projectRoot, ROOT_RULE_REL);
23816
+ const rootRuleBody = existsSync(rootRuleAbs) ? readFileSync(rootRuleAbs, "utf8") : "";
23817
+ if (!LESSONS_HEADING.test(rootRuleBody)) {
23818
+ out2.push(
23819
+ diag(
23820
+ "warning",
23821
+ ROOT_RULE_REL,
23822
+ 'lessons procedural rule ("## Lessons (...)") is missing from _root.md \u2014 recall/capture enforcement will not fire.'
23823
+ )
23824
+ );
23825
+ }
23826
+ return out2;
23827
+ }
23828
+ function diag(level, file, message) {
23829
+ return { level, file, target: LESSONS_TARGET, message };
23830
+ }
22568
23831
 
22569
23832
  // src/core/lint/linter.ts
22570
23833
  var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
@@ -22585,7 +23848,7 @@ async function runLint(config, canonical, projectRoot, targetFilter, options = {
22585
23848
  const hasMcp = config.features.includes("mcp");
22586
23849
  const hasPermissions = config.features.includes("permissions");
22587
23850
  const hasHooks = config.features.includes("hooks");
22588
- const diagnostics = [];
23851
+ const diagnostics = [...lintLessonsSubsystem(projectRoot, scope)];
22589
23852
  const projectFiles = scope === "global" ? [] : await getProjectFiles(projectRoot);
22590
23853
  for (const target31 of targets) {
22591
23854
  const fullDesc = getDescriptor(target31);