agentsmesh 0.33.0 → 0.34.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,9 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { parse, stringify, parseDocument, isMap, Document, isSeq, isScalar, Scalar, Pair, YAMLSeq } from 'yaml';
3
+ import { existsSync, readFileSync, constants, readdirSync, realpathSync, statSync } from 'fs';
3
4
  import { basename, join, dirname, relative, win32, posix, sep, resolve, extname } from 'path';
4
5
  import { readFile, rm, mkdir, readdir, lstat, unlink, writeFile, rename, chmod, access, stat, realpath, mkdtemp, cp } from 'fs/promises';
5
6
  import { setTimeout } from 'timers/promises';
6
- import { existsSync, readFileSync, constants, readdirSync, realpathSync, statSync } from 'fs';
7
7
  import { parse as parse$1, stringify as stringify$1 } from 'smol-toml';
8
8
  import { Buffer } from 'buffer';
9
9
  import { homedir, tmpdir } from 'os';
@@ -94,8 +94,17 @@ var init_target_descriptor_schema = __esm({
94
94
  managedOutputsSchema = z.object({
95
95
  dirs: z.array(z.string()),
96
96
  files: z.array(z.string()),
97
- coOwnedFiles: z.array(z.string()).optional()
97
+ coOwnedFiles: z.array(z.string()).optional(),
98
+ supersededFiles: z.array(z.string()).optional()
98
99
  }).passthrough().superRefine((value, ctx) => {
100
+ for (const file of value.supersededFiles ?? []) {
101
+ if (!value.files.includes(file) && !(value.coOwnedFiles ?? []).includes(file)) continue;
102
+ ctx.addIssue({
103
+ code: "custom",
104
+ path: ["supersededFiles"],
105
+ message: `"${file}" is in managedOutputs.supersededFiles and another managedOutputs list.`
106
+ });
107
+ }
99
108
  for (const file of value.coOwnedFiles ?? []) {
100
109
  if (!value.files.includes(file)) continue;
101
110
  ctx.addIssue({
@@ -398,24 +407,28 @@ var init_target_ids = __esm({
398
407
  }
399
408
  });
400
409
  function parseFrontmatter(content) {
401
- const open = content.indexOf("---");
402
- if (open !== 0) {
403
- return { frontmatter: {}, body: content.trim() };
404
- }
405
- const close = content.indexOf("---", 3);
406
- if (close === -1) {
407
- return { frontmatter: {}, body: content.trim() };
408
- }
409
- const yamlStr = content.slice(3, close).trim();
410
- const body = content.slice(close + 3).trim();
410
+ const split = splitFrontmatter(content);
411
+ if (split === null) return { frontmatter: {}, body: content.trim() };
412
+ const yamlStr = split.yaml.trim();
411
413
  const frontmatter = yamlStr === "" ? {} : parse(yamlStr) ?? {};
412
- return { frontmatter, body };
414
+ return { frontmatter, body: split.body };
415
+ }
416
+ function splitFrontmatter(content) {
417
+ const opener = OPENER.exec(content);
418
+ if (opener === null) return null;
419
+ const yamlStart = opener[0].length;
420
+ const closer = CLOSER.exec(content.slice(yamlStart));
421
+ if (closer === null) return null;
422
+ const closeStart = yamlStart + closer.index;
423
+ const closeEnd = closeStart + closer[0].length;
424
+ return {
425
+ yaml: content.slice(yamlStart, closeStart),
426
+ body: content.slice(closeEnd).trim(),
427
+ prefix: content.slice(0, closeEnd)
428
+ };
413
429
  }
414
430
  function extractBody(content) {
415
- if (content.indexOf("---") !== 0) return content.trim();
416
- const close = content.indexOf("---", 3);
417
- if (close === -1) return content.trim();
418
- return content.slice(close + 3).trim();
431
+ return splitFrontmatter(content)?.body ?? content.trim();
419
432
  }
420
433
  function tryParseFrontmatter(content, filePath2) {
421
434
  try {
@@ -447,8 +460,11 @@ ${yamlStr}
447
460
 
448
461
  ${body}`;
449
462
  }
463
+ var OPENER, CLOSER;
450
464
  var init_markdown = __esm({
451
465
  "src/utils/text/markdown.ts"() {
466
+ OPENER = /^---[ \t]*\r?\n/;
467
+ CLOSER = /^---[ \t]*\r?$/m;
452
468
  }
453
469
  });
454
470
 
@@ -508,11 +524,25 @@ var init_command_skill = __esm({
508
524
  LEGACY_CODEX_COMMAND_SKILL_PREFIX = "ab-command-";
509
525
  }
510
526
  });
527
+ var RECALL_HOOK_COMMAND;
528
+ var init_recall_hook_scaffold = __esm({
529
+ "src/lessons/recall-hook-scaffold.ts"() {
530
+ RECALL_HOOK_COMMAND = "agentsmesh lessons hook";
531
+ }
532
+ });
511
533
 
512
534
  // src/core/hook-types.ts
535
+ function isBestEffortHookEvent(event, entries) {
536
+ if (!BEST_EFFORT_HOOK_EVENTS.has(event)) return false;
537
+ if (!Array.isArray(entries)) return true;
538
+ return entries.every(
539
+ (entry) => typeof entry === "object" && entry !== null && typeof entry.command === "string" && entry.command.includes(RECALL_HOOK_COMMAND)
540
+ );
541
+ }
513
542
  var BEST_EFFORT_HOOK_EVENTS;
514
543
  var init_hook_types = __esm({
515
544
  "src/core/hook-types.ts"() {
545
+ init_recall_hook_scaffold();
516
546
  BEST_EFFORT_HOOK_EVENTS = /* @__PURE__ */ new Set([
517
547
  "UserPromptSubmit",
518
548
  "PostToolUseFailure",
@@ -698,7 +728,7 @@ var init_conf_merge = __esm({
698
728
  });
699
729
 
700
730
  // src/core/errors.ts
701
- var AgentsMeshError, ConfigNotFoundError, ConfigValidationError, TargetNotFoundError, ImportError, GenerationError, RemoteFetchError, LockAcquisitionError, FileSystemError;
731
+ var AgentsMeshError, ConfigNotFoundError, ConfigValidationError, CanonicalParseError, TargetNotFoundError, ImportError, GenerationError, RemoteFetchError, LockAcquisitionError, FileSystemError;
702
732
  var init_errors = __esm({
703
733
  "src/core/errors.ts"() {
704
734
  AgentsMeshError = class extends Error {
@@ -733,6 +763,21 @@ var init_errors = __esm({
733
763
  this.issues = issues;
734
764
  }
735
765
  };
766
+ CanonicalParseError = class extends AgentsMeshError {
767
+ path;
768
+ constructor(path, cause) {
769
+ const detail = cause instanceof Error ? cause.message : String(cause);
770
+ super(
771
+ "AM_CONFIG_INVALID",
772
+ `Invalid canonical file ${path}: ${detail}. Fix the syntax and try again.`,
773
+ {
774
+ cause
775
+ }
776
+ );
777
+ this.name = "CanonicalParseError";
778
+ this.path = path;
779
+ }
780
+ };
736
781
  TargetNotFoundError = class extends AgentsMeshError {
737
782
  target;
738
783
  constructor(target34, options) {
@@ -806,6 +851,11 @@ function normalizeLineEndings(content) {
806
851
  function executableModeFor(path) {
807
852
  return EXECUTABLE_SCRIPT_EXTENSIONS.has(extname(path).toLowerCase()) ? 493 : void 0;
808
853
  }
854
+ function normalizeTextPayload(path, content) {
855
+ if (!shouldNormalizeLineEndings(path)) return content;
856
+ const withoutBom = content.startsWith(UTF8_BOM) ? content.slice(UTF8_BOM.length) : content;
857
+ return normalizeLineEndings(withoutBom);
858
+ }
809
859
  var UTF8_BOM, TEXT_EXTENSIONS, TEXT_DOTFILES, EXECUTABLE_SCRIPT_EXTENSIONS;
810
860
  var init_fs_text_encoding = __esm({
811
861
  "src/utils/filesystem/fs-text-encoding.ts"() {
@@ -1306,10 +1356,8 @@ function stripManagedBlock(content, start, end) {
1306
1356
  return content.replace(managedBlockPattern(start, end), "").trim();
1307
1357
  }
1308
1358
  function splitFrontmatterPrefix(content) {
1309
- if (content.indexOf("---") !== 0) return { prefix: "", body: content.trim() };
1310
- const close = content.indexOf("---", 3);
1311
- if (close === -1) return { prefix: "", body: content.trim() };
1312
- return { prefix: content.slice(0, close + 3), body: content.slice(close + 3).trim() };
1359
+ const split = splitFrontmatter(content);
1360
+ return split === null ? { prefix: "", body: content.trim() } : { prefix: split.prefix, body: split.body };
1313
1361
  }
1314
1362
  function insertAtBodyTop(content, block) {
1315
1363
  const { prefix, body } = splitFrontmatterPrefix(content);
@@ -1410,6 +1458,7 @@ function extractEmbeddedRules(content) {
1410
1458
  var ROOT_CONTRACT_START, ROOT_CONTRACT_END, EMBEDDED_RULES_START, EMBEDDED_RULES_END, EMBEDDED_RULE_END, EMBEDDED_RULE_START_PREFIX, EMBEDDED_RULE_START_SUFFIX;
1411
1459
  var init_managed_blocks = __esm({
1412
1460
  "src/targets/projection/managed-blocks.ts"() {
1461
+ init_markdown();
1413
1462
  ROOT_CONTRACT_START = "<!-- agentsmesh:root-generation-contract:start -->";
1414
1463
  ROOT_CONTRACT_END = "<!-- agentsmesh:root-generation-contract:end -->";
1415
1464
  EMBEDDED_RULES_START = "<!-- agentsmesh:embedded-rules:start -->";
@@ -1575,10 +1624,17 @@ async function serializeImportedCommandWithFallback(destinationPath, imported, b
1575
1624
  })();
1576
1625
  const description = imported.hasDescription ? imported.description ?? "" : typeof existingFrontmatter.description === "string" ? existingFrontmatter.description : "";
1577
1626
  const allowedTools = imported.hasAllowedTools ? imported.allowedTools ?? [] : existingAllowedTools;
1627
+ const {
1628
+ description: _d,
1629
+ "allowed-tools": _a,
1630
+ allowedTools: _c,
1631
+ ...preserved
1632
+ } = existingFrontmatter;
1578
1633
  return serializeFrontmatter(
1579
1634
  {
1580
1635
  description,
1581
- "allowed-tools": allowedTools
1636
+ "allowed-tools": allowedTools,
1637
+ ...preserved
1582
1638
  },
1583
1639
  body.trim() || ""
1584
1640
  );
@@ -2476,6 +2532,26 @@ var init_link_rebaser_resolution = __esm({
2476
2532
  }
2477
2533
  });
2478
2534
 
2535
+ // src/core/reference/link-uri-encoding.ts
2536
+ function decodeLinkPath(token, role) {
2537
+ if (role !== "markdown-link-dest" || !token.includes("%")) return token;
2538
+ try {
2539
+ return decodeURIComponent(token);
2540
+ } catch {
2541
+ return token;
2542
+ }
2543
+ }
2544
+ function encodeLinkPath(path, enabled) {
2545
+ if (!enabled) return path;
2546
+ return path.split("/").map(
2547
+ (segment) => segment === "" || segment === "." || segment === ".." ? segment : encodeURIComponent(segment)
2548
+ ).join("/");
2549
+ }
2550
+ var init_link_uri_encoding = __esm({
2551
+ "src/core/reference/link-uri-encoding.ts"() {
2552
+ }
2553
+ });
2554
+
2479
2555
  // src/core/reference/link-token-guards.ts
2480
2556
  function isTildeHomeRelativePathToken(fullContent, matchOffset, matchText) {
2481
2557
  if (matchOffset >= 2 && fullContent[matchOffset - 2] === "~" && fullContent[matchOffset - 1] === "/") {
@@ -2605,10 +2681,11 @@ function rewriteFileLinks(input) {
2605
2681
  const { candidate: punctStripped, suffix } = stripTrailingPunctuation(match);
2606
2682
  if (!punctStripped) return match;
2607
2683
  const lineNumMatch = LINE_NUMBER_SUFFIX.exec(punctStripped);
2608
- const candidate = lineNumMatch ? punctStripped.slice(0, lineNumMatch.index) : punctStripped;
2684
+ const rawCandidate = lineNumMatch ? punctStripped.slice(0, lineNumMatch.index) : punctStripped;
2609
2685
  const lineNumSuffix = lineNumMatch ? lineNumMatch[0] : "";
2610
- if (!candidate) return match;
2611
- const tokenContext = getTokenContext(fullContent, offset, offset + candidate.length);
2686
+ if (!rawCandidate) return match;
2687
+ const tokenContext = getTokenContext(fullContent, offset, offset + rawCandidate.length);
2688
+ const candidate = decodeLinkPath(rawCandidate, tokenContext.role);
2612
2689
  if (tokenContext.role !== "markdown-link-dest" && WINDOWS_ABSOLUTE_PATH.test(candidate)) {
2613
2690
  return match;
2614
2691
  }
@@ -2678,7 +2755,7 @@ function rewriteFileLinks(input) {
2678
2755
  const targetTop = targetFromRoot.split("/").filter(Boolean)[0] ?? "";
2679
2756
  const tokenIsCanonicalMesh = normalizeSeparators(candidate).startsWith(".agentsmesh/");
2680
2757
  const preferRelativeProseInSameSurface = !tokenIsCanonicalMesh && !targetIsDirectory && destTop.length > 0 && destTop === targetTop && destTop.startsWith(".") && destTop !== ".agentsmesh";
2681
- const forceRelative = preferRelativeProseInSameSurface || tokenContext.role === "markdown-link-dest" || isMarkdownLinkDestinationToken(fullContent, offset, candidate);
2758
+ const forceRelative = preferRelativeProseInSameSurface || tokenContext.role === "markdown-link-dest" || isMarkdownLinkDestinationToken(fullContent, offset, rawCandidate);
2682
2759
  const rewritten = formatLinkPathForDestination(
2683
2760
  input.projectRoot,
2684
2761
  input.destinationFile,
@@ -2695,7 +2772,7 @@ function rewriteFileLinks(input) {
2695
2772
  }
2696
2773
  );
2697
2774
  if (!rewritten) return match;
2698
- return `${rewritten}${lineNumSuffix}${suffix}`;
2775
+ return `${encodeLinkPath(rewritten, candidate !== rawCandidate)}${lineNumSuffix}${suffix}`;
2699
2776
  });
2700
2777
  return { content, missing: [...missing] };
2701
2778
  }
@@ -2705,6 +2782,7 @@ var init_link_rebaser = __esm({
2705
2782
  init_link_rebaser_helpers();
2706
2783
  init_link_rebaser_output();
2707
2784
  init_link_rebaser_resolution();
2785
+ init_link_uri_encoding();
2708
2786
  init_link_token_guards();
2709
2787
  init_link_token_context();
2710
2788
  }
@@ -3588,7 +3666,7 @@ function unsupportedHookEventNames(hooks, supportedEvents) {
3588
3666
  if (!hooks) return [];
3589
3667
  const supported = new Set(supportedEvents);
3590
3668
  return Object.keys(hooks).filter(
3591
- (event) => !supported.has(event) && !BEST_EFFORT_HOOK_EVENTS.has(event)
3669
+ (event) => !supported.has(event) && !isBestEffortHookEvent(event, hooks[event])
3592
3670
  );
3593
3671
  }
3594
3672
  function createUnsupportedHookWarning(event, target34, supportedEvents, options) {
@@ -7125,6 +7203,21 @@ var init_embedded_rules = __esm({
7125
7203
  }
7126
7204
  });
7127
7205
 
7206
+ // src/canonical/features/syntax-error.ts
7207
+ function failSyntax(filePath2, cause, onParseError) {
7208
+ const error = new CanonicalParseError(filePath2, cause);
7209
+ if (onParseError !== void 0) {
7210
+ onParseError(error, filePath2);
7211
+ return null;
7212
+ }
7213
+ throw error;
7214
+ }
7215
+ var init_syntax_error = __esm({
7216
+ "src/canonical/features/syntax-error.ts"() {
7217
+ init_errors();
7218
+ }
7219
+ });
7220
+
7128
7221
  // src/canonical/features/mcp.ts
7129
7222
  function parseStringMap(raw) {
7130
7223
  if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return {};
@@ -7207,14 +7300,14 @@ function stripJsonComments(text) {
7207
7300
  }
7208
7301
  return result2;
7209
7302
  }
7210
- async function parseMcp(mcpPath) {
7303
+ async function parseMcp(mcpPath, onParseError) {
7211
7304
  const content = await readFileSafe(mcpPath);
7212
7305
  if (!content) return null;
7213
7306
  let parsed;
7214
7307
  try {
7215
7308
  parsed = JSON.parse(stripJsonComments(content));
7216
- } catch {
7217
- return null;
7309
+ } catch (err) {
7310
+ return failSyntax(mcpPath, err, onParseError);
7218
7311
  }
7219
7312
  if (!parsed || typeof parsed !== "object") return null;
7220
7313
  const mcpServersRaw = parsed.mcpServers;
@@ -7229,6 +7322,7 @@ async function parseMcp(mcpPath) {
7229
7322
  }
7230
7323
  var init_mcp = __esm({
7231
7324
  "src/canonical/features/mcp.ts"() {
7325
+ init_syntax_error();
7232
7326
  init_fs();
7233
7327
  }
7234
7328
  });
@@ -9008,10 +9102,10 @@ var init_claude_code2 = __esm({
9008
9102
  skillDir: ".claude/skills",
9009
9103
  managedOutputs: {
9010
9104
  dirs: [".claude/agents", ".claude/commands", ".claude/rules", ".claude/skills"],
9011
- // CLAUDE_NESTED_ROOT is the pre-migration project location; listing it here lets
9012
- // `cleanupStaleGeneratedOutputs` evict a leftover `.claude/CLAUDE.md` once generation
9105
+ files: [CLAUDE_ROOT, ".claudeignore"],
9106
+ // CLAUDE_NESTED_ROOT is the pre-migration project location: evicted once a run
9013
9107
  // writes the root `CLAUDE.md`, so Claude Code never concatenates both into context.
9014
- files: [CLAUDE_ROOT, CLAUDE_NESTED_ROOT, ".claudeignore"],
9108
+ supersededFiles: [CLAUDE_NESTED_ROOT],
9015
9109
  // `.mcp.json` is the shared project MCP file teams hand-commit and
9016
9110
  // deepagents-cli writes too; agentsmesh owns only `mcpServers` in it.
9017
9111
  // `.claude/settings.json` is co-owned through the `SETTINGS_JSON_PATHS`
@@ -10242,9 +10336,8 @@ var init_cline2 = __esm({
10242
10336
  };
10243
10337
  }
10244
10338
  });
10245
- function ruleSlug2(source) {
10246
- return basename(source, ".md");
10247
- }
10339
+
10340
+ // src/targets/codex-cli/codex-rule-paths.ts
10248
10341
  function directoryFromGlob(glob) {
10249
10342
  let normalized = glob.trim();
10250
10343
  if (normalized.startsWith("./")) normalized = normalized.slice(2);
@@ -10265,12 +10358,12 @@ function codexRuleDirectory(rule) {
10265
10358
  const dir = directoryFromGlob(glob);
10266
10359
  if (dir) return dir;
10267
10360
  }
10268
- return ruleSlug2(rule.source);
10361
+ return null;
10269
10362
  }
10270
10363
  function codexNestedAgentsPath(rule) {
10271
10364
  const dir = codexRuleDirectory(rule);
10272
10365
  const filename = rule.codexInstructionVariant === "override" ? "AGENTS.override.md" : "AGENTS.md";
10273
- return `${dir}/${filename}`;
10366
+ return dir === null ? filename : `${dir}/${filename}`;
10274
10367
  }
10275
10368
  var GLOB_METACHAR;
10276
10369
  var init_codex_rule_paths = __esm({
@@ -10278,10 +10371,9 @@ var init_codex_rule_paths = __esm({
10278
10371
  GLOB_METACHAR = /[*?[\]]/;
10279
10372
  }
10280
10373
  });
10281
-
10282
- // src/targets/codebuff/rule-paths.ts
10283
10374
  function codebuffNestedKnowledgePath(rule) {
10284
- return `${codexRuleDirectory(rule)}/AGENTS.md`;
10375
+ const dir = codexRuleDirectory(rule) ?? basename(rule.source, ".md");
10376
+ return `${dir}/AGENTS.md`;
10285
10377
  }
10286
10378
  var init_rule_paths = __esm({
10287
10379
  "src/targets/codebuff/rule-paths.ts"() {
@@ -10892,6 +10984,9 @@ function eligibleAdvisoryRules(canonical) {
10892
10984
  return rule.targets.length === 0 || rule.targets.includes("codex-cli");
10893
10985
  });
10894
10986
  }
10987
+ function isRootEmbedded(rule) {
10988
+ return codexNestedAgentsPath(rule) === AGENTS_MD;
10989
+ }
10895
10990
  function groupByNestedPath2(rules) {
10896
10991
  const groups = /* @__PURE__ */ new Map();
10897
10992
  for (const rule of rules) {
@@ -10904,9 +10999,11 @@ function groupByNestedPath2(rules) {
10904
10999
  }
10905
11000
  function generateRules9(canonical) {
10906
11001
  const root = canonical.rules.find((r) => r.root);
11002
+ const advisory = eligibleAdvisoryRules(canonical);
10907
11003
  const outputs = [];
10908
11004
  if (root) {
10909
- outputs.push({ path: AGENTS_MD, content: root.body.trim() });
11005
+ const content = appendEmbeddedRulesBlock(root.body.trim(), advisory.filter(isRootEmbedded));
11006
+ outputs.push({ path: AGENTS_MD, content });
10910
11007
  }
10911
11008
  for (const rule of canonical.rules) {
10912
11009
  if (rule.root) continue;
@@ -10918,7 +11015,8 @@ function generateRules9(canonical) {
10918
11015
  content: toSafeCodexRulesContent(rule.body)
10919
11016
  });
10920
11017
  }
10921
- for (const [path, rules] of groupByNestedPath2(eligibleAdvisoryRules(canonical))) {
11018
+ const nested = advisory.filter((rule) => !isRootEmbedded(rule));
11019
+ for (const [path, rules] of groupByNestedPath2(nested)) {
10922
11020
  const content = rules.map((rule) => rule.body.trim()).filter((body) => body.length > 0).join("\n\n");
10923
11021
  outputs.push({ path, content });
10924
11022
  }
@@ -11743,6 +11841,23 @@ var init_linter9 = __esm({
11743
11841
  });
11744
11842
 
11745
11843
  // src/targets/codex-cli/lint.ts
11844
+ function lintAgents3(canonical) {
11845
+ const diagnostics = [];
11846
+ for (const agent of canonical.agents) {
11847
+ const dropped = CODEX_DROPPED_AGENT_FIELDS.filter(
11848
+ (field) => hasAgentValue(agent, field)
11849
+ ).sort();
11850
+ if (dropped.length === 0) continue;
11851
+ diagnostics.push(
11852
+ createWarning(
11853
+ agent.source,
11854
+ "codex-cli",
11855
+ `Codex agent TOML supports name, description, developer_instructions, model, sandbox_mode and mcp_servers; canonical ${dropped.join(", ")} are not projected to ${CODEX_AGENTS_DIR}/${agent.name}.toml.`
11856
+ )
11857
+ );
11858
+ }
11859
+ return diagnostics;
11860
+ }
11746
11861
  function lintMcp4(canonical) {
11747
11862
  if (!canonical.mcp || Object.keys(canonical.mcp.mcpServers).length === 0) return [];
11748
11863
  const diagnostics = [];
@@ -11774,11 +11889,21 @@ function lintHooks7(canonical) {
11774
11889
  (event) => createUnsupportedHookWarning(event, "codex-cli", CODEX_SUPPORTED_HOOK_EVENTS)
11775
11890
  );
11776
11891
  }
11892
+ var CODEX_DROPPED_AGENT_FIELDS;
11777
11893
  var init_lint8 = __esm({
11778
11894
  "src/targets/codex-cli/lint.ts"() {
11779
11895
  init_helpers();
11780
11896
  init_mcp_servers();
11897
+ init_agents_format();
11781
11898
  init_constants10();
11899
+ CODEX_DROPPED_AGENT_FIELDS = [
11900
+ "tools",
11901
+ "disallowedTools",
11902
+ "maxTurns",
11903
+ "hooks",
11904
+ "skills",
11905
+ "memory"
11906
+ ];
11782
11907
  }
11783
11908
  });
11784
11909
 
@@ -11853,6 +11978,7 @@ var init_codex_cli2 = __esm({
11853
11978
  generateMcp: generateMcp6,
11854
11979
  generateHooks: generateHooks6,
11855
11980
  generatePermissions: generatePermissions7,
11981
+ lint: lintAgents3,
11856
11982
  importFrom: importFromCodex
11857
11983
  };
11858
11984
  project6 = {
@@ -11900,7 +12026,7 @@ var init_codex_cli2 = __esm({
11900
12026
  },
11901
12027
  rewriteGeneratedPath(path) {
11902
12028
  if (path === AGENTS_MD) return CODEX_GLOBAL_AGENTS_MD;
11903
- if (/\/AGENTS(\.override)?\.md$/.test(path)) return null;
12029
+ if (/(^|\/)AGENTS(\.override)?\.md$/.test(path)) return null;
11904
12030
  if (path.startsWith(`${CODEX_INSTRUCTIONS_DIR}/`)) return null;
11905
12031
  return path;
11906
12032
  },
@@ -12556,7 +12682,7 @@ function hasValue(agent, field) {
12556
12682
  if (value && typeof value === "object") return Object.keys(value).length > 0;
12557
12683
  return false;
12558
12684
  }
12559
- function lintAgents3(canonical) {
12685
+ function lintAgents4(canonical) {
12560
12686
  const diagnostics = [];
12561
12687
  for (const agent of canonical.agents) {
12562
12688
  const dropped = DROPPED_FIELDS.filter((field) => hasValue(agent, field)).sort();
@@ -12724,7 +12850,7 @@ var init_continue2 = __esm({
12724
12850
  generateHooks: generateHooks7,
12725
12851
  generateIgnore: generateIgnore8,
12726
12852
  // Feature-independent lint hook: agent warnings must not hang off `rules`.
12727
- lint: lintAgents3,
12853
+ lint: lintAgents4,
12728
12854
  importFrom: importFromContinue
12729
12855
  };
12730
12856
  descriptor10 = {
@@ -12981,7 +13107,7 @@ var init_hook_format = __esm({
12981
13107
  init_hook_entry();
12982
13108
  }
12983
13109
  });
12984
- function ruleSlug3(source) {
13110
+ function ruleSlug2(source) {
12985
13111
  const name = basename(source, ".md");
12986
13112
  return name === "_root" ? "root" : name;
12987
13113
  }
@@ -13015,7 +13141,7 @@ function generateRules11(canonical) {
13015
13141
  if (rule.root) continue;
13016
13142
  if (rule.targets.length > 0 && !rule.targets.includes("copilot")) continue;
13017
13143
  if (rule.globs.length === 0) continue;
13018
- const slug = ruleSlug3(rule.source);
13144
+ const slug = ruleSlug2(rule.source);
13019
13145
  const frontmatter = {
13020
13146
  description: rule.description || void 0,
13021
13147
  applyTo: rule.globs.length === 1 ? rule.globs[0] : rule.globs
@@ -14504,13 +14630,18 @@ var init_rules2 = __esm({
14504
14630
 
14505
14631
  // src/targets/cursor/generator/commands.ts
14506
14632
  function generateCommands13(canonical) {
14507
- return canonical.commands.map((cmd) => ({
14508
- path: `${CURSOR_COMMANDS_DIR}/${cmd.name}.md`,
14509
- content: cmd.body.trim() || ""
14510
- }));
14633
+ return canonical.commands.map((cmd) => {
14634
+ const frontmatter = {};
14635
+ if (cmd.description) frontmatter.description = cmd.description;
14636
+ return {
14637
+ path: `${CURSOR_COMMANDS_DIR}/${cmd.name}.md`,
14638
+ content: serializeFrontmatter(frontmatter, cmd.body.trim() || "")
14639
+ };
14640
+ });
14511
14641
  }
14512
14642
  var init_commands = __esm({
14513
14643
  "src/targets/cursor/generator/commands.ts"() {
14644
+ init_markdown();
14514
14645
  init_constants13();
14515
14646
  }
14516
14647
  });
@@ -14605,7 +14736,7 @@ var init_permissions3 = __esm({
14605
14736
  // src/targets/cursor/hook-format.ts
14606
14737
  function unmappedCursorHookEvents(hooks) {
14607
14738
  return Object.keys(hooks).filter(
14608
- (event) => Array.isArray(hooks[event]) && hooks[event].length > 0 && !(event in CANONICAL_TO_CURSOR) && !BEST_EFFORT_HOOK_EVENTS.has(event)
14739
+ (event) => Array.isArray(hooks[event]) && hooks[event].length > 0 && !(event in CANONICAL_TO_CURSOR) && !isBestEffortHookEvent(event, hooks[event])
14609
14740
  );
14610
14741
  }
14611
14742
  function toCursorHooks(hooks) {
@@ -15043,14 +15174,11 @@ async function hasGlobalCursorArtifacts(projectRoot) {
15043
15174
  join(projectRoot, CURSOR_GLOBAL_USER_RULES),
15044
15175
  join(projectRoot, CURSOR_MCP),
15045
15176
  join(projectRoot, CURSOR_HOOKS),
15046
- join(projectRoot, CURSOR_IGNORE),
15047
- join(projectRoot, CURSOR_SKILLS_DIR),
15048
- join(projectRoot, CURSOR_AGENTS_DIR),
15049
- join(projectRoot, CURSOR_COMMANDS_DIR)
15177
+ join(projectRoot, CURSOR_IGNORE)
15050
15178
  ];
15051
15179
  for (const p of candidates) {
15052
- const stat7 = await readFileSafe(p);
15053
- if (stat7 !== null && stat7.trim() !== "") return true;
15180
+ const content = await readFileSafe(p);
15181
+ if (content !== null && content.trim() !== "") return true;
15054
15182
  }
15055
15183
  const skillFiles = await readDirRecursiveNoSymlinks(join(projectRoot, CURSOR_SKILLS_DIR));
15056
15184
  if (skillFiles.some((f) => f.endsWith(".md"))) return true;
@@ -15337,11 +15465,11 @@ function lintHooks10(canonical) {
15337
15465
  ];
15338
15466
  }
15339
15467
  function lintCommands6(canonical) {
15340
- return canonical.commands.filter((command) => command.description.length > 0 || command.allowedTools.length > 0).map(
15468
+ return canonical.commands.filter((command) => command.allowedTools.length > 0).map(
15341
15469
  (command) => createWarning(
15342
15470
  command.source,
15343
15471
  "cursor",
15344
- "Cursor command files are plain Markdown; command description and allowed-tools metadata are not projected."
15472
+ "Cursor command files project only description frontmatter; allowed-tools metadata is not projected."
15345
15473
  )
15346
15474
  );
15347
15475
  }
@@ -15674,7 +15802,7 @@ var init_mcp_merge4 = __esm({
15674
15802
  // src/targets/deepagents-cli/hooks-format.ts
15675
15803
  function unmappedDeepagentsHookEvents(hooks) {
15676
15804
  return Object.keys(hooks).filter(
15677
- (event) => Array.isArray(hooks[event]) && hooks[event].length > 0 && !(event in CANONICAL_TO_DEEPAGENTS) && !BEST_EFFORT_HOOK_EVENTS.has(event)
15805
+ (event) => Array.isArray(hooks[event]) && hooks[event].length > 0 && !(event in CANONICAL_TO_DEEPAGENTS) && !isBestEffortHookEvent(event, hooks[event])
15678
15806
  );
15679
15807
  }
15680
15808
  function toDeepagentsHooks(hooks) {
@@ -16242,7 +16370,8 @@ var init_deepagents_cli2 = __esm({
16242
16370
  }
16243
16371
  },
16244
16372
  buildImportPaths: buildDeepagentsCliImportPaths,
16245
- detectionPaths: [DEEPAGENTS_CLI_ROOT_FILE, DEEPAGENTS_CLI_MCP_FILE]
16373
+ // `.mcp.json` is co-owned with claude-code (agentsmesh writes it), so it must not enroll this target.
16374
+ detectionPaths: [DEEPAGENTS_CLI_ROOT_FILE]
16246
16375
  };
16247
16376
  }
16248
16377
  });
@@ -20220,7 +20349,8 @@ var init_layout8 = __esm({
20220
20349
  skillDir: KIMI_CODE_SKILLS_DIR,
20221
20350
  managedOutputs: {
20222
20351
  dirs: [KIMI_CODE_AGENTS_DIR, KIMI_CODE_SKILLS_DIR],
20223
- files: [KIMI_CODE_ROOT_FILE, KIMI_CODE_NESTED_ROOT_FILE],
20352
+ files: [KIMI_CODE_ROOT_FILE],
20353
+ supersededFiles: [KIMI_CODE_NESTED_ROOT_FILE],
20224
20354
  // Kimi Code's own MCP config, in the same directory as the credential-
20225
20355
  // bearing config.toml this layout already refuses to delete.
20226
20356
  coOwnedFiles: [KIMI_CODE_MCP_FILE]
@@ -20868,7 +20998,7 @@ function lintMcp9(canonical) {
20868
20998
  }
20869
20999
  return diagnostics;
20870
21000
  }
20871
- function lintAgents4(canonical) {
21001
+ function lintAgents5(canonical) {
20872
21002
  return canonical.agents.flatMap((agent) => {
20873
21003
  const dropped = DROPPED_AGENT_FIELDS.filter(([, has]) => has(agent)).map(([field]) => field);
20874
21004
  if (dropped.length === 0) return [];
@@ -20959,7 +21089,7 @@ var init_kimi_code2 = __esm({
20959
21089
  generateHooks: generateHooks14,
20960
21090
  generatePermissions: generatePermissions16,
20961
21091
  importFrom: importFromKimiCode,
20962
- lint: lintAgents4
21092
+ lint: lintAgents5
20963
21093
  };
20964
21094
  capabilities9 = {
20965
21095
  rules: "native",
@@ -23257,7 +23387,7 @@ function lintAgentFields(agent) {
23257
23387
  )
23258
23388
  ];
23259
23389
  }
23260
- function lintAgents5(canonical) {
23390
+ function lintAgents6(canonical) {
23261
23391
  const diagnostics = [];
23262
23392
  for (const agent of canonical.agents) {
23263
23393
  diagnostics.push(...lintAgentFields(agent));
@@ -23319,7 +23449,7 @@ var init_openhands2 = __esm({
23319
23449
  generatePermissions: generatePermissions19,
23320
23450
  importFrom: importFromOpenhands,
23321
23451
  // Ungated by feature, so agent-only feature sets still get the warning.
23322
- lint: lintAgents5
23452
+ lint: lintAgents6
23323
23453
  };
23324
23454
  descriptor24 = {
23325
23455
  id: OPENHANDS_TARGET,
@@ -27134,7 +27264,7 @@ var init_constants34 = __esm({
27134
27264
  WINDSURF_GLOBAL_AGENTS_SKILLS_DIR = ".agents/skills";
27135
27265
  }
27136
27266
  });
27137
- function ruleSlug4(source) {
27267
+ function ruleSlug3(source) {
27138
27268
  const name = basename(source, ".md");
27139
27269
  return name === "_root" ? "root" : name;
27140
27270
  }
@@ -27155,7 +27285,7 @@ function generateRules32(canonical) {
27155
27285
  for (const rule of canonical.rules) {
27156
27286
  if (rule.root) continue;
27157
27287
  if (rule.targets.length > 0 && !rule.targets.includes("windsurf")) continue;
27158
- const slug = ruleSlug4(rule.source);
27288
+ const slug = ruleSlug3(rule.source);
27159
27289
  const normalizedTrigger = rule.trigger || (rule.globs.length > 0 ? "glob" : void 0);
27160
27290
  const frontmatter = {
27161
27291
  description: rule.description || void 0,
@@ -27259,19 +27389,34 @@ var init_mcp4 = __esm({
27259
27389
  }
27260
27390
  });
27261
27391
 
27262
- // src/targets/windsurf/generator/hooks.ts
27392
+ // src/targets/windsurf/hook-events.ts
27263
27393
  function windsurfEventName(event) {
27264
- const explicit = {
27265
- PreToolUse: "pre_tool_use",
27266
- PostToolUse: "post_tool_use",
27267
- Notification: "notification",
27268
- UserPromptSubmit: "user_prompt_submit",
27269
- SubagentStart: "subagent_start",
27270
- SubagentStop: "subagent_stop"
27271
- };
27272
- if (explicit[event]) return explicit[event];
27273
27394
  return event.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
27274
27395
  }
27396
+ function canonicalHookEventName(event) {
27397
+ if (KNOWN_CANONICAL_HOOK_EVENTS.includes(event)) return event;
27398
+ return WINDSURF_TO_CANONICAL.get(event) ?? null;
27399
+ }
27400
+ var KNOWN_CANONICAL_HOOK_EVENTS, WINDSURF_TO_CANONICAL;
27401
+ var init_hook_events = __esm({
27402
+ "src/targets/windsurf/hook-events.ts"() {
27403
+ init_hook_types();
27404
+ KNOWN_CANONICAL_HOOK_EVENTS = [
27405
+ "PreToolUse",
27406
+ "PostToolUse",
27407
+ "Notification",
27408
+ "UserPromptSubmit",
27409
+ "SubagentStart",
27410
+ "SubagentStop",
27411
+ ...BEST_EFFORT_HOOK_EVENTS
27412
+ ];
27413
+ WINDSURF_TO_CANONICAL = new Map(
27414
+ KNOWN_CANONICAL_HOOK_EVENTS.map((event) => [windsurfEventName(event), event])
27415
+ );
27416
+ }
27417
+ });
27418
+
27419
+ // src/targets/windsurf/generator/hooks.ts
27275
27420
  function toWindsurfHooks(hooks) {
27276
27421
  const result2 = {};
27277
27422
  for (const [event, entries] of Object.entries(hooks)) {
@@ -27299,6 +27444,7 @@ var init_hooks4 = __esm({
27299
27444
  "src/targets/windsurf/generator/hooks.ts"() {
27300
27445
  init_hook_command();
27301
27446
  init_constants34();
27447
+ init_hook_events();
27302
27448
  }
27303
27449
  });
27304
27450
 
@@ -27430,6 +27576,57 @@ var init_skills_adapter5 = __esm({
27430
27576
  init_constants34();
27431
27577
  }
27432
27578
  });
27579
+ function toHookEntry(raw) {
27580
+ if (!raw || typeof raw !== "object") return null;
27581
+ const obj = raw;
27582
+ const matcher = obj.matcher;
27583
+ if (typeof matcher !== "string") return null;
27584
+ const command = getHookText(obj);
27585
+ if (!command) return null;
27586
+ const type = typeof obj.type === "string" && VALID_TYPES.includes(obj.type) ? obj.type : void 0;
27587
+ const timeout = typeof obj.timeout === "number" && Number.isFinite(obj.timeout) ? obj.timeout : void 0;
27588
+ const prompt = getHookPrompt(obj) || void 0;
27589
+ return {
27590
+ matcher,
27591
+ command,
27592
+ ...timeout !== void 0 && { timeout },
27593
+ ...type && { type },
27594
+ ...prompt && { prompt }
27595
+ };
27596
+ }
27597
+ async function parseHooks(hooksPath, onParseError) {
27598
+ const content = await readFileSafe(hooksPath);
27599
+ if (content === null) return null;
27600
+ if (!content.trim()) return {};
27601
+ let parsed;
27602
+ try {
27603
+ parsed = parse(content);
27604
+ } catch (err) {
27605
+ return failSyntax(hooksPath, err, onParseError);
27606
+ }
27607
+ if (!parsed || typeof parsed !== "object") return null;
27608
+ const result2 = {};
27609
+ const obj = parsed;
27610
+ for (const [key, val] of Object.entries(obj)) {
27611
+ if (!Array.isArray(val)) continue;
27612
+ const entries = [];
27613
+ for (const item of val) {
27614
+ const entry = toHookEntry(item);
27615
+ if (entry) entries.push(entry);
27616
+ }
27617
+ if (entries.length > 0) result2[key] = entries;
27618
+ }
27619
+ return result2;
27620
+ }
27621
+ var VALID_TYPES;
27622
+ var init_hooks5 = __esm({
27623
+ "src/canonical/features/hooks.ts"() {
27624
+ init_syntax_error();
27625
+ init_fs();
27626
+ init_hook_command();
27627
+ VALID_TYPES = ["command", "prompt"];
27628
+ }
27629
+ });
27433
27630
  async function importWindsurfHooks(projectRoot, results) {
27434
27631
  const hooksPath = join(projectRoot, WINDSURF_HOOKS_FILE);
27435
27632
  const hooksContent = await readFileSafe(hooksPath);
@@ -27437,9 +27634,10 @@ async function importWindsurfHooks(projectRoot, results) {
27437
27634
  try {
27438
27635
  const parsed = JSON.parse(hooksContent);
27439
27636
  if (!parsed.hooks || typeof parsed.hooks !== "object" || Array.isArray(parsed.hooks)) return;
27440
- const canonical = windsurfHooksToCanonical(parsed.hooks);
27441
- if (Object.keys(canonical).length === 0) return;
27442
27637
  const destPath = join(projectRoot, WINDSURF_CANONICAL_HOOKS);
27638
+ const existing = await parseHooks(destPath) ?? {};
27639
+ const canonical = windsurfHooksToCanonical(parsed.hooks, existing);
27640
+ if (Object.keys(canonical).length === 0) return;
27443
27641
  await mkdirp(dirname(destPath));
27444
27642
  await writeFileAtomic(destPath, stringify(canonical));
27445
27643
  results.push({
@@ -27451,54 +27649,63 @@ async function importWindsurfHooks(projectRoot, results) {
27451
27649
  } catch {
27452
27650
  }
27453
27651
  }
27454
- function canonicalHookEventName(event) {
27455
- const explicit = {
27456
- pre_tool_use: "PreToolUse",
27457
- post_tool_use: "PostToolUse",
27458
- notification: "Notification",
27459
- user_prompt_submit: "UserPromptSubmit",
27460
- subagent_start: "SubagentStart",
27461
- subagent_stop: "SubagentStop"
27462
- };
27463
- return explicit[event] ?? event;
27652
+ function preservedMatcher(existing, event, command) {
27653
+ const match = existing[event]?.find((entry) => entry.command === command);
27654
+ return match?.matcher ?? WILDCARD_MATCHER;
27655
+ }
27656
+ function legacyEntries(entry) {
27657
+ const matcher = typeof entry.matcher === "string" && entry.matcher.trim() ? entry.matcher : WILDCARD_MATCHER;
27658
+ const hooksList = Array.isArray(entry.hooks) ? entry.hooks : [];
27659
+ const out2 = [];
27660
+ for (const item of hooksList) {
27661
+ if (!item || typeof item !== "object") continue;
27662
+ const hook = item;
27663
+ const command = typeof hook.command === "string" ? hook.command : typeof hook.prompt === "string" ? hook.prompt : "";
27664
+ if (!command.trim()) continue;
27665
+ const canonical = {
27666
+ matcher,
27667
+ type: hook.type === "prompt" ? "prompt" : "command",
27668
+ command
27669
+ };
27670
+ if (typeof hook.timeout === "number") canonical.timeout = hook.timeout;
27671
+ out2.push(canonical);
27672
+ }
27673
+ return out2;
27464
27674
  }
27465
- function windsurfHooksToCanonical(hooks) {
27675
+ function windsurfHooksToCanonical(hooks, existing) {
27466
27676
  const result2 = {};
27467
27677
  for (const [event, entries] of Object.entries(hooks)) {
27468
27678
  if (!Array.isArray(entries)) continue;
27469
27679
  const mappedEvent = canonicalHookEventName(event);
27680
+ if (mappedEvent === null) continue;
27470
27681
  const canonicalEntries = [];
27471
27682
  for (const entry of entries) {
27472
27683
  if (!entry || typeof entry !== "object") continue;
27473
27684
  const e = entry;
27474
27685
  if (typeof e.command === "string" && e.command.trim()) {
27475
27686
  canonicalEntries.push({
27476
- matcher: ".*",
27687
+ matcher: preservedMatcher(existing, mappedEvent, e.command),
27477
27688
  type: "command",
27478
27689
  command: e.command
27479
27690
  });
27480
27691
  continue;
27481
27692
  }
27482
- const matcher = typeof e.matcher === "string" && e.matcher.trim() ? e.matcher : ".*";
27483
- const hooksList = Array.isArray(e.hooks) ? e.hooks : [];
27484
- for (const item of hooksList) {
27485
- if (!item || typeof item !== "object") continue;
27486
- const hook = item;
27487
- const command = typeof hook.command === "string" ? hook.command : typeof hook.prompt === "string" ? hook.prompt : "";
27488
- if (!command.trim()) continue;
27489
- const canonical = {
27490
- matcher,
27491
- type: hook.type === "prompt" ? "prompt" : "command",
27492
- command
27493
- };
27494
- if (typeof hook.timeout === "number") canonical.timeout = hook.timeout;
27495
- canonicalEntries.push(canonical);
27496
- }
27693
+ canonicalEntries.push(...legacyEntries(e));
27497
27694
  }
27498
27695
  if (canonicalEntries.length > 0) result2[mappedEvent] = canonicalEntries;
27499
27696
  }
27500
27697
  return result2;
27501
27698
  }
27699
+ var WILDCARD_MATCHER;
27700
+ var init_importer_hooks2 = __esm({
27701
+ "src/targets/windsurf/importer-hooks.ts"() {
27702
+ init_hooks5();
27703
+ init_fs();
27704
+ init_constants34();
27705
+ init_hook_events();
27706
+ WILDCARD_MATCHER = "*";
27707
+ }
27708
+ });
27502
27709
  async function importWindsurfMcp(projectRoot, results) {
27503
27710
  const sourceCandidates = [WINDSURF_MCP_EXAMPLE_FILE, WINDSURF_MCP_CONFIG_FILE];
27504
27711
  for (const relPath of sourceCandidates) {
@@ -27522,8 +27729,8 @@ async function importWindsurfMcp(projectRoot, results) {
27522
27729
  }
27523
27730
  }
27524
27731
  }
27525
- var init_importer_hooks_mcp = __esm({
27526
- "src/targets/windsurf/importer-hooks-mcp.ts"() {
27732
+ var init_importer_mcp = __esm({
27733
+ "src/targets/windsurf/importer-mcp.ts"() {
27527
27734
  init_fs();
27528
27735
  init_constants34();
27529
27736
  }
@@ -27673,7 +27880,8 @@ var init_importer32 = __esm({
27673
27880
  init_constants34();
27674
27881
  init_importer_workflows();
27675
27882
  init_skills_adapter5();
27676
- init_importer_hooks_mcp();
27883
+ init_importer_hooks2();
27884
+ init_importer_mcp();
27677
27885
  }
27678
27886
  });
27679
27887
 
@@ -27758,9 +27966,28 @@ function lintPermissions22(canonical) {
27758
27966
  )
27759
27967
  ];
27760
27968
  }
27969
+ function lintHooks23(canonical) {
27970
+ if (!canonical.hooks) return [];
27971
+ const diagnostics = [];
27972
+ for (const [event, entries] of Object.entries(canonical.hooks)) {
27973
+ for (const entry of entries ?? []) {
27974
+ if (WILDCARD_MATCHERS.has(entry.matcher.trim())) continue;
27975
+ diagnostics.push(
27976
+ createWarning(
27977
+ ".agentsmesh/hooks.yaml",
27978
+ "windsurf",
27979
+ `Windsurf hooks have no matcher field; ${event} hook "${entry.command}" runs on every ${event} event (matcher "${entry.matcher}" is not projected).`
27980
+ )
27981
+ );
27982
+ }
27983
+ }
27984
+ return diagnostics;
27985
+ }
27986
+ var WILDCARD_MATCHERS;
27761
27987
  var init_lint31 = __esm({
27762
27988
  "src/targets/windsurf/lint.ts"() {
27763
27989
  init_helpers();
27990
+ WILDCARD_MATCHERS = /* @__PURE__ */ new Set(["", "*", ".*"]);
27764
27991
  }
27765
27992
  });
27766
27993
 
@@ -27916,6 +28143,7 @@ var init_windsurf2 = __esm({
27916
28143
  lintRules: lintRules32,
27917
28144
  lint: {
27918
28145
  commands: lintCommands10,
28146
+ hooks: lintHooks23,
27919
28147
  mcp: lintMcp13,
27920
28148
  permissions: lintPermissions22
27921
28149
  },
@@ -29785,6 +30013,7 @@ function ruleNameFromSource(source) {
29785
30013
  }
29786
30014
 
29787
30015
  // src/core/generate/collision.ts
30016
+ init_fs_text_encoding();
29788
30017
  init_target_ids();
29789
30018
  var AGENTS_SUFFIX = "AGENTS.md";
29790
30019
  function statusRank(status) {
@@ -29893,7 +30122,7 @@ function assertNoCaseOnlyPathCollisions(results) {
29893
30122
  }
29894
30123
  }
29895
30124
  function refreshResultStatus(result2) {
29896
- const status = result2.currentContent === void 0 ? "created" : result2.currentContent !== result2.content ? "updated" : "unchanged";
30125
+ const status = result2.currentContent === void 0 ? "created" : normalizeTextPayload(result2.path, result2.currentContent) !== normalizeTextPayload(result2.path, result2.content) ? "updated" : "unchanged";
29897
30126
  return result2.status === status ? result2 : { ...result2, status };
29898
30127
  }
29899
30128
 
@@ -30298,8 +30527,46 @@ function mergeLocalConfig(project26, local) {
30298
30527
  if (Array.isArray(local.extends) && local.extends.length > 0) {
30299
30528
  merged.extends = [...project26.extends ?? [], ...local.extends];
30300
30529
  }
30530
+ if (Array.isArray(local.plugins)) {
30531
+ merged.plugins = mergeById(project26.plugins, local.plugins);
30532
+ }
30533
+ if (Array.isArray(local.pluginTargets)) {
30534
+ merged.pluginTargets = [.../* @__PURE__ */ new Set([...project26.pluginTargets, ...local.pluginTargets])];
30535
+ }
30536
+ if (typeof local.collaboration === "object" && local.collaboration !== null && !Array.isArray(local.collaboration)) {
30537
+ merged.collaboration = local.collaboration;
30538
+ }
30539
+ warnUnhandledLocalKeys(local);
30301
30540
  return merged;
30302
30541
  }
30542
+ var LOCAL_KEYS = /* @__PURE__ */ new Set([
30543
+ "version",
30544
+ "targets",
30545
+ "features",
30546
+ "overrides",
30547
+ "conversions",
30548
+ "extends",
30549
+ "plugins",
30550
+ "pluginTargets",
30551
+ "collaboration"
30552
+ ]);
30553
+ function warnUnhandledLocalKeys(local) {
30554
+ const unknown = Object.keys(local).filter((key) => !LOCAL_KEYS.has(key));
30555
+ if (unknown.length === 0) return;
30556
+ logger.warn(
30557
+ `agentsmesh.local.yaml: ignoring unknown key(s) ${unknown.join(", ")}; supported keys are ${[...LOCAL_KEYS].join(", ")}.`
30558
+ );
30559
+ }
30560
+ function mergeById(project26, local) {
30561
+ const byId = /* @__PURE__ */ new Map();
30562
+ const anonymous = [];
30563
+ for (const entry of [...project26, ...local]) {
30564
+ const id = typeof entry === "object" && entry !== null && typeof entry.id === "string" ? entry.id : void 0;
30565
+ if (id === void 0) anonymous.push(entry);
30566
+ else byId.set(id, entry);
30567
+ }
30568
+ return [...byId.values(), ...anonymous];
30569
+ }
30303
30570
  async function loadConfigFromExactDir(configDir) {
30304
30571
  const configPath = join(configDir, CONFIG_FILENAME);
30305
30572
  let config = await loadConfig(configPath);
@@ -30788,24 +31055,14 @@ async function sweepStaleCache(cacheDir, maxAgeMs) {
30788
31055
 
30789
31056
  // src/config/remote/remote-fetcher.ts
30790
31057
  var MAX_CACHE_KEY_LENGTH = 80;
31058
+ var CACHE_KEY_HASH_LENGTH = 12;
30791
31059
  function buildCacheKey(provider, identifier, ref) {
30792
31060
  const safe = (value) => value.replace(/[^a-zA-Z0-9_.-]/g, "_").replace(/^\.+/, "_");
30793
- let key;
30794
- if (provider === "github") {
30795
- const [org, repo] = identifier.split("/", 2);
30796
- if (org && repo) {
30797
- key = `${safe(org)}--${safe(repo)}--${safe(ref)}`;
30798
- } else {
30799
- key = `${safe(provider)}__${safe(identifier)}__${safe(ref)}`;
30800
- }
30801
- } else {
30802
- key = `${safe(provider)}__${safe(identifier)}__${safe(ref)}`;
30803
- }
30804
- if (key.length > MAX_CACHE_KEY_LENGTH) {
30805
- const hash = createHash("sha256").update(key).digest("hex").slice(0, 16);
30806
- key = `${key.slice(0, MAX_CACHE_KEY_LENGTH - 18)}--${hash}`;
30807
- }
30808
- return key;
31061
+ const [org, repo] = provider === "github" ? identifier.split("/", 2) : [];
31062
+ const readable = org && repo ? `${safe(org)}--${safe(repo)}--${safe(ref)}` : `${safe(provider)}__${safe(identifier)}__${safe(ref)}`;
31063
+ const hash = createHash("sha256").update(`${provider}|${identifier}|${ref}`).digest("hex").slice(0, CACHE_KEY_HASH_LENGTH);
31064
+ const maxReadable = MAX_CACHE_KEY_LENGTH - CACHE_KEY_HASH_LENGTH - 2;
31065
+ return `${readable.slice(0, maxReadable)}--${hash}`;
30809
31066
  }
30810
31067
  function getCacheDir() {
30811
31068
  const env = process.env.AGENTSMESH_CACHE;
@@ -30917,6 +31174,13 @@ async function resolveExtendPaths(config, configDir, options = {}) {
30917
31174
  return result2;
30918
31175
  }
30919
31176
 
31177
+ // src/canonical/features/empty-file.ts
31178
+ function isEmptyCanonicalFile(content, path) {
31179
+ if (content.trim() !== "") return false;
31180
+ logger.warn(`Skipping empty canonical file ${path.replaceAll("\\", "/")}`);
31181
+ return true;
31182
+ }
31183
+
30920
31184
  // src/canonical/features/rules.ts
30921
31185
  init_fs();
30922
31186
  init_markdown();
@@ -31056,7 +31320,8 @@ async function parseRules(rulesDir, opts = {}) {
31056
31320
  const rules = [];
31057
31321
  for (const path of mdFiles) {
31058
31322
  const content = await readFileSafe(path);
31059
- if (!content) continue;
31323
+ if (content === null) continue;
31324
+ if (isEmptyCanonicalFile(content, path)) continue;
31060
31325
  const parsed = parseOrSkipFrontmatter(content, path, opts.onParseError);
31061
31326
  if (!parsed) continue;
31062
31327
  const { frontmatter, body } = parsed;
@@ -31110,7 +31375,8 @@ async function parseCommands(commandsDir, opts = {}) {
31110
31375
  const commands = [];
31111
31376
  for (const path of mdFiles) {
31112
31377
  const content = await readFileSafe(path);
31113
- if (!content) continue;
31378
+ if (content === null) continue;
31379
+ if (isEmptyCanonicalFile(content, path)) continue;
31114
31380
  const parsed = parseOrSkipFrontmatter(content, path, opts.onParseError);
31115
31381
  if (!parsed) continue;
31116
31382
  const { frontmatter, body } = parsed;
@@ -31170,15 +31436,14 @@ async function parseAgents(agentsDir, opts = {}) {
31170
31436
  const agents = [];
31171
31437
  for (const path of mdFiles) {
31172
31438
  const content = await readFileSafe(path);
31173
- if (!content) continue;
31439
+ if (content === null) continue;
31440
+ if (isEmptyCanonicalFile(content, path)) continue;
31174
31441
  const parsed = parseOrSkipFrontmatter(content, path, opts.onParseError);
31175
31442
  if (!parsed) continue;
31176
31443
  const { frontmatter, body } = parsed;
31177
31444
  const name = basename(path, ".md");
31178
31445
  assertCanonicalName("agent", name);
31179
- const toolsCamel = toStrArray2(frontmatter.tools);
31180
- const toolsKebab = toStrArray2(frontmatter["tools"]);
31181
- const tools = toolsCamel.length > 0 ? toolsCamel : toolsKebab;
31446
+ const tools = toStrArray2(frontmatter.tools);
31182
31447
  const disallowedCamel = toStrArray2(frontmatter.disallowedTools);
31183
31448
  const disallowedKebab = toStrArray2(frontmatter["disallowed-tools"]);
31184
31449
  const disallowedTools = disallowedCamel.length > 0 ? disallowedCamel : disallowedKebab;
@@ -31300,20 +31565,21 @@ async function parseSkills(skillsDir, opts = {}) {
31300
31565
  init_mcp();
31301
31566
 
31302
31567
  // src/canonical/features/permissions.ts
31568
+ init_syntax_error();
31303
31569
  init_fs();
31304
31570
  function ensureStringArray(val) {
31305
31571
  if (!Array.isArray(val)) return [];
31306
31572
  return val.filter((x) => typeof x === "string");
31307
31573
  }
31308
- async function parsePermissions(permissionsPath) {
31574
+ async function parsePermissions(permissionsPath, onParseError) {
31309
31575
  const content = await readFileSafe(permissionsPath);
31310
31576
  if (content === null) return null;
31311
31577
  if (!content.trim()) return { allow: [], deny: [], ask: [] };
31312
31578
  let parsed;
31313
31579
  try {
31314
31580
  parsed = parse(content);
31315
- } catch {
31316
- return null;
31581
+ } catch (err) {
31582
+ return failSyntax(permissionsPath, err, onParseError);
31317
31583
  }
31318
31584
  if (!parsed || typeof parsed !== "object") return null;
31319
31585
  const obj = parsed;
@@ -31323,52 +31589,8 @@ async function parsePermissions(permissionsPath) {
31323
31589
  return { allow, deny, ask };
31324
31590
  }
31325
31591
 
31326
- // src/canonical/features/hooks.ts
31327
- init_fs();
31328
- init_hook_command();
31329
- var VALID_TYPES = ["command", "prompt"];
31330
- function toHookEntry(raw) {
31331
- if (!raw || typeof raw !== "object") return null;
31332
- const obj = raw;
31333
- const matcher = obj.matcher;
31334
- if (typeof matcher !== "string") return null;
31335
- const command = getHookText(obj);
31336
- if (!command) return null;
31337
- const type = typeof obj.type === "string" && VALID_TYPES.includes(obj.type) ? obj.type : void 0;
31338
- const timeout = typeof obj.timeout === "number" && Number.isFinite(obj.timeout) ? obj.timeout : void 0;
31339
- const prompt = getHookPrompt(obj) || void 0;
31340
- return {
31341
- matcher,
31342
- command,
31343
- ...timeout !== void 0 && { timeout },
31344
- ...type && { type },
31345
- ...prompt && { prompt }
31346
- };
31347
- }
31348
- async function parseHooks(hooksPath) {
31349
- const content = await readFileSafe(hooksPath);
31350
- if (content === null) return null;
31351
- if (!content.trim()) return {};
31352
- let parsed;
31353
- try {
31354
- parsed = parse(content);
31355
- } catch {
31356
- return null;
31357
- }
31358
- if (!parsed || typeof parsed !== "object") return null;
31359
- const result2 = {};
31360
- const obj = parsed;
31361
- for (const [key, val] of Object.entries(obj)) {
31362
- if (!Array.isArray(val)) continue;
31363
- const entries = [];
31364
- for (const item of val) {
31365
- const entry = toHookEntry(item);
31366
- if (entry) entries.push(entry);
31367
- }
31368
- if (entries.length > 0) result2[key] = entries;
31369
- }
31370
- return result2;
31371
- }
31592
+ // src/canonical/load/loader.ts
31593
+ init_hooks5();
31372
31594
 
31373
31595
  // src/canonical/features/ignore.ts
31374
31596
  init_fs();
@@ -31395,9 +31617,9 @@ async function loadCanonicalFiles(canonicalDirOrProjectRoot, opts = {}) {
31395
31617
  parseCommands(join(canonicalDir, "commands"), opts),
31396
31618
  parseAgents(join(canonicalDir, "agents"), opts),
31397
31619
  parseSkills(join(canonicalDir, "skills"), opts),
31398
- parseMcp(join(canonicalDir, "mcp.json")),
31399
- parsePermissions(join(canonicalDir, "permissions.yaml")),
31400
- parseHooks(join(canonicalDir, "hooks.yaml")),
31620
+ parseMcp(join(canonicalDir, "mcp.json"), opts.onParseError),
31621
+ parsePermissions(join(canonicalDir, "permissions.yaml"), opts.onParseError),
31622
+ parseHooks(join(canonicalDir, "hooks.yaml"), opts.onParseError),
31401
31623
  parseIgnore(join(canonicalDir, "ignore"))
31402
31624
  ]);
31403
31625
  return {
@@ -31411,13 +31633,13 @@ async function loadCanonicalFiles(canonicalDirOrProjectRoot, opts = {}) {
31411
31633
  ignore
31412
31634
  };
31413
31635
  }
31414
- function ruleSlug5(r) {
31636
+ function ruleSlug4(r) {
31415
31637
  return basename(r.source, ".md");
31416
31638
  }
31417
31639
  function mergeCanonicalFiles(base, overlay) {
31418
- const baseRuleMap = new Map(base.rules.map((r) => [ruleSlug5(r), r]));
31640
+ const baseRuleMap = new Map(base.rules.map((r) => [ruleSlug4(r), r]));
31419
31641
  for (const r of overlay.rules) {
31420
- baseRuleMap.set(ruleSlug5(r), r);
31642
+ baseRuleMap.set(ruleSlug4(r), r);
31421
31643
  }
31422
31644
  const baseCmdMap = new Map(base.commands.map((c2) => [c2.name, c2]));
31423
31645
  for (const c2 of overlay.commands) {
@@ -32300,6 +32522,7 @@ function gateExtendElevatedArtifacts(canonical, ext) {
32300
32522
  });
32301
32523
  }
32302
32524
  init_mcp();
32525
+ init_hooks5();
32303
32526
 
32304
32527
  // src/install/pack/pack-reader.ts
32305
32528
  init_fs();
@@ -33026,47 +33249,6 @@ function collectOrphans(graph, findings) {
33026
33249
  }
33027
33250
  }
33028
33251
 
33029
- // src/lessons/ranking-text.ts
33030
- var STOP = /* @__PURE__ */ new Set([
33031
- "the",
33032
- "a",
33033
- "an",
33034
- "to",
33035
- "of",
33036
- "in",
33037
- "and",
33038
- "or",
33039
- "for",
33040
- "is",
33041
- "on",
33042
- "at",
33043
- "with",
33044
- "be",
33045
- "as",
33046
- "it",
33047
- "that",
33048
- "this",
33049
- "its",
33050
- "must"
33051
- ]);
33052
- function tokenize(text) {
33053
- return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
33054
- }
33055
-
33056
- // src/lessons/keyword-signal.ts
33057
- var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
33058
- function isLowSignalKeyword(pattern) {
33059
- return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
33060
- }
33061
- function splitRawTokens(pattern) {
33062
- return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
33063
- }
33064
- function keywordNeedleLosesTokens(pattern) {
33065
- const raw = splitRawTokens(pattern);
33066
- if (raw.length < 2) return false;
33067
- return tokenize(pattern).length !== raw.length;
33068
- }
33069
-
33070
33252
  // src/lessons/regex-linear/nfa-compile.ts
33071
33253
  var MAX_NFA_STATES = 2e3;
33072
33254
  var Builder = class {
@@ -33479,6 +33661,10 @@ function isSafeRegexPattern(pattern) {
33479
33661
  if (pattern.length > MAX_PATTERN_LENGTH) return false;
33480
33662
  return compileLinearMatcher(pattern) !== null;
33481
33663
  }
33664
+ function getCommandMatcher(pattern) {
33665
+ if (pattern.length > MAX_PATTERN_LENGTH) return null;
33666
+ return compileLinearMatcher(pattern);
33667
+ }
33482
33668
 
33483
33669
  // src/lessons/validate-quality.ts
33484
33670
  function collectDuplicateRules(graph, findings) {
@@ -33583,6 +33769,52 @@ function collectFanout(graph, findings) {
33583
33769
  });
33584
33770
  }
33585
33771
  }
33772
+ function normalizeRule(rule) {
33773
+ return rule.trim().replace(/\s+/g, " ").toLowerCase();
33774
+ }
33775
+
33776
+ // src/lessons/ranking-text.ts
33777
+ var STOP = /* @__PURE__ */ new Set([
33778
+ "the",
33779
+ "a",
33780
+ "an",
33781
+ "to",
33782
+ "of",
33783
+ "in",
33784
+ "and",
33785
+ "or",
33786
+ "for",
33787
+ "is",
33788
+ "on",
33789
+ "at",
33790
+ "with",
33791
+ "be",
33792
+ "as",
33793
+ "it",
33794
+ "that",
33795
+ "this",
33796
+ "its",
33797
+ "must"
33798
+ ]);
33799
+ function tokenize(text) {
33800
+ return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 2 && !STOP.has(t));
33801
+ }
33802
+
33803
+ // src/lessons/keyword-signal.ts
33804
+ var MAX_RECOMMENDED_KEYWORD_TOKENS = 5;
33805
+ function isLowSignalKeyword(pattern) {
33806
+ return tokenize(pattern).length > MAX_RECOMMENDED_KEYWORD_TOKENS;
33807
+ }
33808
+ function splitRawTokens(pattern) {
33809
+ return pattern.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 0);
33810
+ }
33811
+ function keywordNeedleLosesTokens(pattern) {
33812
+ const raw = splitRawTokens(pattern);
33813
+ if (raw.length < 2) return false;
33814
+ return tokenize(pattern).length !== raw.length;
33815
+ }
33816
+
33817
+ // src/lessons/validate-keywords.ts
33586
33818
  function collectLowSignalKeywords(graph, findings) {
33587
33819
  const activeTriggerIds2 = /* @__PURE__ */ new Set();
33588
33820
  for (const lesson of Object.values(graph.lessons)) {
@@ -33596,7 +33828,7 @@ function collectLowSignalKeywords(graph, findings) {
33596
33828
  findings.push({
33597
33829
  level: "warning",
33598
33830
  code: "LOW_SIGNAL_KEYWORD",
33599
- 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.`,
33831
+ message: `Keyword trigger "${triggerId}" carries more than ${MAX_RECOMMENDED_KEYWORD_TOKENS} tokens (${trigger.pattern}); recall matches a keyword only as a contiguous token-run in --keyword or the file/command, so it rarely fires \u2014 use a short distinctive phrase.`,
33600
33832
  triggerId
33601
33833
  });
33602
33834
  }
@@ -33621,9 +33853,62 @@ function collectStopwordKeywords(graph, findings) {
33621
33853
  });
33622
33854
  }
33623
33855
  }
33624
- function normalizeRule(rule) {
33625
- return rule.trim().replace(/\s+/g, " ").toLowerCase();
33856
+
33857
+ // src/lessons/glob-breadth.ts
33858
+ var WILDCARD = /[*?[\]]/;
33859
+ function globNarrowness(pattern) {
33860
+ const segments = pattern.replaceAll("\\", "/").split("/").filter((segment) => segment !== "" && segment !== ".");
33861
+ if (segments.length === 0) return 0;
33862
+ let literal = 0;
33863
+ let globstars = 0;
33864
+ for (const segment of segments) {
33865
+ if (segment === "**") globstars += 1;
33866
+ else if (!WILDCARD.test(segment)) literal += 1;
33867
+ }
33868
+ return literal / (segments.length + globstars);
33869
+ }
33870
+ var BROAD_GLOB_NARROWNESS = 0.34;
33871
+ function isBroadFileGlob(pattern) {
33872
+ return globNarrowness(pattern) < BROAD_GLOB_NARROWNESS;
33873
+ }
33874
+
33875
+ // src/lessons/command-pattern-breadth.ts
33876
+ var COMMAND_PROBE_CORPUS = [
33877
+ "git status",
33878
+ 'git commit -m "wip"',
33879
+ "pnpm test",
33880
+ "npx vitest run src/x.test.ts",
33881
+ "ls -la",
33882
+ "cat README.md",
33883
+ "rm -rf dist",
33884
+ "mkdir -p build/out",
33885
+ "node scripts/build.js",
33886
+ "docker compose up -d",
33887
+ "curl -s https://example.com",
33888
+ "echo hello > out.txt",
33889
+ "sed -i 's/a/b/' file.txt",
33890
+ "pnpm lint --fix",
33891
+ "python3 -m pytest",
33892
+ "cargo build --release",
33893
+ "make",
33894
+ "npm install --global typescript",
33895
+ "cp a.txt b.txt",
33896
+ "grep -rn TODO src"
33897
+ ];
33898
+ var BROAD_HIT_RATIO = 0.5;
33899
+ var PROBE_BUDGET = 1e5;
33900
+ function isBroadCommandPattern(pattern) {
33901
+ const matcher = getCommandMatcher(pattern);
33902
+ if (matcher === null) return false;
33903
+ if (matcher.test("", { remaining: PROBE_BUDGET })) return true;
33904
+ let hits = 0;
33905
+ for (const command of COMMAND_PROBE_CORPUS) {
33906
+ if (matcher.test(command, { remaining: PROBE_BUDGET })) hits += 1;
33907
+ }
33908
+ return hits > COMMAND_PROBE_CORPUS.length * BROAD_HIT_RATIO;
33626
33909
  }
33910
+
33911
+ // src/lessons/validate-liveness.ts
33627
33912
  function activeTriggerIds(graph) {
33628
33913
  const ids = /* @__PURE__ */ new Set();
33629
33914
  for (const lesson of Object.values(graph.lessons)) {
@@ -33669,6 +33954,34 @@ function collectRunnerAnchoredPatterns(graph, findings) {
33669
33954
  });
33670
33955
  }
33671
33956
  }
33957
+ function collectBroadFileGlobs(graph, findings) {
33958
+ const active = activeTriggerIds(graph);
33959
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33960
+ if (trigger.kind !== "file_glob") continue;
33961
+ if (!active.has(triggerId)) continue;
33962
+ if (!isBroadFileGlob(trigger.pattern)) continue;
33963
+ findings.push({
33964
+ level: "warning",
33965
+ code: "BROAD_FILE_GLOB",
33966
+ message: `file_glob trigger "${triggerId}" (${trigger.pattern}) matches most of the repository, so it outranks nothing and crowds the recall budget. Narrow it to the directory or file class the rule is really about, or detach it with \`lessons untrigger\`.`,
33967
+ triggerId
33968
+ });
33969
+ }
33970
+ }
33971
+ function collectBroadCommandPatterns(graph, findings) {
33972
+ const active = activeTriggerIds(graph);
33973
+ for (const [triggerId, trigger] of Object.entries(graph.triggers)) {
33974
+ if (trigger.kind !== "command_pattern") continue;
33975
+ if (!active.has(triggerId)) continue;
33976
+ if (!isBroadCommandPattern(trigger.pattern)) continue;
33977
+ findings.push({
33978
+ level: "warning",
33979
+ code: "BROAD_COMMAND_PATTERN",
33980
+ message: `command_pattern trigger "${triggerId}" (${trigger.pattern}) matches nearly every command, so the lesson fires on every recall. Key it on the action (e.g. \`\\bgit commit\\b\`), or detach it with \`lessons untrigger\`.`,
33981
+ triggerId
33982
+ });
33983
+ }
33984
+ }
33672
33985
 
33673
33986
  // src/lessons/validate.ts
33674
33987
  function validateLessonsGraph(graph, options = {}) {
@@ -33696,6 +34009,8 @@ function validateLessonsGraph(graph, options = {}) {
33696
34009
  collectLowSignalKeywords(graph, findings);
33697
34010
  collectStopwordKeywords(graph, findings);
33698
34011
  collectRunnerAnchoredPatterns(graph, findings);
34012
+ collectBroadCommandPatterns(graph, findings);
34013
+ collectBroadFileGlobs(graph, findings);
33699
34014
  if (options.knownPaths !== void 0) collectDeadFileGlobs(graph, findings, options.knownPaths);
33700
34015
  const ok = findings.every((f) => f.level !== "error");
33701
34016
  return { ok, findings };
@@ -33747,13 +34062,13 @@ function diag(level, file, message) {
33747
34062
 
33748
34063
  // src/core/lint/linter.ts
33749
34064
  var EXCLUDE_DIRS = ["node_modules", ".git", "dist", "coverage", ".agentsmesh"];
34065
+ function isExcludedProjectPath(rel2) {
34066
+ const posix9 = rel2.replaceAll("\\", "/");
34067
+ return EXCLUDE_DIRS.some((d) => posix9.includes(`/${d}/`) || posix9.startsWith(`${d}/`));
34068
+ }
33750
34069
  async function getProjectFiles(projectRoot) {
33751
34070
  const all = await readDirRecursive(projectRoot);
33752
- const filtered = all.filter((p) => {
33753
- const rel2 = relative(projectRoot, p);
33754
- return !EXCLUDE_DIRS.some((d) => rel2.includes(`/${d}/`) || rel2.startsWith(`${d}/`));
33755
- });
33756
- return filtered.map((p) => relative(projectRoot, p));
34071
+ return all.filter((p) => !isExcludedProjectPath(relative(projectRoot, p))).map((p) => relative(projectRoot, p));
33757
34072
  }
33758
34073
  async function runLint(config, canonical, projectRoot, targetFilter, options = {}) {
33759
34074
  const scope = options.scope ?? "project";
@@ -34015,6 +34330,7 @@ async function diffOutputChecksums(rootBase, lockOutputs) {
34015
34330
  // src/core/generate/stale-cleanup.ts
34016
34331
  init_fs();
34017
34332
  init_builtin_targets();
34333
+ init_registry();
34018
34334
  async function listFiles2(root, base = root) {
34019
34335
  const entries = await readdir(root, { withFileTypes: true });
34020
34336
  const files = [];
@@ -34035,6 +34351,11 @@ function retainedDirs(inactiveTargets, scope) {
34035
34351
  }
34036
34352
  return dirs;
34037
34353
  }
34354
+ function primaryEmitted(target34, scope, expected) {
34355
+ const descriptor34 = getBuiltinTargetDefinition(target34) ?? getDescriptor(target34);
34356
+ const primary = getTargetLayout(target34, scope)?.rootInstructionPath ?? descriptor34?.generators.primaryRootInstructionPath;
34357
+ return primary !== void 0 && expected.has(primary);
34358
+ }
34038
34359
  async function findStaleGeneratedOutputs(args) {
34039
34360
  const expected = new Set(args.expectedPaths);
34040
34361
  const stale = /* @__PURE__ */ new Set();
@@ -34046,7 +34367,13 @@ async function findStaleGeneratedOutputs(args) {
34046
34367
  const managed = getTargetManagedOutputs(target34, scope);
34047
34368
  if (!managed) continue;
34048
34369
  for (const file of managed.coOwnedFiles ?? []) coOwned.add(file);
34049
- for (const file of managed.files) stale.add(file);
34370
+ for (const file of managed.files) {
34371
+ if (generated !== null && !generated.has(file)) continue;
34372
+ stale.add(file);
34373
+ }
34374
+ if (primaryEmitted(target34, scope, expected)) {
34375
+ for (const file of managed.supersededFiles ?? []) stale.add(file);
34376
+ }
34050
34377
  for (const dir of managed.dirs) {
34051
34378
  if (retained.has(dir)) continue;
34052
34379
  const absDir = join(args.projectRoot, dir);