@chimix/agentscan 2.0.0 → 2.3.1

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +54 -22
  3. package/dist/cli.js +1017 -265
  4. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -6952,41 +6952,56 @@ import { resolve as resolve13 } from "node:path";
6952
6952
 
6953
6953
  // src/checks/spec-pages.ts
6954
6954
  var SPEC_PAGES = {
6955
- agentSkills: { url: "https://agentskills.io/specification", lastVerified: "2026-09-03" },
6956
- claudeHooks: { url: "https://code.claude.com/docs/en/hooks", lastVerified: "2026-09-03" },
6957
- claudeSubagents: { url: "https://code.claude.com/docs/en/sub-agents", lastVerified: "2026-09-02" },
6958
- claudeSkills: { url: "https://code.claude.com/docs/en/skills", lastVerified: "2026-09-03" },
6959
- claudeMcp: { url: "https://code.claude.com/docs/en/mcp", lastVerified: "2026-09-03" },
6960
- claudeMemory: { url: "https://code.claude.com/docs/en/memory", lastVerified: "2026-09-03" },
6961
- opencodeMcp: { url: "https://opencode.ai/v2/docs/mcp-servers", lastVerified: "2026-09-03" },
6962
- copilotHooks: { url: "https://docs.github.com/en/copilot/reference/hooks-reference", lastVerified: "2026-09-02" },
6963
- grokHooks: { url: "https://docs.x.ai/build/features/hooks", lastVerified: "2026-09-03" },
6964
- grokMcp: { url: "https://docs.x.ai/build/features/mcp-servers", lastVerified: "2026-09-02" },
6965
- grokSkills: { url: "https://docs.x.ai/build/features/skills-plugins-marketplaces", lastVerified: "2026-09-02" },
6955
+ agentSkills: { url: "https://agentskills.io/specification", lastVerified: "2026-11-14" },
6956
+ claudeHooks: { url: "https://code.claude.com/docs/en/hooks", lastVerified: "2026-11-14" },
6957
+ claudeSubagents: { url: "https://code.claude.com/docs/en/sub-agents", lastVerified: "2026-11-13" },
6958
+ claudeSkills: { url: "https://code.claude.com/docs/en/skills", lastVerified: "2026-11-14" },
6959
+ claudeMcp: { url: "https://code.claude.com/docs/en/mcp", lastVerified: "2026-11-14" },
6960
+ claudeMemory: { url: "https://code.claude.com/docs/en/memory", lastVerified: "2026-11-14" },
6961
+ opencodeMcp: { url: "https://opencode.ai/v2/docs/mcp-servers", lastVerified: "2026-11-14" },
6962
+ copilotHooks: { url: "https://docs.github.com/en/copilot/reference/hooks-reference", lastVerified: "2026-11-14" },
6963
+ grokHooks: { url: "https://docs.x.ai/build/features/hooks", lastVerified: "2026-11-14" },
6964
+ grokMcp: { url: "https://docs.x.ai/build/features/mcp-servers", lastVerified: "2026-11-14" },
6965
+ grokSkills: { url: "https://docs.x.ai/build/features/skills-plugins-marketplaces", lastVerified: "2026-11-14" },
6966
6966
  commandcodeHooks: { url: "https://commandcode.ai/docs/hooks", lastVerified: "2026-09-02" },
6967
- commandcodeMcp: { url: "https://commandcode.ai/docs/mcp", lastVerified: "2026-09-02" },
6968
- commandcodeAgents: { url: "https://commandcode.ai/docs/agents", lastVerified: "2026-09-03" },
6969
- kiroHooks: { url: "https://kiro.dev/docs/hooks/", lastVerified: "2026-09-07" },
6970
- kiroHooksV1: { url: "https://kiro.dev/docs/ide/whats-new-v1/hooks/", lastVerified: "2026-09-07" },
6967
+ commandcodeMcp: { url: "https://commandcode.ai/docs/mcp", lastVerified: "2026-11-14" },
6968
+ commandcodeAgents: { url: "https://commandcode.ai/docs/agents", lastVerified: "2026-11-14" },
6969
+ kiroHooks: { url: "https://kiro.dev/docs/hooks/", lastVerified: "2026-11-14" },
6970
+ kiroHooksV1: { url: "https://kiro.dev/docs/ide/whats-new-v1/hooks/", lastVerified: "2026-11-14" },
6971
6971
  geminiHooks: { url: "https://geminicli.com/docs/hooks/", lastVerified: "2026-09-03" },
6972
- geminiMcp: { url: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md", lastVerified: "2026-09-03" },
6973
- vscodeHooks: { url: "https://code.visualstudio.com/docs/agents/reference/hooks-reference", lastVerified: "2026-11-12" },
6972
+ geminiMcp: { url: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md", lastVerified: "2026-11-14" },
6973
+ vscodeHooks: { url: "https://code.visualstudio.com/docs/agents/reference/hooks-reference", lastVerified: "2026-11-14" },
6974
6974
  vscodeMcp: { url: "https://code.visualstudio.com/docs/agents/reference/mcp-configuration", lastVerified: "2026-11-12" },
6975
- windsurfRules: { url: "https://docs.windsurf.com/windsurf/cascade/memories", lastVerified: "2026-09-02" },
6976
- windsurfMcp: { url: "https://docs.windsurf.com/windsurf/cascade/mcp", lastVerified: "2026-09-03" },
6977
- windsurfHooks: { url: "https://docs.devin.ai/desktop/cascade/hooks", lastVerified: "2026-09-03" },
6978
- cursorHooks: { url: "https://cursor.com/docs/hooks", lastVerified: "2026-09-03" },
6979
- cursorRules: { url: "https://cursor.com/docs/rules", lastVerified: "2026-09-03" },
6980
- cursorMcp: { url: "https://cursor.com/docs/context/mcp", lastVerified: "2026-09-02" },
6975
+ copilotCliConfigDir: { url: "https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference", lastVerified: "2026-11-14" },
6976
+ windsurfRules: { url: "https://docs.windsurf.com/windsurf/cascade/memories", lastVerified: "2026-11-14" },
6977
+ windsurfMcp: { url: "https://docs.windsurf.com/windsurf/cascade/mcp", lastVerified: "2026-11-14" },
6978
+ windsurfHooks: { url: "https://docs.devin.ai/desktop/cascade/hooks", lastVerified: "2026-11-14" },
6979
+ cursorHooks: { url: "https://cursor.com/docs/hooks", lastVerified: "2026-11-14" },
6980
+ cursorRules: { url: "https://cursor.com/docs/rules", lastVerified: "2026-11-14" },
6981
+ cursorMcp: { url: "https://cursor.com/docs/context/mcp", lastVerified: "2026-11-14" },
6981
6982
  continueMcp: { url: "https://docs.continue.dev/customize/deep-dives/mcp", lastVerified: "2026-09-02" },
6982
- codexMcp: { url: "https://learn.chatgpt.com/docs/extend/mcp", lastVerified: "2026-09-03" },
6983
- codexAgentsMd: { url: "https://learn.chatgpt.com/docs/agent-configuration/agents-md", lastVerified: "2026-09-02" },
6983
+ codexMcp: { url: "https://learn.chatgpt.com/docs/extend/mcp", lastVerified: "2026-11-14" },
6984
+ codexAgentsMd: { url: "https://learn.chatgpt.com/docs/agent-configuration/agents-md", lastVerified: "2026-11-14" },
6984
6985
  codexHooks: { url: "https://learn.chatgpt.com/docs/hooks", lastVerified: "2026-11-12" },
6985
- antigravityMcp: { url: "https://antigravity.google/docs/mcp", lastVerified: "2026-11-12" },
6986
+ codexAgents: { url: "https://learn.chatgpt.com/docs/agent-configuration/subagents", lastVerified: "2026-11-14" },
6987
+ antigravityMcp: { url: "https://antigravity.google/docs/mcp", lastVerified: "2026-11-14" },
6986
6988
  antigravityHooks: { url: "https://antigravity.google/docs/hooks", lastVerified: "2026-11-12" },
6987
6989
  antigravityRules: { url: "https://antigravity.google/docs/rules", lastVerified: "2026-11-12" },
6988
- devinHooks: { url: "https://docs.devin.ai/cli/extensibility/hooks/overview", lastVerified: "2026-11-12" },
6989
- devinMcp: { url: "https://docs.devin.ai/cli/extensibility/mcp/configuration", lastVerified: "2026-11-12" },
6990
+ devinHooks: { url: "https://docs.devin.ai/cli/extensibility/hooks/overview", lastVerified: "2026-11-14" },
6991
+ devinMcp: { url: "https://docs.devin.ai/cli/extensibility/mcp/configuration", lastVerified: "2026-11-14" },
6992
+ zedMcp: { url: "https://zed.dev/docs/ai/mcp", lastVerified: "2026-11-14" },
6993
+ ampMcp: { url: "https://ampcode.com/docs/customize/mcp", lastVerified: "2026-11-14" },
6994
+ claudeDesktopMcp: { url: "https://modelcontextprotocol.io/docs/develop/connect-local-servers", lastVerified: "2026-11-14" },
6995
+ claudeDesktopExtensions: {
6996
+ url: "https://raw.githubusercontent.com/anthropics/mcpb/main/MANIFEST.md",
6997
+ lastVerified: "2026-11-14"
6998
+ },
6999
+ qwenHooks: { url: "https://qwenlm.github.io/qwen-code-docs/en/users/features/hooks/", lastVerified: "2026-11-14" },
7000
+ qwenMcp: { url: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/", lastVerified: "2026-11-14" },
7001
+ factoryHooks: { url: "https://docs.factory.com/harness/hooks", lastVerified: "2026-11-14" },
7002
+ factoryMcp: { url: "https://docs.factory.com/harness/mcp", lastVerified: "2026-11-14" },
7003
+ factoryAgents: { url: "https://docs.factory.com/harness/subagents", lastVerified: "2026-11-14" },
7004
+ gooseMcp: { url: "https://goose-docs.ai/docs/guides/config-files/", lastVerified: "2026-11-14" },
6990
7005
  vscodeHooksGuide: { url: "https://code.visualstudio.com/docs/agent-customization/hooks", lastVerified: "2026-09-02" }
6991
7006
  };
6992
7007
 
@@ -6998,7 +7013,7 @@ var STRUCTURAL_CHECKS = [
6998
7013
  {
6999
7014
  id: "config.unreadable",
7000
7015
  description: "Config file is not valid JSON or cannot be read",
7001
- severity: "error",
7016
+ severity: ["error", "info"],
7002
7017
  provenance: "internal-consistency",
7003
7018
  lastVerified: "2026-09-02",
7004
7019
  source: { kind: "derived", detail: "parser result" }
@@ -7114,7 +7129,7 @@ var STRUCTURAL_CHECKS = [
7114
7129
  {
7115
7130
  id: "copilot.hook.unknown-event",
7116
7131
  description: "Copilot CLI hook registered under an event name that is never dispatched",
7117
- severity: "error",
7132
+ severity: ["error", "warning"],
7118
7133
  provenance: "spec-required",
7119
7134
  ...spec(SPEC_PAGES.copilotHooks, "copilot-hooks.md")
7120
7135
  },
@@ -7177,7 +7192,7 @@ var STRUCTURAL_CHECKS = [
7177
7192
  },
7178
7193
  {
7179
7194
  id: "claude.skill.missing-description",
7180
- description: "SKILL.md frontmatter has no description and no first markdown paragraph",
7195
+ description: "SKILL.md frontmatter has no description and no first non-empty markdown line",
7181
7196
  severity: "info",
7182
7197
  provenance: "vendor-recommendation",
7183
7198
  ...spec(SPEC_PAGES.claudeSkills, "skills.md")
@@ -7335,8 +7350,8 @@ var STRUCTURAL_CHECKS = [
7335
7350
  },
7336
7351
  {
7337
7352
  id: "claude.agent.invalid-name",
7338
- description: "Agent frontmatter name is not a valid identifier",
7339
- severity: ["warning", "error"],
7353
+ description: "Agent frontmatter name is one Claude Code skips",
7354
+ severity: "error",
7340
7355
  provenance: "spec-required",
7341
7356
  ...spec(SPEC_PAGES.claudeSubagents, "agents.md")
7342
7357
  },
@@ -7737,7 +7752,7 @@ var STRUCTURAL_CHECKS = [
7737
7752
  {
7738
7753
  id: "windsurf.rule.missing-trigger",
7739
7754
  description: "Windsurf workspace rule is missing frontmatter trigger",
7740
- severity: "warning",
7755
+ severity: "info",
7741
7756
  provenance: "spec-required",
7742
7757
  ...spec(SPEC_PAGES.windsurfRules, "windsurf-rules.md")
7743
7758
  },
@@ -7792,8 +7807,8 @@ var STRUCTURAL_CHECKS = [
7792
7807
  },
7793
7808
  {
7794
7809
  id: "windsurf.hook.unknown-event",
7795
- description: "Windsurf hook registered under an event name that is never dispatched",
7796
- severity: "error",
7810
+ description: "Windsurf hook registered under an event name outside the legacy Cascade event table",
7811
+ severity: "warning",
7797
7812
  provenance: "spec-required",
7798
7813
  ...spec(SPEC_PAGES.windsurfHooks, "windsurf-hooks.md")
7799
7814
  },
@@ -7905,6 +7920,13 @@ var STRUCTURAL_CHECKS = [
7905
7920
  provenance: "spec-required",
7906
7921
  ...spec(SPEC_PAGES.codexHooks, "codex-hooks.md")
7907
7922
  },
7923
+ {
7924
+ id: "codex.agent.missing-field",
7925
+ description: "Codex custom agent file does not define string name, description, and developer_instructions",
7926
+ severity: "error",
7927
+ provenance: "spec-required",
7928
+ ...spec(SPEC_PAGES.codexAgents, "codex-agents.md")
7929
+ },
7908
7930
  {
7909
7931
  id: "codex.hook.timeout-out-of-bounds",
7910
7932
  description: "Codex SessionEnd or Interrupt hook timeout is outside 1–3 seconds",
@@ -8019,6 +8041,143 @@ var STRUCTURAL_CHECKS = [
8019
8041
  severity: "error",
8020
8042
  provenance: "spec-required",
8021
8043
  ...spec(SPEC_PAGES.kiroHooks, "kiro-hooks.md")
8044
+ },
8045
+ {
8046
+ id: "zed.mcp.no-launch",
8047
+ description: "Zed context server declares neither command nor url",
8048
+ severity: "error",
8049
+ provenance: "spec-required",
8050
+ ...spec(SPEC_PAGES.zedMcp, "zed-mcp.md")
8051
+ },
8052
+ {
8053
+ id: "amp.mcp.no-launch",
8054
+ description: "Amp MCP server declares neither command nor url",
8055
+ severity: "warning",
8056
+ provenance: "internal-consistency",
8057
+ lastVerified: "2026-11-14",
8058
+ source: { kind: "derived", detail: "agentscan inference" }
8059
+ },
8060
+ {
8061
+ id: "claude-desktop.mcp.no-launch",
8062
+ description: "Claude Desktop MCP server declares no command",
8063
+ severity: "warning",
8064
+ provenance: "internal-consistency",
8065
+ lastVerified: "2026-11-14",
8066
+ source: { kind: "derived", detail: "agentscan inference" }
8067
+ },
8068
+ {
8069
+ id: "claude-desktop.extension.missing-field",
8070
+ description: "Claude Desktop extension manifest lacks a required field or names an unknown server type",
8071
+ severity: "warning",
8072
+ provenance: "spec-required",
8073
+ ...spec(SPEC_PAGES.claudeDesktopExtensions, "claude-desktop-extensions.md")
8074
+ },
8075
+ {
8076
+ id: "qwen.mcp.no-launch",
8077
+ description: "Qwen Code MCP server has neither command, url, nor httpUrl",
8078
+ severity: "error",
8079
+ provenance: "spec-required",
8080
+ ...spec(SPEC_PAGES.qwenMcp, "qwen-mcp.md")
8081
+ },
8082
+ {
8083
+ id: "qwen.hook.unknown-event",
8084
+ description: "Qwen Code hook registered under an event name that is never dispatched",
8085
+ severity: "error",
8086
+ provenance: "spec-required",
8087
+ ...spec(SPEC_PAGES.qwenHooks, "qwen-hooks.md")
8088
+ },
8089
+ {
8090
+ id: "qwen.hook.missing-script",
8091
+ description: "Qwen Code hook points at a script that does not exist, so it never runs",
8092
+ severity: "error",
8093
+ provenance: "internal-consistency",
8094
+ lastVerified: "2026-11-14",
8095
+ source: { kind: "derived", detail: "agentscan inference" }
8096
+ },
8097
+ {
8098
+ id: "qwen.hook.command-without-command",
8099
+ description: "Qwen Code command hook is missing the command field",
8100
+ severity: "error",
8101
+ provenance: "spec-required",
8102
+ ...spec(SPEC_PAGES.qwenHooks, "qwen-hooks.md")
8103
+ },
8104
+ {
8105
+ id: "qwen.hook.http-without-url",
8106
+ description: "Qwen Code http hook is missing the url field",
8107
+ severity: "error",
8108
+ provenance: "spec-required",
8109
+ ...spec(SPEC_PAGES.qwenHooks, "qwen-hooks.md")
8110
+ },
8111
+ {
8112
+ id: "qwen.hook.unknown-handler-type",
8113
+ description: "Qwen Code hook type is missing or is not command, http, or prompt",
8114
+ severity: "error",
8115
+ provenance: "spec-required",
8116
+ ...spec(SPEC_PAGES.qwenHooks, "qwen-hooks.md")
8117
+ },
8118
+ {
8119
+ id: "factory.mcp.no-launch",
8120
+ description: "Factory Droid MCP server has neither command nor url",
8121
+ severity: "error",
8122
+ provenance: "spec-required",
8123
+ ...spec(SPEC_PAGES.factoryMcp, "factory-mcp.md")
8124
+ },
8125
+ {
8126
+ id: "factory.hook.unknown-event",
8127
+ description: "Factory Droid hook registered under an event name that is never dispatched",
8128
+ severity: "error",
8129
+ provenance: "spec-required",
8130
+ ...spec(SPEC_PAGES.factoryHooks, "factory-hooks.md")
8131
+ },
8132
+ {
8133
+ id: "factory.hook.missing-script",
8134
+ description: "Factory Droid hook points at a script that does not exist, so it never runs",
8135
+ severity: "error",
8136
+ provenance: "internal-consistency",
8137
+ lastVerified: "2026-11-14",
8138
+ source: { kind: "derived", detail: "agentscan inference" }
8139
+ },
8140
+ {
8141
+ id: "factory.hook.invalid-group",
8142
+ description: "Factory Droid hook group has no hooks array",
8143
+ severity: "error",
8144
+ provenance: "spec-required",
8145
+ ...spec(SPEC_PAGES.factoryHooks, "factory-hooks.md")
8146
+ },
8147
+ {
8148
+ id: "factory.hook.command-without-command",
8149
+ description: "Factory Droid command hook is missing the command field",
8150
+ severity: "error",
8151
+ provenance: "spec-required",
8152
+ ...spec(SPEC_PAGES.factoryHooks, "factory-hooks.md")
8153
+ },
8154
+ {
8155
+ id: "factory.hook.unknown-handler-type",
8156
+ description: "Factory Droid hook type is missing or is not command",
8157
+ severity: "error",
8158
+ provenance: "spec-required",
8159
+ ...spec(SPEC_PAGES.factoryHooks, "factory-hooks.md")
8160
+ },
8161
+ {
8162
+ id: "factory.droid.invalid-name",
8163
+ description: "Factory custom droid name is missing or does not match ^[a-z0-9-_]+$",
8164
+ severity: "error",
8165
+ provenance: "spec-required",
8166
+ ...spec(SPEC_PAGES.factoryAgents, "factory-agents.md")
8167
+ },
8168
+ {
8169
+ id: "factory.droid.empty-body",
8170
+ description: "Factory custom droid has an empty system prompt body",
8171
+ severity: "error",
8172
+ provenance: "spec-required",
8173
+ ...spec(SPEC_PAGES.factoryAgents, "factory-agents.md")
8174
+ },
8175
+ {
8176
+ id: "goose.mcp.no-launch",
8177
+ description: "Goose extension declares type sse, which Goose does not support",
8178
+ severity: "error",
8179
+ provenance: "spec-required",
8180
+ ...spec(SPEC_PAGES.gooseMcp, "goose-mcp.md")
8022
8181
  }
8023
8182
  ];
8024
8183
  var BY_ID = new Map(STRUCTURAL_CHECKS.map((check) => [check.id, check]));
@@ -8029,6 +8188,16 @@ function ignoreRuleSet(ids) {
8029
8188
  return new Set(ids);
8030
8189
  }
8031
8190
 
8191
+ // src/facts/paths.ts
8192
+ import { isAbsolute, relative, resolve, sep } from "node:path";
8193
+ function toPosix(path) {
8194
+ return path.replaceAll("\\", "/");
8195
+ }
8196
+ function isInside(child, parent) {
8197
+ const rel = relative(resolve(parent), resolve(child));
8198
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
8199
+ }
8200
+
8032
8201
  // src/checks/budgets.ts
8033
8202
  import { basename } from "node:path";
8034
8203
 
@@ -8036,7 +8205,9 @@ import { basename } from "node:path";
8036
8205
  function lineCount(text) {
8037
8206
  return text.length === 0 ? 0 : text.replace(/\r?\n$/, "").split(/\r?\n/).length;
8038
8207
  }
8039
- var INTERPOLATED = /\$\{[A-Za-z_][A-Za-z0-9_]*(?::-[^}]*)?\}|\$\{env:[A-Za-z_][A-Za-z0-9_]*\}|\$\{file:[^}]+\}|\$\{input:[A-Za-z0-9_-]+\}|\$\{(?:userHome|workspaceFolder|workspaceFolderBasename|pathSeparator|\/)\}|\{env:[A-Za-z_][A-Za-z0-9_]*\}|\$\{\{\s*secrets\.[A-Za-z0-9_.]+\s*\}\}|\{\{[^{}]+\}\}|\$[A-Za-z_][A-Za-z0-9_]*|%[A-Za-z_][A-Za-z0-9_]*%/;
8208
+ var NON_PERCENT_INTERPOLATED = /\$\{[A-Za-z_][A-Za-z0-9_]*(?::-[^}]*)?\}|\$\{env:[A-Za-z_][A-Za-z0-9_]*\}|\$\{file:[^}]+\}|\$\{input:[A-Za-z0-9_-]+\}|\$\{(?:userHome|workspaceFolder|workspaceFolderBasename|pathSeparator|\/)\}|\{env:[A-Za-z_][A-Za-z0-9_]*\}|\$\{\{\s*secrets\.[A-Za-z0-9_.]+\s*\}\}|\{\{[^{}]+\}\}|\$[A-Za-z_][A-Za-z0-9_]*/;
8209
+ var INTERPOLATED = new RegExp(`${NON_PERCENT_INTERPOLATED.source}|%[A-Za-z_][A-Za-z0-9_]*%`);
8210
+ var INTERPOLATED_NO_PERCENT = NON_PERCENT_INTERPOLATED;
8040
8211
 
8041
8212
  // src/checks/make.ts
8042
8213
  function make(ruleId, subject, args) {
@@ -8206,16 +8377,6 @@ function applyPrecedence(items, keyOf, rankOf) {
8206
8377
  }
8207
8378
  }
8208
8379
 
8209
- // src/facts/paths.ts
8210
- import { isAbsolute, relative, resolve, sep } from "node:path";
8211
- function toPosix(path) {
8212
- return path.replaceAll("\\", "/");
8213
- }
8214
- function isInside(child, parent) {
8215
- const rel = relative(resolve(parent), resolve(child));
8216
- return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
8217
- }
8218
-
8219
8380
  // src/facts/provider.ts
8220
8381
  function assertNever(value, message) {
8221
8382
  throw new Error(message);
@@ -8256,7 +8417,19 @@ var MCP_FILE_RULES = [
8256
8417
  {
8257
8418
  profile: "copilot-json",
8258
8419
  test: (p) => /(?:^|\/)\.copilot\/mcp-config\.json$/.test(p)
8259
- }
8420
+ },
8421
+ {
8422
+ profile: "zed-json",
8423
+ test: (p) => /(?:^|\/)(?:\.zed|\.config\/zed|Zed)\/settings\.json$/.test(p),
8424
+ jsonc: true
8425
+ },
8426
+ {
8427
+ profile: "amp-json",
8428
+ test: (p) => /(?:^|\/)(?:\.amp|\.config\/amp)\/settings\.jsonc?$/.test(p),
8429
+ jsonc: true
8430
+ },
8431
+ { profile: "qwen-json", test: (p) => /(?:^|\/)\.qwen\/settings\.json$/.test(p), jsonc: true },
8432
+ { profile: "factory-json", test: (p) => /(?:^|\/)\.factory\/mcp\.json$/.test(p) }
8260
8433
  ];
8261
8434
  var DEFAULT_MCP_PATHS = [
8262
8435
  ".mcp.json",
@@ -8317,7 +8490,7 @@ function commandcodeAgentSuggest(agent, defect) {
8317
8490
  case "reserved-name":
8318
8491
  return `Rename ${agent.path} — explore, plan, review, and general are reserved`;
8319
8492
  case "invalid-permission-mode":
8320
- return `Use permissionMode default, auto-accept, bypass, plan, or dont-ask in ${agent.path}`;
8493
+ return `Use permissionMode default, accept-edits, yolo, plan, or dont-ask in ${agent.path}`;
8321
8494
  case "invalid-field-type":
8322
8495
  return `Fix the type of ${agent.invalidField ?? "the field"} in ${agent.path}`;
8323
8496
  default: {
@@ -8348,6 +8521,22 @@ function checkCommandcodeAgents(facts) {
8348
8521
  }
8349
8522
  return out;
8350
8523
  }
8524
+ function checkCodexAgents(facts) {
8525
+ const out = [];
8526
+ for (const agent of facts.agents) {
8527
+ if (agent.sourceProvider !== "codex" || agent.codexMissingFields === undefined) {
8528
+ continue;
8529
+ }
8530
+ const fields = agent.codexMissingFields.join(", ");
8531
+ out.push(make("codex.agent.missing-field", `agent:${agent.path}`, {
8532
+ message: `Codex agent "${agent.name}" does not define string ${fields}`,
8533
+ reason: "Every standalone Codex custom agent file must define name, description, and developer_instructions. See docs/spec/codex-agents.md.",
8534
+ evidence: [{ kind: "agent", value: agent.path }],
8535
+ suggest: `Add ${fields} as strings to ${agent.path}`
8536
+ }));
8537
+ }
8538
+ return out;
8539
+ }
8351
8540
  function checkAgents(facts) {
8352
8541
  const out = [];
8353
8542
  const names = new Map;
@@ -8384,20 +8573,20 @@ function checkAgents(facts) {
8384
8573
  }));
8385
8574
  } else {
8386
8575
  const name = agent.frontmatterName;
8387
- const skipped = name.startsWith("-") || name.includes(":");
8388
- const formatOk = /^[a-z]+(?:-[a-z]+)*$/.test(name);
8389
- if (!formatOk) {
8576
+ if (name.trim() === "" || name.startsWith("-") || name.includes(":") || name.length > 256) {
8390
8577
  out.push(make("claude.agent.invalid-name", `agent:${agent.name}`, {
8391
- severity: skipped ? "error" : "warning",
8392
- message: skipped ? `Agent name "${name}" is skipped (starts with "-" or contains ":")` : `Agent name "${name}" is not a valid identifier`,
8393
- reason: "The subagent reference specifies a unique identifier in lowercase letters and hyphens. The filename does not have to match. A name that starts with `-` or contains `:` is skipped. See docs/spec/agents.md.",
8578
+ message: `Agent name "${name.slice(0, 80)}" is skipped (empty, starts with "-", contains ":", or longer than 256 characters)`,
8579
+ reason: "Claude Code skips a subagent whose name is empty, starts with `-`, contains `:`, or is longer than 256 characters, and writes an error to the debug log. See docs/spec/agents.md.",
8394
8580
  evidence: [{ kind: "agent", value: agent.path }],
8395
- suggest: "Use lowercase letters and hyphens in the `name` field; do not start with `-` or include `:`"
8581
+ suggest: "Use a non-empty name of at most 256 characters that does not start with `-` or contain `:`"
8396
8582
  }));
8397
8583
  }
8398
8584
  const scope = agent.namespace ?? "";
8399
8585
  const key = `${scope}\x00${agent.frontmatterName}`;
8400
8586
  const list = names.get(key) ?? [];
8587
+ if (list.includes(agent.path)) {
8588
+ continue;
8589
+ }
8401
8590
  list.push(agent.path);
8402
8591
  names.set(key, list);
8403
8592
  }
@@ -8413,7 +8602,45 @@ function checkAgents(facts) {
8413
8602
  }));
8414
8603
  }
8415
8604
  }
8416
- return [...out, ...checkCommandcodeAgents(facts)];
8605
+ return [...out, ...checkCommandcodeAgents(facts), ...checkCodexAgents(facts), ...checkFactoryDroids(facts)];
8606
+ }
8607
+ function checkFactoryDroids(facts) {
8608
+ const out = [];
8609
+ for (const agent of facts.agents) {
8610
+ if (agent.sourceProvider !== "factory") {
8611
+ continue;
8612
+ }
8613
+ for (const defect of agent.factoryDroidDefects ?? []) {
8614
+ out.push(defect === "invalid-name" ? make("factory.droid.invalid-name", `agent:${agent.path}`, {
8615
+ message: `Droid "${agent.name}" has no frontmatter name matching ^[a-z0-9-_]+$`,
8616
+ reason: "Factory requires `name` (lowercase letters, digits, `-`, `_`); it drives the `subagent_type` value. The CLI validates every definition and exposes only valid droids. See docs/spec/factory-agents.md.",
8617
+ evidence: [{ kind: "agent", value: agent.path }],
8618
+ suggest: `Set a lowercase name in ${agent.path}`
8619
+ }) : make("factory.droid.empty-body", `agent:${agent.path}`, {
8620
+ message: `Droid "${agent.name}" has an empty system prompt`,
8621
+ reason: "The body after the frontmatter is the droid's system prompt and cannot be empty. See docs/spec/factory-agents.md.",
8622
+ evidence: [{ kind: "agent", value: agent.path }],
8623
+ suggest: `Write the system prompt below the frontmatter in ${agent.path}`
8624
+ }));
8625
+ }
8626
+ }
8627
+ return out;
8628
+ }
8629
+
8630
+ // src/checks/claude-desktop.ts
8631
+ function checkClaudeDesktopExtensions(facts) {
8632
+ return (facts.claudeDesktopExtensions ?? []).map((ext) => {
8633
+ const problems = [
8634
+ ...ext.missingFields.length > 0 ? [`is missing ${ext.missingFields.join(", ")}`] : [],
8635
+ ...ext.unknownServerType === undefined ? [] : [`has unknown server.type "${ext.unknownServerType}"`]
8636
+ ];
8637
+ return make("claude-desktop.extension.missing-field", `extension:${ext.path}`, {
8638
+ message: `Claude Desktop extension manifest ${problems.join(" and ")}`,
8639
+ reason: "MCPB manifests require manifest_version, name, version, description, author (with name) and server; server.type is node, python, binary or uv. See docs/spec/claude-desktop-extensions.md.",
8640
+ evidence: [{ kind: "config", value: ext.path }],
8641
+ suggest: "Reinstall the extension, or fix its manifest.json"
8642
+ });
8643
+ });
8417
8644
  }
8418
8645
 
8419
8646
  // src/checks/config.ts
@@ -8425,7 +8652,16 @@ function safeDetail(detail) {
8425
8652
  return position === undefined ? head : `${head} at ${position}`;
8426
8653
  }
8427
8654
  function checkConfigErrors(facts) {
8428
- return facts.configErrors.map((err) => {
8655
+ const denied = new Set;
8656
+ const errors = facts.configErrors.filter((err) => {
8657
+ if (err.kind !== "permission-denied")
8658
+ return true;
8659
+ if (denied.has(err.path))
8660
+ return false;
8661
+ denied.add(err.path);
8662
+ return true;
8663
+ });
8664
+ return errors.map((err) => {
8429
8665
  if (err.kind === "truncated") {
8430
8666
  return make("scan.truncated", `scan:${err.path}`, {
8431
8667
  message: `${basename2(err.path)} is larger than the scan cap — only its first bytes were read`,
@@ -8436,8 +8672,20 @@ function checkConfigErrors(facts) {
8436
8672
  ]
8437
8673
  });
8438
8674
  }
8675
+ if (err.kind === "permission-denied") {
8676
+ return make("config.unreadable", `config:${err.path}#${err.kind}`, {
8677
+ severity: "info",
8678
+ message: `${basename2(err.path)} could not be read (permission denied) — skipped`,
8679
+ reason: "The scanning user lacks permission to read this path. That is a property of this scan, not a defect in the file, which is often not agent config at all.",
8680
+ evidence: [
8681
+ { kind: "config", value: err.path },
8682
+ { kind: "detail", value: safeDetail(err.detail) }
8683
+ ]
8684
+ });
8685
+ }
8439
8686
  const what = err.kind === "invalid-json" ? "is not valid JSON" : err.kind === "unreadable" ? "could not be read" : "has an unexpected shape";
8440
8687
  return make("config.unreadable", `config:${err.path}#${err.kind}:${safeDetail(err.detail)}`, {
8688
+ severity: "error",
8441
8689
  message: `${basename2(err.path)} ${what} — its contents are invisible to the scan`,
8442
8690
  reason: "An unparseable config is silently ignored by the tools that read it, so whatever it configured is simply not in effect.",
8443
8691
  evidence: [
@@ -8455,14 +8703,14 @@ import { join } from "node:path";
8455
8703
  var PROVIDERS = {
8456
8704
  "agent-skills": { dotDir: ".agents", skills: "agent-skills", userSkills: true, portableSkills: true },
8457
8705
  claude: { dotDir: ".claude", homeEnv: "CLAUDE_CONFIG_DIR", skills: "claude", userSkills: true },
8458
- cursor: { dotDir: ".cursor", skills: "agent-skills", portableSkills: true },
8459
- codex: { dotDir: ".codex", homeEnv: "CODEX_HOME", skills: "agent-skills", userSkills: true, portableSkills: true },
8706
+ cursor: { dotDir: ".cursor", skills: "agent-skills", userSkills: true, portableSkills: true },
8707
+ codex: { dotDir: ".codex", homeEnv: "CODEX_HOME" },
8460
8708
  commandcode: { dotDir: ".commandcode", skills: "agent-skills", userSkills: true },
8461
8709
  grok: { dotDir: ".grok", homeEnv: "GROK_HOME", skills: "grok", userSkills: true },
8462
8710
  windsurf: { dotDir: ".windsurf", home: ".codeium/windsurf", skills: "agent-skills", userSkills: true },
8463
8711
  devin: { dotDir: ".devin", home: ".config/devin", skills: "agent-skills", userSkills: true },
8464
8712
  kiro: { dotDir: ".kiro", skills: "agent-skills", userSkills: true },
8465
- cline: { dotDir: ".cline", skills: "agent-skills", userSkills: true },
8713
+ cline: { dotDir: ".cline", skillDotDirs: [".clinerules"], skills: "agent-skills", userSkills: true },
8466
8714
  roo: { dotDir: ".roo", skills: "agent-skills", userSkills: true },
8467
8715
  kilo: { dotDir: ".kilo", skills: "agent-skills", userSkills: true },
8468
8716
  junie: { dotDir: ".junie", skills: "agent-skills", userSkills: true },
@@ -8471,7 +8719,13 @@ var PROVIDERS = {
8471
8719
  opencode: { dotDir: ".opencode" },
8472
8720
  continue: { dotDir: ".continue" },
8473
8721
  antigravity: { home: ".gemini/config", userSkills: true, skills: "agent-skills" },
8474
- copilot: { dotDir: ".github", home: ".copilot", homeEnv: "COPILOT_HOME" }
8722
+ copilot: { dotDir: ".github", home: ".copilot", homeEnv: "COPILOT_HOME" },
8723
+ zed: { dotDir: ".zed" },
8724
+ amp: { home: ".config/amp", skills: "agent-skills", userSkills: true },
8725
+ "claude-desktop": {},
8726
+ qwen: { dotDir: ".qwen", skills: "agent-skills", userSkills: true },
8727
+ factory: { dotDir: ".factory", skills: "agent-skills", userSkills: true },
8728
+ goose: { home: ".config/goose" }
8475
8729
  };
8476
8730
  var SPECS = Object.entries(PROVIDERS);
8477
8731
  function providerHome(id) {
@@ -8483,20 +8737,22 @@ function providerHome(id) {
8483
8737
  return join(homedir(), spec.home ?? spec.dotDir ?? "");
8484
8738
  }
8485
8739
  var WITH_SKILLS = SPECS.filter(([, spec]) => spec.skills !== undefined);
8486
- var PROJECT_SKILL_DIRS = WITH_SKILLS.flatMap(([, spec]) => spec.dotDir === undefined ? [] : [`${spec.dotDir}/skills`]);
8740
+ var PROJECT_SKILL_DIRS = WITH_SKILLS.flatMap(([, spec]) => [...spec.dotDir === undefined ? [] : [spec.dotDir], ...spec.skillDotDirs ?? []].map((dir) => `${dir}/skills`));
8487
8741
  var SKILL_PATHS_BEFORE_BARE = [".agents/skills", ".claude/skills"];
8488
8742
  var DEFAULT_SKILL_PATHS = [
8489
8743
  ...SKILL_PATHS_BEFORE_BARE,
8490
8744
  "skills",
8491
8745
  ...PROJECT_SKILL_DIRS.filter((dir) => !SKILL_PATHS_BEFORE_BARE.includes(dir))
8492
8746
  ];
8493
- var SKILL_DOT_DIRS = new Set(WITH_SKILLS.flatMap(([id, spec]) => id === "commandcode" || spec.dotDir === undefined ? [] : [spec.dotDir]));
8747
+ var SKILL_DOT_DIRS = new Set(WITH_SKILLS.flatMap(([id, spec]) => id === "commandcode" || spec.dotDir === undefined ? [] : [spec.dotDir, ...spec.skillDotDirs ?? []]));
8494
8748
  var USER_SKILL_PROVIDERS = WITH_SKILLS.filter(([, spec]) => spec.userSkills).map(([id]) => id);
8495
8749
  var AGENT_CONFIG_DOT_DIRS = SPECS.flatMap(([, spec]) => spec.dotDir ?? []);
8496
8750
  var SKILL_MARKERS = WITH_SKILLS.flatMap(([id, spec]) => {
8497
8751
  const markers = [];
8498
- if (spec.dotDir !== undefined) {
8499
- markers.push(`/${spec.dotDir}/skills`);
8752
+ for (const dir of [spec.dotDir, ...spec.skillDotDirs ?? []]) {
8753
+ if (dir !== undefined) {
8754
+ markers.push(`/${dir}/skills`);
8755
+ }
8500
8756
  }
8501
8757
  if (spec.home !== undefined && spec.userSkills) {
8502
8758
  markers.push(`/${spec.home}/skills`);
@@ -8547,11 +8803,13 @@ var COMMANDCODE_RESERVED_AGENT_NAMES = new Set([
8547
8803
  ]);
8548
8804
  var COMMANDCODE_PERMISSION_MODES = new Set([
8549
8805
  "default",
8550
- "auto-accept",
8551
- "bypass",
8806
+ "accept-edits",
8807
+ "yolo",
8552
8808
  "plan",
8553
8809
  "dont-ask",
8554
- "inherit"
8810
+ "inherit",
8811
+ "auto-accept",
8812
+ "bypass"
8555
8813
  ]);
8556
8814
  var COMMANDCODE_MCP_TRANSPORTS = new Set(["http", "stdio"]);
8557
8815
  var COMMANDCODE_TOLERATED_MCP_TRANSPORTS = new Set(["sse"]);
@@ -8743,7 +9001,6 @@ var COPILOT_HOOK_EVENTS = new Set([
8743
9001
  ]);
8744
9002
  var CLAUDE_ALL_HANDLER_EVENTS = new Set([
8745
9003
  "PermissionDenied",
8746
- "PermissionRequest",
8747
9004
  "PostToolBatch",
8748
9005
  "PostToolUse",
8749
9006
  "PostToolUseFailure",
@@ -8756,6 +9013,7 @@ var CLAUDE_ALL_HANDLER_EVENTS = new Set([
8756
9013
  "UserPromptExpansion",
8757
9014
  "UserPromptSubmit"
8758
9015
  ]);
9016
+ var CLAUDE_NO_AGENT_EVENTS = new Set(["PermissionRequest"]);
8759
9017
  var CLAUDE_COMMAND_HTTP_MCP_EVENTS = new Set([
8760
9018
  "ConfigChange",
8761
9019
  "CwdChanged",
@@ -8784,6 +9042,9 @@ function claudeHandlerCompatible(event, type) {
8784
9042
  if (CLAUDE_ALL_HANDLER_EVENTS.has(event)) {
8785
9043
  return true;
8786
9044
  }
9045
+ if (CLAUDE_NO_AGENT_EVENTS.has(event)) {
9046
+ return type !== "agent";
9047
+ }
8787
9048
  if (CLAUDE_COMMAND_MCP_EVENTS.has(event)) {
8788
9049
  return type === "command" || type === "mcp_tool";
8789
9050
  }
@@ -8826,7 +9087,9 @@ var KIRO_HOOK_EVENTS = new Set([
8826
9087
  "PostToolUse",
8827
9088
  "PostFileCreate",
8828
9089
  "PostFileSave",
8829
- "PostFileDelete"
9090
+ "PostFileDelete",
9091
+ "AgentSpawn",
9092
+ "agentSpawn"
8830
9093
  ]);
8831
9094
  var KIRO_HOOK_HANDLER_TYPES = new Set(["command", "agent"]);
8832
9095
 
@@ -8846,6 +9109,48 @@ var WINDSURF_HOOK_EVENTS = new Set([
8846
9109
  "post_setup_worktree"
8847
9110
  ]);
8848
9111
 
9112
+ // src/facts/qwen.ts
9113
+ var QWEN_HOOK_EVENTS = new Set([
9114
+ "PreToolUse",
9115
+ "PostToolUse",
9116
+ "PostToolUseFailure",
9117
+ "PostToolBatch",
9118
+ "UserPromptSubmit",
9119
+ "UserPromptExpansion",
9120
+ "SessionStart",
9121
+ "SessionEnd",
9122
+ "SessionDelete",
9123
+ "MessageDisplay",
9124
+ "Stop",
9125
+ "StopFailure",
9126
+ "SubagentStart",
9127
+ "SubagentStop",
9128
+ "PreCompact",
9129
+ "PostCompact",
9130
+ "Notification",
9131
+ "PermissionRequest",
9132
+ "PermissionDenied",
9133
+ "TodoCreated",
9134
+ "TodoCompleted",
9135
+ "InstructionsLoaded",
9136
+ "FileChanged"
9137
+ ]);
9138
+ var QWEN_HOOK_HANDLER_TYPES = new Set(["command", "http", "prompt", "function"]);
9139
+
9140
+ // src/facts/factory.ts
9141
+ var FACTORY_HOOK_EVENTS = new Set([
9142
+ "PreToolUse",
9143
+ "PostToolUse",
9144
+ "UserPromptSubmit",
9145
+ "Notification",
9146
+ "Stop",
9147
+ "SubagentStop",
9148
+ "PreCompact",
9149
+ "SessionStart",
9150
+ "SessionEnd"
9151
+ ]);
9152
+ var FACTORY_DROID_NAME = /^[a-z0-9-_]+$/;
9153
+
8849
9154
  // src/facts/hook-profiles.ts
8850
9155
  var PROFILES = {
8851
9156
  claude: {
@@ -8996,6 +9301,20 @@ var PROFILES = {
8996
9301
  layout: "own",
8997
9302
  fileSource: "kiro-hooks",
8998
9303
  invalidGroupHasNoEvent: true
9304
+ },
9305
+ qwen: {
9306
+ events: QWEN_HOOK_EVENTS,
9307
+ ruleIdPrefix: "qwen",
9308
+ defects: ["command-without-command", "http-without-url", "unknown-handler-type"],
9309
+ layout: "nested",
9310
+ fileSource: "qwen-settings"
9311
+ },
9312
+ factory: {
9313
+ events: FACTORY_HOOK_EVENTS,
9314
+ ruleIdPrefix: "factory",
9315
+ defects: ["invalid-group", "command-without-command", "unknown-handler-type"],
9316
+ layout: "nested",
9317
+ fileSource: "factory-hooks"
8999
9318
  }
9000
9319
  };
9001
9320
  var HOOK_PROFILES = PROFILES;
@@ -9041,8 +9360,10 @@ function checkHookEvents(facts) {
9041
9360
  continue;
9042
9361
  }
9043
9362
  reported.add(key);
9044
- out.push(make(ruleId, `hook:${event}`, {
9045
- message: `"${event}" is not a hook event that gets dispatched`,
9363
+ const recased = `${event.charAt(0).toLowerCase()}${event.slice(1)}`;
9364
+ const casingSlip = profile === "copilot-cli" && recased !== event && known.has(recased);
9365
+ const args = {
9366
+ message: casingSlip ? `"${event}" is not a documented Copilot CLI hook event; the documented name is "${recased}"` : `"${event}" is not a hook event that gets dispatched`,
9046
9367
  reason: "Hook events are matched by exact name. An unrecognised name never matches, so everything registered under it silently never runs — a typo here looks identical to a working hook. Event sets are per schema profile; a VS Code name is not validated against Claude's list, and Copilot CLI camelCase names map onto VS Code events where the Copilot hooks reference documents that mapping.",
9047
9368
  evidence: [
9048
9369
  { kind: "hook", value: `${event} @ ${hook.path}` },
@@ -9051,8 +9372,9 @@ function checkHookEvents(facts) {
9051
9372
  value: [...known].join(", ")
9052
9373
  }
9053
9374
  ],
9054
- suggest: `Fix the event name in ${hook.path} (or ignoreRules: ["${ruleId}"] if it is newly supported)`
9055
- }));
9375
+ suggest: casingSlip ? `Rename "${event}" to "${recased}" in ${hook.path}` : `Fix the event name in ${hook.path} (or ignoreRules: ["${ruleId}"] if it is newly supported)`
9376
+ };
9377
+ out.push(ruleId === "copilot.hook.unknown-event" ? make(ruleId, `hook:${event}`, { ...args, severity: casingSlip ? "warning" : "error" }) : make(ruleId, `hook:${event}`, args));
9056
9378
  }
9057
9379
  return out;
9058
9380
  }
@@ -9246,6 +9568,44 @@ var PROFILES2 = {
9246
9568
  sourceProvider: "vscode",
9247
9569
  consumedBy: ["vscode"],
9248
9570
  ruleIdPrefix: "vscode"
9571
+ },
9572
+ "zed-json": {
9573
+ ...COMMAND_OR_URL,
9574
+ sourceProvider: "zed",
9575
+ consumedBy: ["zed"],
9576
+ ruleIdPrefix: "zed",
9577
+ launchable: (server) => commandOrUrl(server) || server.extensionProvided === true || server.commandPathObject === true
9578
+ },
9579
+ "amp-json": { ...COMMAND_OR_URL, sourceProvider: "amp", consumedBy: ["amp"], ruleIdPrefix: "amp" },
9580
+ "claude-desktop-json": {
9581
+ sourceProvider: "claude-desktop",
9582
+ consumedBy: ["claude-desktop"],
9583
+ ruleIdPrefix: "claude-desktop",
9584
+ launchable: (server) => commandOrUrl(server) || server.transport !== undefined,
9585
+ noLaunchDeclares: "no command",
9586
+ noLaunchAdd: "a command"
9587
+ },
9588
+ "qwen-json": {
9589
+ sourceProvider: "qwen",
9590
+ consumedBy: ["qwen"],
9591
+ ruleIdPrefix: "qwen",
9592
+ launchable: (server) => commandOrUrl(server) || server.hasHttpUrl === true,
9593
+ noLaunchDeclares: "neither command, url, nor httpUrl",
9594
+ noLaunchAdd: "a command, url, or httpUrl"
9595
+ },
9596
+ "factory-json": {
9597
+ ...COMMAND_OR_URL,
9598
+ sourceProvider: "factory",
9599
+ consumedBy: ["factory"],
9600
+ ruleIdPrefix: "factory"
9601
+ },
9602
+ "goose-yaml": {
9603
+ ...COMMAND_OR_URL,
9604
+ sourceProvider: "goose",
9605
+ consumedBy: ["goose"],
9606
+ ruleIdPrefix: "goose",
9607
+ noLaunchDeclares: "type sse, which Goose does not support",
9608
+ noLaunchAdd: "type streamable_http with uri"
9249
9609
  }
9250
9610
  };
9251
9611
  var MCP_PROFILES = PROFILES2;
@@ -9652,12 +10012,12 @@ function checkSkillStructure(facts) {
9652
10012
  }
9653
10013
  if (schema === "agent-skills") {
9654
10014
  out.push(...checkAgentSkillsFrontmatter(skill));
9655
- } else if (schema === "grok") {} else if (skill.description === undefined && skill.firstMarkdownParagraph === undefined) {
10015
+ } else if (schema === "grok") {} else if (skill.description === undefined && skill.firstNonEmptyLine === undefined) {
9656
10016
  out.push(make("claude.skill.missing-description", skillSubject(skill), {
9657
- message: "SKILL.md frontmatter has no `description` and no first markdown paragraph",
9658
- reason: "The description is what Claude matches against when deciding to load a skill automatically. If it is omitted, Claude uses the first markdown paragraph. Without either, the skill still works when invoked directly, but it will not be picked up on its own. The spec marks description Recommended, not required — see docs/spec/skills.md.",
10017
+ message: "SKILL.md frontmatter has no `description` and no first non-empty markdown line",
10018
+ reason: "The description is what Claude matches against when deciding to load a skill automatically. If it is omitted, Claude uses the first non-empty markdown line. Without either, the skill still works when invoked directly, but it will not be picked up on its own. The spec marks description Recommended, not required — see docs/spec/skills.md.",
9659
10019
  evidence: skillEvidence(skill, `${skill.path}/SKILL.md`),
9660
- suggest: "Add a one-line description to the frontmatter, or a first markdown paragraph"
10020
+ suggest: "Add a one-line description to the frontmatter, or a first non-empty markdown line"
9661
10021
  }));
9662
10022
  }
9663
10023
  }
@@ -9666,7 +10026,7 @@ function checkSkillStructure(facts) {
9666
10026
  function checkAgentSkillsFrontmatter(skill) {
9667
10027
  const out = [];
9668
10028
  const dirName = basename3(toPosix(skill.path));
9669
- if (skill.frontmatterName === undefined) {
10029
+ if (skill.frontmatterName === undefined && skill.sourceProvider !== "antigravity") {
9670
10030
  const typeNote = skill.nameKind !== undefined && skill.nameKind !== "string" ? ` (found ${skill.nameKind}, not a string)` : "";
9671
10031
  out.push(make("agent-skills.skill.missing-name", skillSubject(skill), {
9672
10032
  message: `SKILL.md frontmatter has no string \`name\`${typeNote}`,
@@ -9674,7 +10034,7 @@ function checkAgentSkillsFrontmatter(skill) {
9674
10034
  evidence: skillEvidence(skill, `${skill.path}/SKILL.md`),
9675
10035
  suggest: `Add name: ${dirName}`
9676
10036
  }));
9677
- } else {
10037
+ } else if (skill.frontmatterName !== undefined) {
9678
10038
  const name = skill.frontmatterName;
9679
10039
  if (name.length > AGENT_SKILLS_NAME_MAX) {
9680
10040
  out.push(make("agent-skills.skill.name-too-long", skillSubject(skill), {
@@ -9683,7 +10043,7 @@ function checkAgentSkillsFrontmatter(skill) {
9683
10043
  evidence: skillEvidence(skill, `${skill.path}/SKILL.md`),
9684
10044
  suggest: "Shorten the frontmatter name"
9685
10045
  }));
9686
- } else if (!isValidAgentSkillsName(name)) {
10046
+ } else if (!isValidAgentSkillsName(name) && skill.sourceProvider !== "qwen") {
9687
10047
  out.push(make("agent-skills.skill.invalid-name", skillSubject(skill), {
9688
10048
  message: `Skill name "${name}" is not a valid Agent Skills identifier`,
9689
10049
  reason: "Name must be lowercase letters (any script), numbers, and hyphens, without a leading or trailing hyphen or consecutive hyphens. See docs/spec/agent-skills.md.",
@@ -9691,7 +10051,7 @@ function checkAgentSkillsFrontmatter(skill) {
9691
10051
  suggest: "Use a lowercase hyphenated name that matches the directory"
9692
10052
  }));
9693
10053
  }
9694
- if (name !== dirName && skill.sourceProvider !== "junie") {
10054
+ if (name !== dirName && skill.sourceProvider !== "junie" && skill.sourceProvider !== "qwen" && skill.sourceProvider !== "factory") {
9695
10055
  out.push(make("agent-skills.skill.name-does-not-match-directory", skillSubject(skill), {
9696
10056
  message: `Skill name "${name}" does not match directory "${dirName}"`,
9697
10057
  reason: "The Agent Skills spec requires `name` to match the parent directory. See docs/spec/agent-skills.md.",
@@ -9819,7 +10179,7 @@ function checkLockIntegrity(facts, requireLock) {
9819
10179
  const inScope = projectSkills.filter((skill) => governingLockRoot(skillAbs(skill, facts), lockRoots) === root);
9820
10180
  const onDisk = new Set(inScope.map((skill) => skill.id));
9821
10181
  for (const entry of entries) {
9822
- if (onDisk.has(entry.id)) {
10182
+ if (onDisk.has(entry.id) || entry.installedBesideLock === true) {
9823
10183
  continue;
9824
10184
  }
9825
10185
  const from = entry.source === undefined ? "" : ` (from ${entry.source})`;
@@ -9890,7 +10250,7 @@ function checkDescriptionBudget(facts, thresholds) {
9890
10250
  const subject = byRoot.size === 1 ? "skills:description-budget" : `skills:description-budget:${where}`;
9891
10251
  out.push(make("skill.description-budget", subject, {
9892
10252
  message: `Skill listing text totals ${chars} characters across ${skills.length} skills in ${where} (over ${ceiling}; per-entry cap ${perEntryCap})`,
9893
- reason: "Claude loads description (or the first markdown paragraph) plus when_to_use for every skill at listing time. That listing is 1% of the model context window, with an 8000-character fallback when the window is unknown, and each entry is truncated at 1536 characters. Past the budget, matching keywords are lost. See docs/spec/skills.md.",
10253
+ reason: "Claude loads description (or the first non-empty markdown line) plus when_to_use for every skill at listing time. That listing is 1% of the model context window, with an 8000-character fallback when the window is unknown, and each entry is truncated at 1536 characters. Past the budget, matching keywords are lost. See docs/spec/skills.md.",
9894
10254
  evidence: [
9895
10255
  { kind: "count", value: `listingChars=${chars}` },
9896
10256
  { kind: "threshold", value: String(ceiling) }
@@ -9901,15 +10261,37 @@ function checkDescriptionBudget(facts, thresholds) {
9901
10261
  return out;
9902
10262
  }
9903
10263
  function listingText(skill) {
9904
- const description = skill.description ?? skill.firstMarkdownParagraph ?? "";
10264
+ const description = skill.description ?? skill.firstNonEmptyLine ?? "";
9905
10265
  const when = skill.whenToUse ?? "";
9906
10266
  return `${description}${when}`;
9907
10267
  }
9908
10268
 
9909
10269
  // src/checks/index.ts
10270
+ var GLOBAL_COPY_TREES = [
10271
+ "/.claude/plugins/cache/",
10272
+ "/.claude/plugins/marketplaces/",
10273
+ "/.claude-backups/",
10274
+ "/.codex/plugins/",
10275
+ "/.grok/installed-plugins/",
10276
+ "/.cursor/plugins/"
10277
+ ];
10278
+ function inGlobalCopyTree(path) {
10279
+ const posix = toPosix(path);
10280
+ return GLOBAL_COPY_TREES.some((tree) => posix.includes(tree));
10281
+ }
10282
+ function withoutGlobalCopies(facts) {
10283
+ return {
10284
+ ...facts,
10285
+ rules: facts.rules?.filter((r) => r.sourceProvider !== "windsurf" || !inGlobalCopyTree(r.path)),
10286
+ agents: facts.agents.filter((a) => !inGlobalCopyTree(a.path)),
10287
+ mcp: facts.mcp.filter((m) => !inGlobalCopyTree(m.path)),
10288
+ configErrors: facts.configErrors.filter((e) => e.kind !== "truncated" || !inGlobalCopyTree(e.path))
10289
+ };
10290
+ }
9910
10291
  function runChecks(facts, config) {
10292
+ const quiet = config.includeGlobal === true ? withoutGlobalCopies(facts) : facts;
9911
10293
  return [
9912
- ...checkConfigErrors(facts),
10294
+ ...checkConfigErrors(quiet),
9913
10295
  ...checkHookEvents(facts),
9914
10296
  ...checkHooks(facts),
9915
10297
  ...checkSkillStructure(facts),
@@ -9918,8 +10300,9 @@ function runChecks(facts, config) {
9918
10300
  ...checkDescriptionBudget(facts, config.thresholds),
9919
10301
  ...checkAgents(facts),
9920
10302
  ...checkMcp(facts),
9921
- ...checkRules(facts),
9922
- ...runBudgets(facts, config.thresholds)
10303
+ ...checkClaudeDesktopExtensions(facts),
10304
+ ...checkRules(quiet),
10305
+ ...runBudgets(quiet, config.thresholds)
9923
10306
  ];
9924
10307
  }
9925
10308
 
@@ -13789,8 +14172,8 @@ function loadConfig(root, configPath) {
13789
14172
  }
13790
14173
 
13791
14174
  // src/discover/extract.ts
13792
- import { existsSync as existsSync7 } from "node:fs";
13793
- import { join as join21 } from "node:path";
14175
+ import { existsSync as existsSync9 } from "node:fs";
14176
+ import { join as join25 } from "node:path";
13794
14177
 
13795
14178
  // src/discover/fs.ts
13796
14179
  import {
@@ -15609,6 +15992,10 @@ function isRealDirectory(path) {
15609
15992
  function errorDetail(err) {
15610
15993
  return err instanceof Error ? err.message : String(err);
15611
15994
  }
15995
+ function unreadableKind(err) {
15996
+ const code = err instanceof Error ? err.code : undefined;
15997
+ return code === "EACCES" || code === "EPERM" ? "permission-denied" : "unreadable";
15998
+ }
15612
15999
  function isEnoent(err) {
15613
16000
  return err instanceof Error && err.code === "ENOENT";
15614
16001
  }
@@ -15622,7 +16009,7 @@ function listDir(dir, io, opts = {}) {
15622
16009
  if (opts.optional === true && isEnoent(err)) {
15623
16010
  return;
15624
16011
  }
15625
- io.errors.push({ path: dir, kind: "unreadable", detail: opts.detail ?? errorDetail(err) });
16012
+ io.errors.push({ path: dir, kind: unreadableKind(err), detail: opts.detail ?? errorDetail(err) });
15626
16013
  return;
15627
16014
  }
15628
16015
  }
@@ -15640,7 +16027,7 @@ function readCapped(path, cap, io, opts = {}) {
15640
16027
  if (opts.optional === true && isEnoent(err)) {
15641
16028
  return;
15642
16029
  }
15643
- io.errors.push({ path, kind: "unreadable", detail: errorDetail(err) });
16030
+ io.errors.push({ path, kind: unreadableKind(err), detail: errorDetail(err) });
15644
16031
  return;
15645
16032
  }
15646
16033
  const truncated = buf.length > cap;
@@ -15699,7 +16086,7 @@ function parseConfigFile(path, errors) {
15699
16086
  try {
15700
16087
  text = readFileSync2(path, "utf8");
15701
16088
  } catch (err) {
15702
- errors.push({ path, kind: "unreadable", detail: errorDetail(err) });
16089
+ errors.push({ path, kind: unreadableKind(err), detail: errorDetail(err) });
15703
16090
  return;
15704
16091
  }
15705
16092
  const format = configFormat(path);
@@ -15727,11 +16114,11 @@ function parseConfigFile(path, errors) {
15727
16114
  }
15728
16115
 
15729
16116
  // src/discover/index.ts
15730
- import { homedir as homedir4 } from "node:os";
15731
- import { dirname as dirname11, join as join20, resolve as resolve12 } from "node:path";
16117
+ import { homedir as homedir6 } from "node:os";
16118
+ import { dirname as dirname12, join as join24, resolve as resolve12 } from "node:path";
15732
16119
 
15733
16120
  // src/discover/agents.ts
15734
- import { dirname as dirname6, join as join10 } from "node:path";
16121
+ import { dirname as dirname6, join as join11 } from "node:path";
15735
16122
 
15736
16123
  // src/discover/commandcode.ts
15737
16124
  import { existsSync as existsSync4 } from "node:fs";
@@ -15894,7 +16281,7 @@ function scriptCandidateFromArgv(argv, bases = {}) {
15894
16281
  return expandPlaceholder(candidate, bases);
15895
16282
  }
15896
16283
  function scriptCandidateFromLaunch(launch, bases = {}) {
15897
- if (launch.args.length === 0 && /\s/.test(launch.executable)) {
16284
+ if (launch.args.length === 0 && /[\s"']/.test(launch.executable)) {
15898
16285
  const tokens = tokenize(launch.executable);
15899
16286
  return tokens === undefined ? undefined : scriptCandidateFromArgv(tokens, bases);
15900
16287
  }
@@ -16085,6 +16472,7 @@ var NESTED_DISCOVERY_SKIP = new Set([
16085
16472
  "coverage",
16086
16473
  "vendor"
16087
16474
  ]);
16475
+ var NESTED_DISCOVERY_SKIP_PATHS = ["/.local/share/containers"];
16088
16476
  function walkFiles(dir, opts, depth = 0) {
16089
16477
  const out = [];
16090
16478
  for (const entry of listDir(dir, opts.io, { optional: true }) ?? []) {
@@ -16093,6 +16481,9 @@ function walkFiles(dir, opts, depth = 0) {
16093
16481
  }
16094
16482
  const abs = join4(dir, entry.name);
16095
16483
  if (entry.isDirectory()) {
16484
+ if (NESTED_DISCOVERY_SKIP_PATHS.some((tail) => toPosix(abs).endsWith(tail))) {
16485
+ continue;
16486
+ }
16096
16487
  if (depth >= opts.maxDepth) {
16097
16488
  continue;
16098
16489
  }
@@ -16156,39 +16547,39 @@ function commandFactsFromEntry(entry, root, hostPlatform = process.platform) {
16156
16547
  function isSecretNamedKey(key) {
16157
16548
  return SECRET_NAMED_KEY.test(key) || AUTH_HEADER_NAME.test(key);
16158
16549
  }
16159
- function isLiteralCredentialValue(val) {
16160
- return typeof val === "string" && val.length > 0 && !INTERPOLATED.test(val);
16550
+ function isLiteralCredentialValue(val, interpolated) {
16551
+ return typeof val === "string" && val.length > 0 && !interpolated.test(val);
16161
16552
  }
16162
- function literalEnvKeysFrom(entry) {
16553
+ function literalEnvKeysFrom(entry, interpolated) {
16163
16554
  const env = entry.env;
16164
16555
  if (!isRecord(env)) {
16165
16556
  return [];
16166
16557
  }
16167
16558
  const keys = [];
16168
16559
  for (const [key, val] of Object.entries(env)) {
16169
- if (isLiteralCredentialValue(val) && SECRET_NAMED_KEY.test(key)) {
16560
+ if (isLiteralCredentialValue(val, interpolated) && SECRET_NAMED_KEY.test(key)) {
16170
16561
  keys.push(key);
16171
16562
  }
16172
16563
  }
16173
16564
  return keys;
16174
16565
  }
16175
- function literalFieldsFromMap(obj, prefix) {
16566
+ function literalFieldsFromMap(obj, prefix, interpolated) {
16176
16567
  if (!isRecord(obj)) {
16177
16568
  return [];
16178
16569
  }
16179
16570
  const fields = [];
16180
16571
  for (const [key, val] of Object.entries(obj)) {
16181
- if (isLiteralCredentialValue(val) && isSecretNamedKey(key)) {
16572
+ if (isLiteralCredentialValue(val, interpolated) && isSecretNamedKey(key)) {
16182
16573
  fields.push(`${prefix}.${key}`);
16183
16574
  }
16184
16575
  }
16185
16576
  return fields;
16186
16577
  }
16187
- function literalCredentialFieldsFrom(entry) {
16578
+ function literalCredentialFieldsFrom(entry, interpolated) {
16188
16579
  return [
16189
- ...literalFieldsFromMap(entry.headers, "headers"),
16190
- ...literalFieldsFromMap(entry.http_headers, "http_headers"),
16191
- ...literalFieldsFromMap(entry.auth, "auth")
16580
+ ...literalFieldsFromMap(entry.headers, "headers", interpolated),
16581
+ ...literalFieldsFromMap(entry.http_headers, "http_headers", interpolated),
16582
+ ...literalFieldsFromMap(entry.auth, "auth", interpolated)
16192
16583
  ];
16193
16584
  }
16194
16585
  function looksLikeServerEntry(value, profile) {
@@ -16274,6 +16665,8 @@ function parseMcpServers(raw, filePath, root, io, profile) {
16274
16665
  let hasUrl = false;
16275
16666
  let hasServerUrl = false;
16276
16667
  let hasHttpUrl = false;
16668
+ let extensionProvided = false;
16669
+ let commandPathObject = false;
16277
16670
  let transport;
16278
16671
  let transportField;
16279
16672
  let literalEnvKeys = [];
@@ -16286,14 +16679,17 @@ function parseMcpServers(raw, filePath, root, io, profile) {
16286
16679
  hasUrl = typeof entry.url === "string" && entry.url.length > 0;
16287
16680
  hasServerUrl = typeof entry.serverUrl === "string" && entry.serverUrl.length > 0;
16288
16681
  hasHttpUrl = typeof entry.httpUrl === "string" && entry.httpUrl.length > 0;
16682
+ commandPathObject = isRecord(entry.command) && typeof entry.command.path === "string";
16683
+ extensionProvided = entry.command === undefined && entry.url === undefined && isRecord(entry.settings);
16289
16684
  const declared = transportFromEntry(entry);
16290
16685
  transport = declared.transport;
16291
16686
  transportField = declared.transportField;
16292
16687
  if (typeof entry.uses === "string" && entry.uses.length > 0) {
16293
16688
  uses = entry.uses;
16294
16689
  }
16295
- literalEnvKeys = literalEnvKeysFrom(entry);
16296
- literalCredentialFields = literalCredentialFieldsFrom(entry);
16690
+ const interpolated = profile === "qwen-json" ? INTERPOLATED_NO_PERCENT : INTERPOLATED;
16691
+ literalEnvKeys = literalEnvKeysFrom(entry, interpolated);
16692
+ literalCredentialFields = literalCredentialFieldsFrom(entry, interpolated);
16297
16693
  }
16298
16694
  const launchKind = profile === "continue-yaml" && uses !== undefined ? "registry-reference" : commandVariants.some((variant) => variant.hasCommand) ? "command" : hasUrl || hasServerUrl || hasHttpUrl ? "url" : "no-launch";
16299
16695
  const variants = launchKind === "command" ? commandVariants.filter((variant) => variant.hasCommand) : [{ hasCommand: false }];
@@ -16312,6 +16708,8 @@ function parseMcpServers(raw, filePath, root, io, profile) {
16312
16708
  ...uses === undefined ? {} : { uses },
16313
16709
  ...hasServerUrl ? { hasServerUrl: true } : {},
16314
16710
  ...hasHttpUrl ? { hasHttpUrl: true } : {},
16711
+ ...extensionProvided ? { extensionProvided: true } : {},
16712
+ ...commandPathObject ? { commandPathObject: true } : {},
16315
16713
  ...variant.command === undefined ? {} : { command: variant.command },
16316
16714
  ...variant.commandExists === undefined ? {} : { commandExists: variant.commandExists },
16317
16715
  ...variant.cwd === undefined ? {} : { cwd: variant.cwd },
@@ -16527,6 +16925,10 @@ function parseProfiledMcpFile(filePath, root, io, profile, opts = {}) {
16527
16925
  reportRootShapeOnce(filePath, io, "MCP config root is not an object");
16528
16926
  return [];
16529
16927
  }
16928
+ if (opts.serversKey !== undefined) {
16929
+ const servers = raw[opts.serversKey];
16930
+ return isRecord(servers) ? parseMcpServers({ mcpServers: servers }, filePath, root, io, profile) : [];
16931
+ }
16530
16932
  if (opts.mcpServersOptional === true && !isRecord(raw.mcpServers)) {
16531
16933
  return [];
16532
16934
  }
@@ -16780,6 +17182,36 @@ function discoverNestedContinueMcp(root, io) {
16780
17182
  }
16781
17183
  return facts;
16782
17184
  }
17185
+ function discoverGooseUserMcp(root, io) {
17186
+ const appData = process.env.APPDATA;
17187
+ const dir = process.platform === "win32" && appData !== undefined && appData.length > 0 ? join5(appData, "Block", "goose", "config") : providerHome("goose");
17188
+ const filePath = join5(dir, "config.yaml");
17189
+ const raw = readConfigFile(filePath, io, { optional: true });
17190
+ if (raw === undefined) {
17191
+ return [];
17192
+ }
17193
+ if (!isRecord(raw)) {
17194
+ reportRootShapeOnce(filePath, io, "Goose config root is not a mapping");
17195
+ return [];
17196
+ }
17197
+ if (!isRecord(raw.extensions)) {
17198
+ return [];
17199
+ }
17200
+ const servers = {};
17201
+ for (const [id, ext] of Object.entries(raw.extensions)) {
17202
+ if (!isRecord(ext) || ext.enabled === false) {
17203
+ continue;
17204
+ }
17205
+ if (ext.type === "stdio" && typeof ext.cmd === "string" && ext.cmd.length > 0) {
17206
+ servers[id] = { command: ext.cmd, args: ext.args, env: ext.envs };
17207
+ } else if (ext.type === "streamable_http" && typeof ext.uri === "string" && ext.uri.length > 0) {
17208
+ servers[id] = { url: ext.uri, headers: ext.headers };
17209
+ } else if (ext.type === "sse") {
17210
+ servers[id] = {};
17211
+ }
17212
+ }
17213
+ return parseMcpServers({ mcpServers: servers }, filePath, root, io, "goose-yaml");
17214
+ }
16783
17215
 
16784
17216
  // src/discover/claude.ts
16785
17217
  import { basename as basename6, dirname as dirname4, join as join8, resolve as resolve6 } from "node:path";
@@ -17178,6 +17610,33 @@ function devinHookFromEntry(item, ctx) {
17178
17610
  }
17179
17611
  return [commandHook(baseFact(ctx), nonemptyString(item.command), ctx.bases.project)];
17180
17612
  }
17613
+ function qwenHookFromEntry(item, ctx) {
17614
+ const typeRaw = typeof item.type === "string" ? item.type : undefined;
17615
+ if (typeRaw === undefined || !QWEN_HOOK_HANDLER_TYPES.has(typeRaw)) {
17616
+ return [baseFact(ctx, { defect: "unknown-handler-type", unknownHandlerType: unknownTypeLabel(item) })];
17617
+ }
17618
+ if (typeRaw === "http") {
17619
+ return [
17620
+ baseFact(ctx, {
17621
+ handlerType: "http",
17622
+ ...nonemptyString(item.url) === undefined ? { defect: "http-without-url" } : {}
17623
+ })
17624
+ ];
17625
+ }
17626
+ if (typeRaw === "prompt") {
17627
+ return [baseFact(ctx, { handlerType: "prompt" })];
17628
+ }
17629
+ if (typeRaw === "function") {
17630
+ return [baseFact(ctx)];
17631
+ }
17632
+ return commandFactsFromLaunches(ctx, item, "command-without-command");
17633
+ }
17634
+ function factoryHookFromEntry(item, ctx) {
17635
+ if (item.type !== "command") {
17636
+ return [baseFact(ctx, { defect: "unknown-handler-type", unknownHandlerType: unknownTypeLabel(item) })];
17637
+ }
17638
+ return [commandHook(baseFact(ctx), nonemptyString(item.command), ctx.bases.project)];
17639
+ }
17181
17640
  var ENTRY_READERS = {
17182
17641
  claude: claudeHookFromEntry,
17183
17642
  "vscode-native": vscodeNativeHookFromEntry,
@@ -17186,7 +17645,9 @@ var ENTRY_READERS = {
17186
17645
  grok: grokHookFromEntry,
17187
17646
  gemini: geminiHookFromEntry,
17188
17647
  devin: devinHookFromEntry,
17189
- codex: codexHookFromEntry
17648
+ codex: codexHookFromEntry,
17649
+ qwen: qwenHookFromEntry,
17650
+ factory: factoryHookFromEntry
17190
17651
  };
17191
17652
  function isPipedProfile(profile) {
17192
17653
  const layout = HOOK_PROFILES[profile].layout;
@@ -17382,6 +17843,9 @@ function discoverCopilotUserSettingsHooks(io) {
17382
17843
  function discoverGrokHooks(dir, projectRoot, io) {
17383
17844
  return hookJsonFiles(dir, io).flatMap((filePath) => readHooksFile(filePath, { source: "grok-hooks", sourceProvider: "grok", profile: "grok" }, { project: projectRoot, own: dir }, io));
17384
17845
  }
17846
+ function discoverQwenHooksFile(filePath, projectRoot, io) {
17847
+ return readHooksFile(filePath, { source: "qwen-settings", sourceProvider: "qwen", profile: "qwen" }, { project: projectRoot }, io);
17848
+ }
17385
17849
 
17386
17850
  // src/discover/scan-context.ts
17387
17851
  import { existsSync as existsSync3 } from "node:fs";
@@ -17411,7 +17875,7 @@ function hasWorkspaceMarker(dir) {
17411
17875
  return WORKSPACE_MARKERS.some((name) => existsSync3(join7(dir, name)));
17412
17876
  }
17413
17877
  var SCAN_ROOT_MARKER = ".agentscan-root";
17414
- function resolveScanContext(startDir) {
17878
+ function resolveScanContext(startDir, opts = {}) {
17415
17879
  const requestedDir = resolve5(startDir);
17416
17880
  let dir = requestedDir;
17417
17881
  let nearestSignal;
@@ -17441,6 +17905,15 @@ function resolveScanContext(startDir) {
17441
17905
  dir = parent;
17442
17906
  }
17443
17907
  const projectRoot = nearer(requestedDir, nearestSignal, nearestWorkspace) ?? nearestGit;
17908
+ if (projectRoot === undefined && opts.allowNoProject === true) {
17909
+ return {
17910
+ startDir: requestedDir,
17911
+ workspaceBoundary: undefined,
17912
+ repositoryBoundary: undefined,
17913
+ scanBoundary: requestedDir,
17914
+ root: requestedDir
17915
+ };
17916
+ }
17444
17917
  if (projectRoot === undefined) {
17445
17918
  throw new Error(`No package.json or agent configuration found walking up from ${requestedDir}`);
17446
17919
  }
@@ -18042,6 +18515,75 @@ function applyCommandcodeSkillPrecedence(skills, ctx) {
18042
18515
  applyPrecedence(skills, (skill) => skill.id, (skill) => rankIn(table, (dir) => isInside(resolve7(skill.path), dir)));
18043
18516
  }
18044
18517
 
18518
+ // src/discover/factory.ts
18519
+ import { existsSync as existsSync5 } from "node:fs";
18520
+ import { join as join10 } from "node:path";
18521
+ function standaloneHooks(filePath, projectRoot, io, unwrap) {
18522
+ const doc = readHooksDocument(filePath, io);
18523
+ if (doc === undefined) {
18524
+ return [];
18525
+ }
18526
+ return hooksFromObject(unwrap && isRecord(doc.hooks) ? doc.hooks : doc, filePath, "factory-hooks", { project: projectRoot }, io, {
18527
+ sourceProvider: "factory",
18528
+ schemaProfile: "factory"
18529
+ });
18530
+ }
18531
+ function factoryHooks(factoryDir, projectRoot, io, legacy) {
18532
+ const files = [join10(factoryDir, "hooks.json"), ...legacy ? [join10(factoryDir, "hooks", "hooks.json")] : []];
18533
+ if (!files.some((file) => existsSync5(file))) {
18534
+ if (legacy)
18535
+ return [];
18536
+ return readHooksFile(join10(factoryDir, "settings.json"), { source: "settings", sourceProvider: "factory", profile: "factory" }, { project: projectRoot }, io);
18537
+ }
18538
+ return files.flatMap((file, index) => standaloneHooks(file, projectRoot, io, index > 0));
18539
+ }
18540
+ function factoryMcp(factoryDir, projectRoot, io) {
18541
+ return parseProfiledMcpFile(join10(factoryDir, "mcp.json"), projectRoot, io, "factory-json", { optional: true });
18542
+ }
18543
+ function discoverFactoryProject(dir, io) {
18544
+ const root = join10(dir, ".factory");
18545
+ return { hooks: factoryHooks(root, dir, io, true), mcp: factoryMcp(root, dir, io) };
18546
+ }
18547
+ function discoverFactoryUser(projectRoot, io) {
18548
+ const home = providerHome("factory");
18549
+ return { hooks: factoryHooks(home, projectRoot, io, false), mcp: factoryMcp(home, projectRoot, io) };
18550
+ }
18551
+ function discoverFactoryDroidsDir(dir, io) {
18552
+ const facts = [];
18553
+ for (const { name } of listDir(dir, io, { optional: true }) ?? []) {
18554
+ const filePath = join10(dir, name);
18555
+ if (name.startsWith(".") || !name.endsWith(".md") || !isFile(filePath)) {
18556
+ continue;
18557
+ }
18558
+ const fm = readFrontmatter(filePath, io);
18559
+ const fact = {
18560
+ name: fm.name ?? name.slice(0, -".md".length),
18561
+ path: filePath,
18562
+ sourceProvider: "factory",
18563
+ namespace: dir,
18564
+ nameSource: fm.name === undefined ? "filename" : "frontmatter",
18565
+ hasFrontmatter: fm.hasFrontmatter
18566
+ };
18567
+ if (fm.unreadable === true || fm.unparseable === true) {
18568
+ fact.unreadable = true;
18569
+ facts.push(fact);
18570
+ continue;
18571
+ }
18572
+ const defects = [];
18573
+ if (fm.name === undefined || !FACTORY_DROID_NAME.test(fm.name)) {
18574
+ defects.push("invalid-name");
18575
+ }
18576
+ if ((fm.body ?? "").trim().length === 0) {
18577
+ defects.push("empty-body");
18578
+ }
18579
+ if (defects.length > 0) {
18580
+ fact.factoryDroidDefects = defects;
18581
+ }
18582
+ facts.push(fact);
18583
+ }
18584
+ return facts;
18585
+ }
18586
+
18045
18587
  // src/discover/agents.ts
18046
18588
  function agentFiles(dir, io) {
18047
18589
  const out = [];
@@ -18050,7 +18592,7 @@ function agentFiles(dir, io) {
18050
18592
  continue;
18051
18593
  }
18052
18594
  if (entry.endsWith("/")) {
18053
- out.push(...agentFiles(join10(dir, entry.slice(0, -1)), io).map((p) => join10(entry.slice(0, -1), p)));
18595
+ out.push(...agentFiles(join11(dir, entry.slice(0, -1)), io).map((p) => join11(entry.slice(0, -1), p)));
18054
18596
  continue;
18055
18597
  }
18056
18598
  out.push(entry);
@@ -18098,7 +18640,7 @@ function discoverClaudeAgentsDir(dir, root, io, includeFrontmatterHooks = true)
18098
18640
  if (!name.endsWith(".md")) {
18099
18641
  continue;
18100
18642
  }
18101
- const filePath = join10(dir, name);
18643
+ const filePath = join11(dir, name);
18102
18644
  if (!isFile(filePath)) {
18103
18645
  continue;
18104
18646
  }
@@ -18107,7 +18649,7 @@ function discoverClaudeAgentsDir(dir, root, io, includeFrontmatterHooks = true)
18107
18649
  return facts;
18108
18650
  }
18109
18651
  function discoverVscodeAgents(root, io) {
18110
- const dir = join10(root, ".github", "agents");
18652
+ const dir = join11(root, ".github", "agents");
18111
18653
  const facts = [];
18112
18654
  for (const { name } of listDir(dir, io, { optional: true }) ?? []) {
18113
18655
  if (name.startsWith(".")) {
@@ -18116,7 +18658,7 @@ function discoverVscodeAgents(root, io) {
18116
18658
  if (!name.endsWith(".agent.md") && !name.endsWith(".md")) {
18117
18659
  continue;
18118
18660
  }
18119
- const filePath = join10(dir, name);
18661
+ const filePath = join11(dir, name);
18120
18662
  if (!isFile(filePath)) {
18121
18663
  continue;
18122
18664
  }
@@ -18197,7 +18739,7 @@ function discoverKiroAgentsDir(dir, io) {
18197
18739
  if (name === undefined) {
18198
18740
  continue;
18199
18741
  }
18200
- const filePath = join10(dir, rel);
18742
+ const filePath = join11(dir, rel);
18201
18743
  if (!isFile(filePath)) {
18202
18744
  continue;
18203
18745
  }
@@ -18221,29 +18763,84 @@ function shadowFartherKiroAgents(pending) {
18221
18763
  return item.fact;
18222
18764
  });
18223
18765
  }
18766
+ var CODEX_AGENT_REQUIRED = ["name", "description", "developer_instructions"];
18767
+ function discoverCodexAgentsDir(dir, io) {
18768
+ const facts = [];
18769
+ for (const entry of listDir(dir, io, { optional: true }) ?? []) {
18770
+ if (entry.name.startsWith(".") || !entry.name.endsWith(".toml")) {
18771
+ continue;
18772
+ }
18773
+ const filePath = join11(dir, entry.name);
18774
+ if (!isFile(filePath)) {
18775
+ continue;
18776
+ }
18777
+ const fact = {
18778
+ name: entry.name.slice(0, -".toml".length),
18779
+ path: filePath,
18780
+ sourceProvider: "codex",
18781
+ namespace: dir,
18782
+ nameSource: "filename",
18783
+ hasFrontmatter: false
18784
+ };
18785
+ const value = readConfigFile(filePath, io);
18786
+ if (value === undefined) {
18787
+ fact.unreadable = true;
18788
+ } else if (!isRecord(value)) {
18789
+ reportRootShapeOnce(filePath, io, "Codex agent root is not a table");
18790
+ fact.unreadable = true;
18791
+ } else {
18792
+ if (typeof value.name === "string") {
18793
+ fact.name = value.name;
18794
+ fact.nameSource = "frontmatter";
18795
+ }
18796
+ const missing = CODEX_AGENT_REQUIRED.filter((key) => typeof value[key] !== "string");
18797
+ if (missing.length > 0) {
18798
+ fact.codexMissingFields = missing;
18799
+ }
18800
+ }
18801
+ facts.push(fact);
18802
+ }
18803
+ return facts;
18804
+ }
18224
18805
  function discoverAgents(root, io, startDir = root, includeGlobal = false, commandcodeProjectRoot = root) {
18225
18806
  const facts = [];
18226
18807
  const kiroPending = [];
18227
18808
  const seenDirs = new Set;
18228
18809
  const ancestors = ancestorDirsInclusive(startDir, root);
18229
18810
  for (const [hop, dir] of ancestors.entries()) {
18230
- const agentsDir = join10(dir, ".claude", "agents");
18811
+ const agentsDir = join11(dir, ".claude", "agents");
18231
18812
  if (!seenDirs.has(agentsDir)) {
18232
18813
  seenDirs.add(agentsDir);
18233
18814
  facts.push(...discoverClaudeAgentsDir(agentsDir, root, io));
18234
18815
  facts.push(...discoverVscodeAgents(dir, io));
18235
18816
  }
18236
- for (const fact of discoverKiroAgentsDir(join10(dir, ".kiro", "agents"), io)) {
18817
+ for (const fact of discoverKiroAgentsDir(join11(dir, ".kiro", "agents"), io)) {
18237
18818
  kiroPending.push({ fact, hops: hop });
18238
18819
  }
18820
+ facts.push(...discoverCodexAgentsDir(join11(dir, ".codex", "agents"), io));
18821
+ facts.push(...discoverFactoryDroidsDir(join11(dir, ".factory", "droids"), io));
18239
18822
  }
18240
18823
  facts.push(...discoverCommandcodeAgents(commandcodeProjectRoot, includeGlobal, io));
18241
18824
  if (includeGlobal) {
18242
- facts.push(...discoverClaudeAgentsDir(join10(providerHome("claude"), "agents"), root, io));
18243
- for (const fact of discoverKiroAgentsDir(join10(providerHome("kiro"), "agents"), io)) {
18244
- kiroPending.push({ fact, hops: KIRO_GLOBAL_HOPS });
18825
+ const claudeGlobal = join11(providerHome("claude"), "agents");
18826
+ if (!seenDirs.has(claudeGlobal)) {
18827
+ facts.push(...discoverClaudeAgentsDir(claudeGlobal, root, io));
18828
+ }
18829
+ const codexGlobal = join11(providerHome("codex"), "agents");
18830
+ if (!ancestors.some((dir) => join11(dir, ".codex", "agents") === codexGlobal)) {
18831
+ facts.push(...discoverCodexAgentsDir(codexGlobal, io));
18832
+ }
18833
+ const kiroGlobal = join11(providerHome("kiro"), "agents");
18834
+ if (!ancestors.some((dir) => join11(dir, ".kiro", "agents") === kiroGlobal)) {
18835
+ for (const fact of discoverKiroAgentsDir(kiroGlobal, io)) {
18836
+ kiroPending.push({ fact, hops: KIRO_GLOBAL_HOPS });
18837
+ }
18245
18838
  }
18246
18839
  }
18840
+ const factoryGlobal = join11(providerHome("factory"), "droids");
18841
+ if (includeGlobal && !ancestors.some((dir) => join11(dir, ".factory", "droids") === factoryGlobal)) {
18842
+ facts.push(...discoverFactoryDroidsDir(factoryGlobal, io));
18843
+ }
18247
18844
  facts.push(...shadowFartherKiroAgents(kiroPending));
18248
18845
  return facts;
18249
18846
  }
@@ -18333,10 +18930,10 @@ function handlerFact(event, item, filePath, projectRoot) {
18333
18930
  }
18334
18931
 
18335
18932
  // src/discover/codex.ts
18336
- import { isAbsolute as isAbsolute6, join as join11 } from "node:path";
18933
+ import { isAbsolute as isAbsolute6, join as join12 } from "node:path";
18337
18934
  function discoverCodexUserMcp(io) {
18338
18935
  const home = providerHome("codex");
18339
- return parseCodexTomlFile(join11(home, "config.toml"), home, io, { optional: true });
18936
+ return parseCodexTomlFile(join12(home, "config.toml"), home, io, { optional: true });
18340
18937
  }
18341
18938
  var CODEX_PROJECT_DOC_MAX_BYTES = 32768;
18342
18939
  function readCodexProjectConfig(dirs, mcpPaths, io) {
@@ -18344,7 +18941,7 @@ function readCodexProjectConfig(dirs, mcpPaths, io) {
18344
18941
  for (const dir of dirs) {
18345
18942
  const inDir = {};
18346
18943
  for (const rel of mcpPaths) {
18347
- const filePath = isAbsolute6(rel) ? rel : join11(dir, rel);
18944
+ const filePath = isAbsolute6(rel) ? rel : join12(dir, rel);
18348
18945
  if (mcpProfileFromPath(filePath) !== "codex-toml") {
18349
18946
  continue;
18350
18947
  }
@@ -18383,12 +18980,12 @@ var CODEX_HOOKS_FILE = {
18383
18980
  };
18384
18981
  function discoverCodexHookFiles(dir, projectRoot, io) {
18385
18982
  return [
18386
- ...readHooksFile(join11(dir, "hooks.json"), CODEX_HOOKS_FILE, { project: projectRoot }, io),
18387
- ...hooksFromCodexToml(join11(dir, "config.toml"), projectRoot, io)
18983
+ ...readHooksFile(join12(dir, "hooks.json"), CODEX_HOOKS_FILE, { project: projectRoot }, io),
18984
+ ...hooksFromCodexToml(join12(dir, "config.toml"), projectRoot, io)
18388
18985
  ];
18389
18986
  }
18390
18987
  function discoverCodexProjectHooks(dir, io) {
18391
- return discoverCodexHookFiles(join11(dir, ".codex"), dir, io);
18988
+ return discoverCodexHookFiles(join12(dir, ".codex"), dir, io);
18392
18989
  }
18393
18990
  function discoverCodexUserHooks(projectRoot, io) {
18394
18991
  return discoverCodexHookFiles(providerHome("codex"), projectRoot, io);
@@ -18405,7 +19002,8 @@ function hooksFromCodexToml(filePath, projectRoot, io) {
18405
19002
  if (raw.hooks === undefined) {
18406
19003
  return [];
18407
19004
  }
18408
- return hooksFromObject(raw.hooks, filePath, "codex-hooks", { project: projectRoot }, io, {
19005
+ const hooks = isRecord(raw.hooks) ? Object.fromEntries(Object.entries(raw.hooks).filter(([key]) => key !== "state")) : raw.hooks;
19006
+ return hooksFromObject(hooks, filePath, "codex-hooks", { project: projectRoot }, io, {
18409
19007
  sourceProvider: "codex",
18410
19008
  schemaProfile: "codex"
18411
19009
  });
@@ -18418,7 +19016,7 @@ function stringArray(value) {
18418
19016
  }
18419
19017
 
18420
19018
  // src/discover/cursor.ts
18421
- import { join as join12 } from "node:path";
19019
+ import { join as join13 } from "node:path";
18422
19020
  function discoverCursorHooksFile(filePath, projectRoot, io) {
18423
19021
  return readFlatHooks(filePath, "cursor", io, (item, base) => {
18424
19022
  if (!isRecord(item)) {
@@ -18438,11 +19036,11 @@ function discoverCursorHooksFile(filePath, projectRoot, io) {
18438
19036
  });
18439
19037
  }
18440
19038
  function discoverCursorUserMcp(projectRoot, io) {
18441
- return parseMcpFile(join12(providerHome("cursor"), "mcp.json"), projectRoot, io, { optional: true });
19039
+ return parseMcpFile(join13(providerHome("cursor"), "mcp.json"), projectRoot, io, { optional: true });
18442
19040
  }
18443
19041
 
18444
19042
  // src/discover/devin.ts
18445
- import { join as join13 } from "node:path";
19043
+ import { join as join14 } from "node:path";
18446
19044
  var SETTINGS_HOOKS = {
18447
19045
  source: "settings",
18448
19046
  sourceProvider: "devin",
@@ -18460,29 +19058,29 @@ function discoverDevinSettingsHooks(filePath, projectRoot, io) {
18460
19058
  }
18461
19059
  function discoverDevinMcpFiles(dir, projectRoot, io) {
18462
19060
  return [
18463
- ...parseProfiledMcpFile(join13(dir, "config.json"), projectRoot, io, "devin-json", {
19061
+ ...parseProfiledMcpFile(join14(dir, "config.json"), projectRoot, io, "devin-json", {
18464
19062
  optional: true,
18465
19063
  mcpServersOptional: true
18466
19064
  }),
18467
- ...parseProfiledMcpFile(join13(dir, "config.local.json"), projectRoot, io, "devin-json", {
19065
+ ...parseProfiledMcpFile(join14(dir, "config.local.json"), projectRoot, io, "devin-json", {
18468
19066
  optional: true,
18469
19067
  mcpServersOptional: true
18470
19068
  }),
18471
- ...parseProfiledMcpFile(join13(dir, "mcp_config.json"), projectRoot, io, "devin-json", {
19069
+ ...parseProfiledMcpFile(join14(dir, "mcp_config.json"), projectRoot, io, "devin-json", {
18472
19070
  optional: true
18473
19071
  }),
18474
- ...parseProfiledMcpFile(join13(dir, "mcp_config.local.json"), projectRoot, io, "devin-json", {
19072
+ ...parseProfiledMcpFile(join14(dir, "mcp_config.local.json"), projectRoot, io, "devin-json", {
18475
19073
  optional: true
18476
19074
  })
18477
19075
  ];
18478
19076
  }
18479
19077
  function discoverDevinProject(dir, io) {
18480
- const root = join13(dir, ".devin");
19078
+ const root = join14(dir, ".devin");
18481
19079
  return {
18482
19080
  hooks: [
18483
- ...discoverDevinV1Hooks(join13(root, "hooks.v1.json"), dir, io),
18484
- ...discoverDevinSettingsHooks(join13(root, "config.json"), dir, io),
18485
- ...discoverDevinSettingsHooks(join13(root, "config.local.json"), dir, io)
19081
+ ...discoverDevinV1Hooks(join14(root, "hooks.v1.json"), dir, io),
19082
+ ...discoverDevinSettingsHooks(join14(root, "config.json"), dir, io),
19083
+ ...discoverDevinSettingsHooks(join14(root, "config.local.json"), dir, io)
18486
19084
  ],
18487
19085
  mcp: discoverDevinMcpFiles(root, dir, io)
18488
19086
  };
@@ -18490,7 +19088,7 @@ function discoverDevinProject(dir, io) {
18490
19088
  function discoverDevinUser(projectRoot, io) {
18491
19089
  const home = providerHome("devin");
18492
19090
  return {
18493
- hooks: discoverDevinSettingsHooks(join13(home, "config.json"), projectRoot, io),
19091
+ hooks: discoverDevinSettingsHooks(join14(home, "config.json"), projectRoot, io),
18494
19092
  mcp: discoverDevinMcpFiles(home, projectRoot, io)
18495
19093
  };
18496
19094
  }
@@ -18547,13 +19145,13 @@ function discoverKiroHooksFile(filePath, projectRoot, io) {
18547
19145
  }
18548
19146
 
18549
19147
  // src/discover/grok.ts
18550
- import { dirname as dirname7, join as join14, resolve as resolve8 } from "node:path";
19148
+ import { dirname as dirname7, join as join15, resolve as resolve8 } from "node:path";
18551
19149
  function discoverGrokUserMcp(io) {
18552
19150
  const home = providerHome("grok");
18553
- return parseGrokTomlFile(join14(home, "config.toml"), home, io, { optional: true });
19151
+ return parseGrokTomlFile(join15(home, "config.toml"), home, io, { optional: true });
18554
19152
  }
18555
19153
  function applyGrokMcpPrecedence(mcp, startDir) {
18556
- const userFile = resolve8(join14(providerHome("grok"), "config.toml"));
19154
+ const userFile = resolve8(join15(providerHome("grok"), "config.toml"));
18557
19155
  applyPrecedence(mcp, (entry) => entry.name, (entry) => entry.schemaProfile === "grok-toml" ? grokMcpScore(entry, startDir, userFile) : undefined);
18558
19156
  }
18559
19157
  function grokMcpScore(entry, startDir, userFile) {
@@ -18565,11 +19163,11 @@ function grokMcpScore(entry, startDir, userFile) {
18565
19163
  }
18566
19164
 
18567
19165
  // src/discover/plugins.ts
18568
- import { existsSync as existsSync5 } from "node:fs";
18569
- import { join as join16, resolve as resolve10 } from "node:path";
19166
+ import { existsSync as existsSync6 } from "node:fs";
19167
+ import { join as join17, resolve as resolve10 } from "node:path";
18570
19168
 
18571
19169
  // src/discover/skills.ts
18572
- import { basename as basename8, join as join15, resolve as resolve9 } from "node:path";
19170
+ import { basename as basename8, dirname as dirname8, join as join16, resolve as resolve9 } from "node:path";
18573
19171
  var BUNDLED = "scripts|references|assets|templates|examples";
18574
19172
  var REFERENCE_RE = new RegExp(`(?<![\\w/.-])(?:\\./)?((?:${BUNDLED})/[A-Za-z0-9_./-]+\\.[A-Za-z0-9]{1,10})(?![A-Za-z0-9_.-])`, "g");
18575
19173
  var FENCE_BLOCK = /^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?(?:^[ \t]*\1[ \t]*$|(?![\s\S]))/gm;
@@ -18589,28 +19187,36 @@ function skillReferences(body) {
18589
19187
  }
18590
19188
  return out;
18591
19189
  }
18592
- function firstMarkdownParagraph(body) {
18593
- const blocks = body.replace(/\r\n/g, `
18594
- `).trim().split(/\n\s*\n/);
18595
- for (const block of blocks) {
18596
- const lines = block.split(`
18597
- `).filter((line) => !/^#{1,6}\s/.test(line.trim()));
18598
- const text = lines.map((line) => line.trim()).filter((line) => line.length > 0).join(" ");
18599
- if (text.length > 0) {
18600
- return text;
19190
+ function firstNonEmptyLine(body) {
19191
+ return body.split(/\r?\n/).map((line) => line.trim()).find((line) => line.length > 0);
19192
+ }
19193
+ function inNamedSibling(body, skillDir, rel) {
19194
+ const needle = `\`${rel}\``;
19195
+ for (let at = body.indexOf(needle);at !== -1; at = body.indexOf(needle, at + needle.length)) {
19196
+ const before = body.slice(0, at);
19197
+ const start = Math.max(before.lastIndexOf("."), before.search(/\n\s*\n(?![\s\S]*\n\s*\n)/));
19198
+ for (const match of before.slice(start + 1).matchAll(/`([a-z0-9][a-z0-9-]*)`/g)) {
19199
+ const sibling = match[1];
19200
+ if (sibling !== undefined && isFile(join16(dirname8(skillDir), sibling, rel))) {
19201
+ return true;
19202
+ }
18601
19203
  }
18602
19204
  }
18603
- return;
19205
+ return false;
18604
19206
  }
18605
19207
  function brokenReferences(body, skillDir, root, skillRootOnly) {
18606
19208
  return skillReferences(body).filter((rel) => {
18607
- if (isFile(join15(skillDir, rel))) {
19209
+ if (isFile(join16(skillDir, rel)) || inNamedSibling(body, skillDir, rel)) {
18608
19210
  return false;
18609
19211
  }
18610
19212
  if (skillRootOnly) {
18611
19213
  return true;
18612
19214
  }
18613
- return !isFile(join15(root, rel));
19215
+ if (isFile(join16(root, rel))) {
19216
+ return false;
19217
+ }
19218
+ const marker = skillsMarker(skillDir)?.root;
19219
+ return marker === undefined || !isFile(join16(dirname8(dirname8(marker)), rel));
18614
19220
  });
18615
19221
  }
18616
19222
  function findSkillMdDirs(dir, io, depth = 0) {
@@ -18618,7 +19224,7 @@ function findSkillMdDirs(dir, io, depth = 0) {
18618
19224
  if (names === undefined) {
18619
19225
  return [];
18620
19226
  }
18621
- if (isFile(join15(dir, "SKILL.md"))) {
19227
+ if (isFile(join16(dir, "SKILL.md"))) {
18622
19228
  return [dir];
18623
19229
  }
18624
19230
  if (depth >= NESTED_DISCOVERY_MAX_DEPTH) {
@@ -18629,7 +19235,7 @@ function findSkillMdDirs(dir, io, depth = 0) {
18629
19235
  if (name.startsWith(".") || NESTED_DISCOVERY_SKIP.has(name)) {
18630
19236
  continue;
18631
19237
  }
18632
- const child = join15(dir, name);
19238
+ const child = join16(dir, name);
18633
19239
  if (!isRealDirectory(child)) {
18634
19240
  continue;
18635
19241
  }
@@ -18688,7 +19294,7 @@ function applyAgentSkillsOptionalFields(fact, fields) {
18688
19294
  function skillFactFromDir(skillDir, source, skillsRoot, io, root, opts) {
18689
19295
  const sourceProvider = opts.provider ?? providerFromSkillsDir(skillsRoot);
18690
19296
  const schemaProfile = opts.profile ?? skillSchemaFor(sourceProvider);
18691
- const skillMd = join15(skillDir, "SKILL.md");
19297
+ const skillMd = join16(skillDir, "SKILL.md");
18692
19298
  const fm = opts.hasSkillMd ? readFrontmatter(skillMd, io) : { hasFrontmatter: false };
18693
19299
  const fact = {
18694
19300
  id: basename8(skillDir),
@@ -18710,9 +19316,9 @@ function skillFactFromDir(skillDir, source, skillsRoot, io, root, opts) {
18710
19316
  if (broken.length > 0) {
18711
19317
  fact.brokenReferences = broken;
18712
19318
  }
18713
- const paragraph = firstMarkdownParagraph(fm.body);
18714
- if (paragraph !== undefined) {
18715
- fact.firstMarkdownParagraph = paragraph;
19319
+ const firstLine = firstNonEmptyLine(fm.body);
19320
+ if (firstLine !== undefined) {
19321
+ fact.firstNonEmptyLine = firstLine;
18716
19322
  }
18717
19323
  }
18718
19324
  if (fm.lineCount !== undefined) {
@@ -18748,7 +19354,7 @@ function skillFactFromDir(skillDir, source, skillsRoot, io, root, opts) {
18748
19354
  return fact;
18749
19355
  }
18750
19356
  function discoverSkillAtDir(skillDir, source, io, root, provider) {
18751
- const skillMd = join15(skillDir, "SKILL.md");
19357
+ const skillMd = join16(skillDir, "SKILL.md");
18752
19358
  if (!isFile(skillMd)) {
18753
19359
  return;
18754
19360
  }
@@ -18765,7 +19371,7 @@ function discoverSkillsInDir(dir, source, io, root, provider, profile) {
18765
19371
  if (name.startsWith(".") || NESTED_DISCOVERY_SKIP.has(name)) {
18766
19372
  continue;
18767
19373
  }
18768
- const child = join15(dir, name);
19374
+ const child = join16(dir, name);
18769
19375
  if (!isRealDirectory(child)) {
18770
19376
  continue;
18771
19377
  }
@@ -18795,7 +19401,7 @@ function discoverNestedClaudeSkills(root, configuredRoots, io, options = {}) {
18795
19401
  for (const entry of entries) {
18796
19402
  if (!entry.isDirectory() || entry.name !== "skills")
18797
19403
  continue;
18798
- const child = join15(dir, entry.name);
19404
+ const child = join16(dir, entry.name);
18799
19405
  if (configuredRoots.has(resolve9(child)))
18800
19406
  continue;
18801
19407
  out.push(...discoverSkillsInDir(child, "project", io, root));
@@ -18810,7 +19416,7 @@ function discoverNestedClaudeSkills(root, configuredRoots, io, options = {}) {
18810
19416
  continue;
18811
19417
  if (entry.name.startsWith(".") && !SKILL_DOT_DIRS.has(entry.name))
18812
19418
  continue;
18813
- const child = join15(dir, entry.name);
19419
+ const child = join16(dir, entry.name);
18814
19420
  if (SKILL_DOT_DIRS.has(entry.name)) {
18815
19421
  inspectSkillsParent(child);
18816
19422
  continue;
@@ -18838,7 +19444,7 @@ function disambiguateSkills(skills, root) {
18838
19444
  // src/discover/plugins.ts
18839
19445
  function findPluginRoots(root, io) {
18840
19446
  const found = [];
18841
- if (existsSync5(join16(root, "hooks", "hooks.json"))) {
19447
+ if (existsSync6(join17(root, "hooks", "hooks.json"))) {
18842
19448
  found.push(root);
18843
19449
  }
18844
19450
  const walk = (dir, depth) => {
@@ -18850,8 +19456,8 @@ function findPluginRoots(root, io) {
18850
19456
  if (entry.name.startsWith(".") && entry.name !== ".claude") {
18851
19457
  continue;
18852
19458
  }
18853
- const child = join16(dir, entry.name);
18854
- if (existsSync5(join16(child, ".claude-plugin", "plugin.json"))) {
19459
+ const child = join17(dir, entry.name);
19460
+ if (existsSync6(join17(child, ".claude-plugin", "plugin.json"))) {
18855
19461
  found.push(child);
18856
19462
  continue;
18857
19463
  }
@@ -18867,7 +19473,7 @@ function findPluginRoots(root, io) {
18867
19473
  function discoverPluginHooks(root, io, pluginRoots) {
18868
19474
  const facts = [];
18869
19475
  for (const pluginRoot of pluginRoots) {
18870
- facts.push(...readHooksFile(join16(pluginRoot, "hooks", "hooks.json"), { source: "plugin", sourceProvider: "claude", profile: "claude" }, { project: root, plugin: pluginRoot, own: pluginRoot }, io));
19476
+ facts.push(...readHooksFile(join17(pluginRoot, "hooks", "hooks.json"), { source: "plugin", sourceProvider: "claude", profile: "claude" }, { project: root, plugin: pluginRoot, own: pluginRoot }, io));
18871
19477
  }
18872
19478
  return facts;
18873
19479
  }
@@ -18877,25 +19483,25 @@ function discoverPluginSurfaces(root, io, configuredSkillRoots, pluginRoots) {
18877
19483
  const mcp = [];
18878
19484
  const commands = [];
18879
19485
  for (const pluginRoot of pluginRoots) {
18880
- const skillsDir = resolve10(join16(pluginRoot, "skills"));
19486
+ const skillsDir = resolve10(join17(pluginRoot, "skills"));
18881
19487
  if (!configuredSkillRoots.has(skillsDir)) {
18882
19488
  configuredSkillRoots.add(skillsDir);
18883
- skills.push(...discoverSkillsInDir(join16(pluginRoot, "skills"), "project", io, root, "claude"));
19489
+ skills.push(...discoverSkillsInDir(join17(pluginRoot, "skills"), "project", io, root, "claude"));
18884
19490
  }
18885
19491
  const rootSkill = discoverSkillAtDir(pluginRoot, "project", io, root, "claude");
18886
19492
  if (rootSkill !== undefined) {
18887
19493
  skills.push(rootSkill);
18888
19494
  }
18889
- agents.push(...discoverClaudeAgentsDir(join16(pluginRoot, "agents"), root, io, false));
18890
- commands.push(...discoverClaudeCommandsInDir(join16(pluginRoot, "commands"), "project", root, io));
18891
- mcp.push(...parseClaudePluginMcpFile(join16(pluginRoot, ".mcp.json"), root, io));
19495
+ agents.push(...discoverClaudeAgentsDir(join17(pluginRoot, "agents"), root, io, false));
19496
+ commands.push(...discoverClaudeCommandsInDir(join17(pluginRoot, "commands"), "project", root, io));
19497
+ mcp.push(...parseClaudePluginMcpFile(join17(pluginRoot, ".mcp.json"), root, io));
18892
19498
  }
18893
19499
  return { skills, agents, mcp, commands };
18894
19500
  }
18895
19501
 
18896
19502
  // src/discover/policy.ts
18897
- import { existsSync as existsSync6 } from "node:fs";
18898
- import { basename as basename9, dirname as dirname8, join as join17, resolve as resolve11 } from "node:path";
19503
+ import { existsSync as existsSync7 } from "node:fs";
19504
+ import { basename as basename9, dirname as dirname9, join as join18, resolve as resolve11 } from "node:path";
18899
19505
  function readPolicyFile(filePath, io, meta) {
18900
19506
  const result = readCapped(filePath, POLICY_CAP, io, { optional: true });
18901
19507
  if (result === undefined) {
@@ -18906,7 +19512,7 @@ function readPolicyFile(filePath, io, meta) {
18906
19512
  text: result.text,
18907
19513
  sourceProvider: meta.sourceProvider,
18908
19514
  kind: meta.kind,
18909
- hopsFromStart: hops(meta.startDir, meta.hopsDir ?? dirname8(filePath))
19515
+ hopsFromStart: hops(meta.startDir, meta.hopsDir ?? dirname9(filePath))
18910
19516
  };
18911
19517
  }
18912
19518
  function addUnique(out, seen, fact) {
@@ -18928,7 +19534,7 @@ function resolveCodexProjectRoot(startDir, scanBoundary, markers) {
18928
19534
  }
18929
19535
  const names = markers ?? [".git"];
18930
19536
  for (const dir of ancestorDirsInclusive(start, boundary)) {
18931
- if (names.some((name) => existsSync6(join17(dir, name)))) {
19537
+ if (names.some((name) => existsSync7(join18(dir, name)))) {
18932
19538
  return dir;
18933
19539
  }
18934
19540
  }
@@ -18953,7 +19559,7 @@ function codexInstructionChain(files, startDir, codexRoot, fallbacks) {
18953
19559
  if (!isAgentsMdPolicy(policy) && !isFallbackPolicy(policy, fallbacks)) {
18954
19560
  return false;
18955
19561
  }
18956
- const dir = resolve11(dirname8(policy.path));
19562
+ const dir = resolve11(dirname9(policy.path));
18957
19563
  if (basename9(dir) === ".commandcode" && basename9(policy.path) === "AGENTS.md") {
18958
19564
  return false;
18959
19565
  }
@@ -18961,7 +19567,7 @@ function codexInstructionChain(files, startDir, codexRoot, fallbacks) {
18961
19567
  });
18962
19568
  const byDir = new Map;
18963
19569
  for (const policy of onChain) {
18964
- const dir = resolve11(dirname8(policy.path));
19570
+ const dir = resolve11(dirname9(policy.path));
18965
19571
  const bucket = byDir.get(dir);
18966
19572
  if (bucket === undefined) {
18967
19573
  byDir.set(dir, [policy]);
@@ -19002,7 +19608,7 @@ function pickCodexFile(group, fallbacks) {
19002
19608
  return;
19003
19609
  }
19004
19610
  function isCommandcodeMemoryFallback(abs) {
19005
- return basename9(abs) === "AGENTS.md" && basename9(dirname8(abs)) === ".commandcode";
19611
+ return basename9(abs) === "AGENTS.md" && basename9(dirname9(abs)) === ".commandcode";
19006
19612
  }
19007
19613
  function discoverPolicyFiles(root, policyFiles, io, startDir = root, includeGlobal = false, fallbackFilenames = []) {
19008
19614
  const out = [];
@@ -19011,38 +19617,38 @@ function discoverPolicyFiles(root, policyFiles, io, startDir = root, includeGlob
19011
19617
  for (const rel of policyFiles) {
19012
19618
  const kind = basename9(rel) === "CLAUDE.md" || basename9(rel) === "CLAUDE.local.md" ? "claude-md" : basename9(rel).endsWith(".instructions.md") || basename9(rel) === "copilot-instructions.md" ? "vscode-instructions" : "agents-md";
19013
19619
  const sourceProvider = kind === "claude-md" ? "claude" : kind === "vscode-instructions" ? "vscode" : "unknown";
19014
- addUnique(out, seen, readPolicyFile(join17(root, rel), io, { sourceProvider, kind, startDir: start }));
19620
+ addUnique(out, seen, readPolicyFile(join18(root, rel), io, { sourceProvider, kind, startDir: start }));
19015
19621
  }
19016
19622
  for (const dir of ancestorDirsInclusive(start, root)) {
19017
- addUnique(out, seen, readPolicyFile(join17(dir, "AGENTS.md"), io, {
19623
+ addUnique(out, seen, readPolicyFile(join18(dir, "AGENTS.md"), io, {
19018
19624
  sourceProvider: "unknown",
19019
19625
  kind: "agents-md",
19020
19626
  startDir: start
19021
19627
  }));
19022
- addUnique(out, seen, readPolicyFile(join17(dir, "agents.md"), io, {
19628
+ addUnique(out, seen, readPolicyFile(join18(dir, "agents.md"), io, {
19023
19629
  sourceProvider: "unknown",
19024
19630
  kind: "agents-md",
19025
19631
  startDir: start
19026
19632
  }));
19027
- addUnique(out, seen, readPolicyFile(join17(dir, "Agents.md"), io, {
19633
+ addUnique(out, seen, readPolicyFile(join18(dir, "Agents.md"), io, {
19028
19634
  sourceProvider: "grok",
19029
19635
  kind: "agents-md",
19030
19636
  startDir: start
19031
19637
  }));
19032
- addUnique(out, seen, readPolicyFile(join17(dir, "AGENT.md"), io, {
19638
+ addUnique(out, seen, readPolicyFile(join18(dir, "AGENT.md"), io, {
19033
19639
  sourceProvider: "grok",
19034
19640
  kind: "agents-md",
19035
19641
  startDir: start
19036
19642
  }));
19037
- if (!existsSync6(join17(dir, "AGENTS.md"))) {
19038
- addUnique(out, seen, readPolicyFile(join17(dir, ".commandcode", "AGENTS.md"), io, {
19643
+ if (!existsSync7(join18(dir, "AGENTS.md"))) {
19644
+ addUnique(out, seen, readPolicyFile(join18(dir, ".commandcode", "AGENTS.md"), io, {
19039
19645
  sourceProvider: "commandcode",
19040
19646
  kind: "agents-md",
19041
19647
  startDir: start,
19042
19648
  hopsDir: dir
19043
19649
  }));
19044
19650
  }
19045
- addUnique(out, seen, readPolicyFile(join17(dir, "AGENTS.override.md"), io, {
19651
+ addUnique(out, seen, readPolicyFile(join18(dir, "AGENTS.override.md"), io, {
19046
19652
  sourceProvider: "codex",
19047
19653
  kind: "agents-md",
19048
19654
  startDir: start
@@ -19051,23 +19657,23 @@ function discoverPolicyFiles(root, policyFiles, io, startDir = root, includeGlob
19051
19657
  if (name === "AGENTS.md" || name === "AGENTS.override.md" || basename9(name) !== name) {
19052
19658
  continue;
19053
19659
  }
19054
- addUnique(out, seen, readPolicyFile(join17(dir, name), io, {
19660
+ addUnique(out, seen, readPolicyFile(join18(dir, name), io, {
19055
19661
  sourceProvider: "codex",
19056
19662
  kind: "agents-md",
19057
19663
  startDir: start
19058
19664
  }));
19059
19665
  }
19060
- addUnique(out, seen, readPolicyFile(join17(dir, "CLAUDE.md"), io, {
19666
+ addUnique(out, seen, readPolicyFile(join18(dir, "CLAUDE.md"), io, {
19061
19667
  sourceProvider: "claude",
19062
19668
  kind: "claude-md",
19063
19669
  startDir: start
19064
19670
  }));
19065
- addUnique(out, seen, readPolicyFile(join17(dir, ".claude", "CLAUDE.md"), io, {
19671
+ addUnique(out, seen, readPolicyFile(join18(dir, ".claude", "CLAUDE.md"), io, {
19066
19672
  sourceProvider: "claude",
19067
19673
  kind: "claude-md",
19068
19674
  startDir: start
19069
19675
  }));
19070
- addUnique(out, seen, readPolicyFile(join17(dir, "CLAUDE.local.md"), io, {
19676
+ addUnique(out, seen, readPolicyFile(join18(dir, "CLAUDE.local.md"), io, {
19071
19677
  sourceProvider: "claude",
19072
19678
  kind: "claude-md",
19073
19679
  startDir: start
@@ -19082,8 +19688,8 @@ function discoverPolicyFiles(root, policyFiles, io, startDir = root, includeGlob
19082
19688
  if (name !== "AGENTS.md") {
19083
19689
  return false;
19084
19690
  }
19085
- if (basename9(dirname8(abs)) === ".commandcode") {
19086
- return !existsSync6(join17(dirname8(dirname8(abs)), "AGENTS.md"));
19691
+ if (basename9(dirname9(abs)) === ".commandcode") {
19692
+ return !existsSync7(join18(dirname9(dirname9(abs)), "AGENTS.md"));
19087
19693
  }
19088
19694
  return true;
19089
19695
  },
@@ -19094,15 +19700,15 @@ function discoverPolicyFiles(root, policyFiles, io, startDir = root, includeGlob
19094
19700
  sourceProvider: commandcodeMemory ? "commandcode" : basename9(abs) === "AGENTS.override.md" ? "codex" : basename9(abs) === "Agents.md" || basename9(abs) === "AGENT.md" ? "grok" : "unknown",
19095
19701
  kind: "agents-md",
19096
19702
  startDir: start,
19097
- ...commandcodeMemory ? { hopsDir: dirname8(dirname8(abs)) } : {}
19703
+ ...commandcodeMemory ? { hopsDir: dirname9(dirname9(abs)) } : {}
19098
19704
  }));
19099
19705
  }
19100
- addUnique(out, seen, readPolicyFile(join17(root, ".github", "copilot-instructions.md"), io, {
19706
+ addUnique(out, seen, readPolicyFile(join18(root, ".github", "copilot-instructions.md"), io, {
19101
19707
  sourceProvider: "vscode",
19102
19708
  kind: "vscode-instructions",
19103
19709
  startDir: start
19104
19710
  }));
19105
- const instructionsDir = join17(root, ".github", "instructions");
19711
+ const instructionsDir = join18(root, ".github", "instructions");
19106
19712
  for (const abs of walkFiles(instructionsDir, {
19107
19713
  maxDepth: NESTED_DISCOVERY_MAX_DEPTH,
19108
19714
  match: (_abs, name) => name.endsWith(".instructions.md"),
@@ -19115,22 +19721,22 @@ function discoverPolicyFiles(root, policyFiles, io, startDir = root, includeGlob
19115
19721
  }));
19116
19722
  }
19117
19723
  if (includeGlobal) {
19118
- addUnique(out, seen, readPolicyFile(join17(providerHome("codex"), "AGENTS.override.md"), io, {
19724
+ addUnique(out, seen, readPolicyFile(join18(providerHome("codex"), "AGENTS.override.md"), io, {
19119
19725
  sourceProvider: "codex",
19120
19726
  kind: "agents-md",
19121
19727
  startDir: start
19122
19728
  }));
19123
- addUnique(out, seen, readPolicyFile(join17(providerHome("codex"), "AGENTS.md"), io, {
19729
+ addUnique(out, seen, readPolicyFile(join18(providerHome("codex"), "AGENTS.md"), io, {
19124
19730
  sourceProvider: "codex",
19125
19731
  kind: "agents-md",
19126
19732
  startDir: start
19127
19733
  }));
19128
- addUnique(out, seen, readPolicyFile(join17(providerHome("commandcode"), "AGENTS.md"), io, {
19734
+ addUnique(out, seen, readPolicyFile(join18(providerHome("commandcode"), "AGENTS.md"), io, {
19129
19735
  sourceProvider: "commandcode",
19130
19736
  kind: "agents-md",
19131
19737
  startDir: start
19132
19738
  }));
19133
- addUnique(out, seen, readPolicyFile(join17(providerHome("claude"), "CLAUDE.md"), io, {
19739
+ addUnique(out, seen, readPolicyFile(join18(providerHome("claude"), "CLAUDE.md"), io, {
19134
19740
  sourceProvider: "claude",
19135
19741
  kind: "claude-md",
19136
19742
  startDir: start
@@ -19171,10 +19777,13 @@ function parseSkillsLockFile(filePath, io) {
19171
19777
  });
19172
19778
  return { entries: [], invalid: true };
19173
19779
  }
19174
- const lockRoot = dirname8(resolve11(filePath));
19780
+ const lockRoot = dirname9(resolve11(filePath));
19175
19781
  const locked = [];
19176
19782
  for (const [id, value] of Object.entries(skills)) {
19177
19783
  const entry = { id, lockRoot, lockPath: filePath };
19784
+ if (PROJECT_SKILL_DIRS.some((dir) => isFile(join18(lockRoot, dir, id, "SKILL.md")))) {
19785
+ entry.installedBesideLock = true;
19786
+ }
19178
19787
  if (isRecord(value)) {
19179
19788
  const v = value;
19180
19789
  if (typeof v.source === "string") {
@@ -19212,10 +19821,10 @@ function discoverSkillsLocks(scanBoundary, projectRoot, io) {
19212
19821
  return;
19213
19822
  }
19214
19823
  validCount += 1;
19215
- lockRoots.push(dirname8(resolved));
19824
+ lockRoots.push(dirname9(resolved));
19216
19825
  locked.push(...parsed.entries);
19217
19826
  };
19218
- consider(join17(projectRoot, "skills-lock.json"));
19827
+ consider(join18(projectRoot, "skills-lock.json"));
19219
19828
  for (const abs of walkFiles(scanBoundary, {
19220
19829
  maxDepth: NESTED_DISCOVERY_MAX_DEPTH,
19221
19830
  match: (_abs, name) => name === "skills-lock.json",
@@ -19232,7 +19841,7 @@ function discoverSkillsLocks(scanBoundary, projectRoot, io) {
19232
19841
  }
19233
19842
 
19234
19843
  // src/discover/rules.ts
19235
- import { basename as basename10, dirname as dirname9, join as join18 } from "node:path";
19844
+ import { basename as basename10, dirname as dirname10, join as join19 } from "node:path";
19236
19845
  function windsurfWorkspaceTrigger(text) {
19237
19846
  const split = splitFrontmatter(text);
19238
19847
  if (split.kind === "none") {
@@ -19304,7 +19913,7 @@ function listOneLevelMd(dir, io) {
19304
19913
  if (!entry.isFile() || entry.name.startsWith(".") || !entry.name.endsWith(".md")) {
19305
19914
  continue;
19306
19915
  }
19307
- out.push(join18(dir, entry.name));
19916
+ out.push(join19(dir, entry.name));
19308
19917
  }
19309
19918
  return out;
19310
19919
  }
@@ -19376,7 +19985,7 @@ function pushAntigravity(out, fact) {
19376
19985
  }
19377
19986
  function discoverRules(root, io) {
19378
19987
  const out = [];
19379
- const cursorRules = join18(root, ".cursor", "rules");
19988
+ const cursorRules = join19(root, ".cursor", "rules");
19380
19989
  for (const abs of walkFiles(cursorRules, {
19381
19990
  maxDepth: NESTED_DISCOVERY_MAX_DEPTH,
19382
19991
  match: (_abs, name) => name.endsWith(".mdc"),
@@ -19387,27 +19996,27 @@ function discoverRules(root, io) {
19387
19996
  out.push(fact);
19388
19997
  }
19389
19998
  }
19390
- out.push(...collectMdRules(join18(root, ".grok", "rules"), "grok", io));
19391
- out.push(...collectMdRules(join18(root, ".claude", "rules"), "claude", io));
19392
- out.push(...collectMdRules(join18(root, ".devin", "rules"), "windsurf", io, {
19999
+ out.push(...collectMdRules(join19(root, ".grok", "rules"), "grok", io));
20000
+ out.push(...collectMdRules(join19(root, ".claude", "rules"), "claude", io));
20001
+ out.push(...collectMdRules(join19(root, ".devin", "rules"), "windsurf", io, {
19393
20002
  windsurfScope: "workspace"
19394
20003
  }));
19395
- out.push(...collectMdRules(join18(root, ".windsurf", "rules"), "windsurf", io, {
20004
+ out.push(...collectMdRules(join19(root, ".windsurf", "rules"), "windsurf", io, {
19396
20005
  windsurfScope: "workspace"
19397
20006
  }));
19398
- const legacy = join18(root, ".windsurfrules");
20007
+ const legacy = join19(root, ".windsurfrules");
19399
20008
  const legacyFact = readRuleFile(legacy, "windsurf", io, { windsurfScope: "legacy" });
19400
20009
  if (legacyFact !== undefined) {
19401
20010
  out.push(legacyFact);
19402
20011
  }
19403
- pushAntigravity(out, readAntigravityRule(join18(root, "GEMINI.md"), io, "standalone"));
19404
- pushAntigravity(out, readAntigravityRule(join18(root, ".agents", "GEMINI.md"), io, "standalone"));
19405
- for (const dir of [join18(root, ".agents", "rules"), join18(root, ".agent", "rules")]) {
20012
+ pushAntigravity(out, readAntigravityRule(join19(root, "GEMINI.md"), io, "standalone"));
20013
+ pushAntigravity(out, readAntigravityRule(join19(root, ".agents", "GEMINI.md"), io, "standalone"));
20014
+ for (const dir of [join19(root, ".agents", "rules"), join19(root, ".agent", "rules")]) {
19406
20015
  for (const abs of listOneLevelMd(dir, io)) {
19407
20016
  pushAntigravity(out, readAntigravityRule(abs, io, "modular"));
19408
20017
  }
19409
20018
  }
19410
- readAntigravityRulesJson(join18(root, ".agents", "rules.json"), io);
20019
+ readAntigravityRulesJson(join19(root, ".agents", "rules.json"), io);
19411
20020
  return out;
19412
20021
  }
19413
20022
  function discoverNestedWindsurfRules(scanBoundary, io) {
@@ -19425,13 +20034,13 @@ function discoverNestedWindsurfRules(scanBoundary, io) {
19425
20034
  return out;
19426
20035
  }
19427
20036
  function discoverClaudeUserRules(io) {
19428
- return collectMdRules(join18(providerHome("claude"), "rules"), "claude", io);
20037
+ return collectMdRules(join19(providerHome("claude"), "rules"), "claude", io);
19429
20038
  }
19430
20039
  function discoverWindsurfGlobalRules(filePath, io) {
19431
20040
  if (basename10(filePath) !== "global_rules.md") {
19432
20041
  return [];
19433
20042
  }
19434
- if (basename10(dirname9(filePath)) !== "memories") {
20043
+ if (basename10(dirname10(filePath)) !== "memories") {
19435
20044
  return [];
19436
20045
  }
19437
20046
  const fact = readRuleFile(filePath, "windsurf", io, { windsurfScope: "global" });
@@ -19442,23 +20051,23 @@ function discoverAntigravityUserRules(io) {
19442
20051
  const geminiHome = providerHome("gemini");
19443
20052
  const configHome = providerHome("antigravity");
19444
20053
  for (const path of [
19445
- join18(geminiHome, "GEMINI.md"),
19446
- join18(geminiHome, "AGENTS.md"),
19447
- join18(configHome, "GEMINI.md"),
19448
- join18(configHome, "AGENTS.md")
20054
+ join19(geminiHome, "GEMINI.md"),
20055
+ join19(geminiHome, "AGENTS.md"),
20056
+ join19(configHome, "GEMINI.md"),
20057
+ join19(configHome, "AGENTS.md")
19449
20058
  ]) {
19450
20059
  pushAntigravity(out, readAntigravityRule(path, io, "standalone"));
19451
20060
  }
19452
- for (const abs of listOneLevelMd(join18(configHome, "rules"), io)) {
20061
+ for (const abs of listOneLevelMd(join19(configHome, "rules"), io)) {
19453
20062
  pushAntigravity(out, readAntigravityRule(abs, io, "modular"));
19454
20063
  }
19455
20064
  return out;
19456
20065
  }
19457
20066
 
19458
20067
  // src/discover/windsurf.ts
19459
- import { dirname as dirname10, join as join19 } from "node:path";
20068
+ import { dirname as dirname11, join as join20 } from "node:path";
19460
20069
  function windsurfUserHooksPath() {
19461
- return join19(providerHome("windsurf"), "hooks.json");
20070
+ return join20(providerHome("windsurf"), "hooks.json");
19462
20071
  }
19463
20072
  function discoverWindsurfHooksFile(filePath, projectRoot, io) {
19464
20073
  return readFlatHooks(filePath, "windsurf", io, (item, base, hostPlatform) => {
@@ -19481,19 +20090,111 @@ function discoverWindsurfHooksFile(filePath, projectRoot, io) {
19481
20090
  });
19482
20091
  }
19483
20092
  function windsurfUserMcpPath() {
19484
- return join19(providerHome("windsurf"), "mcp_config.json");
20093
+ return join20(providerHome("windsurf"), "mcp_config.json");
19485
20094
  }
19486
20095
  function windsurfGlobalRulesPath() {
19487
- return join19(providerHome("windsurf"), "memories", "global_rules.md");
20096
+ return join20(providerHome("windsurf"), "memories", "global_rules.md");
19488
20097
  }
19489
20098
  function discoverWindsurfUserMcp(io) {
19490
20099
  const filePath = windsurfUserMcpPath();
19491
- return parseWindsurfJsonFile(filePath, dirname10(filePath), io, { optional: true });
20100
+ return parseWindsurfJsonFile(filePath, dirname11(filePath), io, { optional: true });
19492
20101
  }
19493
20102
  function discoverWindsurfUserRules(io) {
19494
20103
  return discoverWindsurfGlobalRules(windsurfGlobalRulesPath(), io);
19495
20104
  }
19496
20105
 
20106
+ // src/discover/zed.ts
20107
+ import { homedir as homedir4 } from "node:os";
20108
+ import { join as join21 } from "node:path";
20109
+ function zedSettingsMcp(path, root, io) {
20110
+ return parseProfiledMcpFile(path, root, io, "zed-json", { optional: true, serversKey: "context_servers" });
20111
+ }
20112
+ function discoverZedProjectMcp(dir, io) {
20113
+ return zedSettingsMcp(join21(dir, ".zed", "settings.json"), dir, io);
20114
+ }
20115
+ function discoverZedUserMcp(root, io) {
20116
+ const appData = process.env.APPDATA;
20117
+ const path = process.platform === "win32" && appData !== undefined ? join21(appData, "Zed", "settings.json") : join21(homedir4(), ".config", "zed", "settings.json");
20118
+ return zedSettingsMcp(path, root, io);
20119
+ }
20120
+
20121
+ // src/discover/amp.ts
20122
+ import { existsSync as existsSync8 } from "node:fs";
20123
+ import { join as join22 } from "node:path";
20124
+ function settingsIn(dir) {
20125
+ return ["settings.json", "settings.jsonc"].map((name) => join22(dir, name)).find((path) => existsSync8(path));
20126
+ }
20127
+ function ampSettingsMcp(path, root, io) {
20128
+ return path === undefined ? [] : parseProfiledMcpFile(path, root, io, "amp-json", { optional: true, serversKey: "amp.mcpServers" });
20129
+ }
20130
+ function discoverAmpProjectMcp(dirs, io) {
20131
+ for (const dir of dirs) {
20132
+ const path = settingsIn(join22(dir, ".amp"));
20133
+ if (path !== undefined) {
20134
+ return ampSettingsMcp(path, dir, io);
20135
+ }
20136
+ }
20137
+ return [];
20138
+ }
20139
+ function discoverAmpUserMcp(root, io) {
20140
+ return ampSettingsMcp(settingsIn(providerHome("amp")), root, io);
20141
+ }
20142
+
20143
+ // src/discover/claude-desktop.ts
20144
+ import { homedir as homedir5 } from "node:os";
20145
+ import { join as join23 } from "node:path";
20146
+ function claudeDesktopConfigDir() {
20147
+ if (process.platform === "darwin") {
20148
+ return join23(homedir5(), "Library", "Application Support", "Claude");
20149
+ }
20150
+ const appData = process.env.APPDATA;
20151
+ if (process.platform === "win32" && appData !== undefined) {
20152
+ return join23(appData, "Claude");
20153
+ }
20154
+ return join23(homedir5(), ".config", "Claude");
20155
+ }
20156
+ function discoverClaudeDesktopMcp(root, io) {
20157
+ return parseProfiledMcpFile(join23(claudeDesktopConfigDir(), "claude_desktop_config.json"), root, io, "claude-desktop-json", {
20158
+ optional: true,
20159
+ mcpServersOptional: true
20160
+ });
20161
+ }
20162
+ var REQUIRED = ["manifest_version", "name", "version", "description", "author", "server"];
20163
+ var SERVER_TYPES = new Set(["node", "python", "binary", "uv"]);
20164
+ function present(value) {
20165
+ return value !== undefined && value !== null && value !== "";
20166
+ }
20167
+ function manifestDefects(raw) {
20168
+ const missingFields = REQUIRED.filter((key) => !present(raw[key]) && !(key === "manifest_version" && present(raw.dxt_version)));
20169
+ if (present(raw.author) && !(isRecord(raw.author) && present(raw.author.name))) {
20170
+ missingFields.push("author.name");
20171
+ }
20172
+ const type = isRecord(raw.server) ? raw.server.type : undefined;
20173
+ return {
20174
+ missingFields,
20175
+ ...typeof type === "string" && !SERVER_TYPES.has(type) ? { unknownServerType: type } : {}
20176
+ };
20177
+ }
20178
+ function discoverClaudeDesktopExtensions(io) {
20179
+ const dir = join23(claudeDesktopConfigDir(), "Claude Extensions");
20180
+ const out = [];
20181
+ for (const entry of listDir(dir, io, { optional: true }) ?? []) {
20182
+ if (!entry.isDirectory()) {
20183
+ continue;
20184
+ }
20185
+ const path = join23(dir, entry.name, "manifest.json");
20186
+ const raw = readConfigFile(path, io, { optional: true });
20187
+ if (!isRecord(raw)) {
20188
+ continue;
20189
+ }
20190
+ const defects = manifestDefects(raw);
20191
+ if (defects.missingFields.length > 0 || defects.unknownServerType !== undefined) {
20192
+ out.push({ path, ...defects });
20193
+ }
20194
+ }
20195
+ return out;
20196
+ }
20197
+
19497
20198
  // src/discover/index.ts
19498
20199
  function hookFileKey(fact) {
19499
20200
  return `${resolve12(fact.path)}\x00${fact.source ?? ""}\x00${fact.sourceProvider ?? ""}`;
@@ -19534,7 +20235,10 @@ var SOURCES = [
19534
20235
  },
19535
20236
  {
19536
20237
  id: "copilot",
19537
- project: (dir, { io }) => ({ hooks: discoverCopilotSettingsHooks(dir, io) }),
20238
+ project: (dir, { io }) => ({
20239
+ hooks: discoverCopilotSettingsHooks(dir, io),
20240
+ mcp: parseProfiledMcpFile(join24(dir, ".github", "mcp.json"), dir, io, "copilot-json", { optional: true })
20241
+ }),
19538
20242
  user: ({ root, io }) => ({
19539
20243
  hooks: [...discoverCopilotUserHooks(io), ...discoverCopilotUserSettingsHooks(io)],
19540
20244
  mcp: discoverCopilotUserMcp(root, io)
@@ -19543,10 +20247,10 @@ var SOURCES = [
19543
20247
  {
19544
20248
  id: "grok",
19545
20249
  project: (dir, { io }) => ({
19546
- hooks: discoverGrokHooks(join20(dir, ".grok", "hooks"), dir, io)
20250
+ hooks: discoverGrokHooks(join24(dir, ".grok", "hooks"), dir, io)
19547
20251
  }),
19548
20252
  user: ({ io }) => ({
19549
- hooks: discoverGrokHooks(join20(providerHome("grok"), "hooks"), providerHome("grok"), io),
20253
+ hooks: discoverGrokHooks(join24(providerHome("grok"), "hooks"), providerHome("grok"), io),
19550
20254
  mcp: discoverGrokUserMcp(io)
19551
20255
  })
19552
20256
  },
@@ -19561,10 +20265,10 @@ var SOURCES = [
19561
20265
  {
19562
20266
  id: "antigravity",
19563
20267
  project: (dir, { io }) => ({
19564
- hooks: discoverAntigravityHooksFile(join20(dir, ".agents", "hooks.json"), dir, io)
20268
+ hooks: discoverAntigravityHooksFile(join24(dir, ".agents", "hooks.json"), dir, io)
19565
20269
  }),
19566
20270
  user: ({ root, io }) => ({
19567
- hooks: discoverAntigravityHooksFile(join20(providerHome("antigravity"), "hooks.json"), root, io),
20271
+ hooks: discoverAntigravityHooksFile(join24(providerHome("antigravity"), "hooks.json"), root, io),
19568
20272
  rules: discoverAntigravityUserRules(io)
19569
20273
  })
19570
20274
  },
@@ -19590,7 +20294,7 @@ var SOURCES = [
19590
20294
  {
19591
20295
  id: "windsurf",
19592
20296
  project: (dir, { io }) => ({
19593
- hooks: discoverWindsurfHooksFile(join20(dir, ".windsurf", "hooks.json"), dir, io)
20297
+ hooks: discoverWindsurfHooksFile(join24(dir, ".windsurf", "hooks.json"), dir, io)
19594
20298
  }),
19595
20299
  nested: ({ scanBoundary, io }) => ({
19596
20300
  rules: discoverNestedWindsurfRules(scanBoundary, io)
@@ -19602,27 +20306,27 @@ var SOURCES = [
19602
20306
  return {
19603
20307
  mcp,
19604
20308
  rules,
19605
- hooks: discoverWindsurfHooksFile(hooksPath, dirname11(hooksPath), io)
20309
+ hooks: discoverWindsurfHooksFile(hooksPath, dirname12(hooksPath), io)
19606
20310
  };
19607
20311
  }
19608
20312
  },
19609
20313
  {
19610
20314
  id: "cursor",
19611
20315
  project: (dir, { io }) => ({
19612
- hooks: discoverCursorHooksFile(join20(dir, ".cursor", "hooks.json"), dir, io)
20316
+ hooks: discoverCursorHooksFile(join24(dir, ".cursor", "hooks.json"), dir, io)
19613
20317
  }),
19614
20318
  user: ({ root, io }) => ({
19615
- hooks: discoverCursorHooksFile(join20(providerHome("cursor"), "hooks.json"), root, io),
20319
+ hooks: discoverCursorHooksFile(join24(providerHome("cursor"), "hooks.json"), root, io),
19616
20320
  mcp: discoverCursorUserMcp(root, io)
19617
20321
  })
19618
20322
  },
19619
20323
  {
19620
20324
  id: "kiro",
19621
20325
  project: (dir, { io }) => ({
19622
- hooks: discoverKiroHooksDir(join20(dir, ".kiro", "hooks"), dir, io)
20326
+ hooks: discoverKiroHooksDir(join24(dir, ".kiro", "hooks"), dir, io)
19623
20327
  }),
19624
20328
  user: ({ root, io }) => ({
19625
- hooks: discoverKiroHooksDir(join20(providerHome("kiro"), "hooks"), root, io)
20329
+ hooks: discoverKiroHooksDir(join24(providerHome("kiro"), "hooks"), root, io)
19626
20330
  })
19627
20331
  },
19628
20332
  {
@@ -19631,6 +20335,48 @@ var SOURCES = [
19631
20335
  mcp: discoverNestedContinueMcp(scanBoundary, io)
19632
20336
  })
19633
20337
  },
20338
+ {
20339
+ id: "zed",
20340
+ project: (dir, { io }) => ({ mcp: discoverZedProjectMcp(dir, io) }),
20341
+ user: ({ root, io }) => ({ mcp: discoverZedUserMcp(root, io) })
20342
+ },
20343
+ {
20344
+ id: "amp",
20345
+ nested: ({ startDir, scanBoundary, io }) => ({
20346
+ mcp: discoverAmpProjectMcp(ancestorDirsInclusive(startDir, scanBoundary), io)
20347
+ }),
20348
+ user: ({ root, io }) => ({ mcp: discoverAmpUserMcp(root, io) })
20349
+ },
20350
+ {
20351
+ id: "claude-desktop",
20352
+ user: ({ root, io }) => ({ mcp: discoverClaudeDesktopMcp(root, io) })
20353
+ },
20354
+ {
20355
+ id: "qwen",
20356
+ project: (dir, { io }) => {
20357
+ const file = join24(dir, ".qwen", "settings.json");
20358
+ return {
20359
+ hooks: discoverQwenHooksFile(file, dir, io),
20360
+ mcp: parseProfiledMcpFile(file, dir, io, "qwen-json", { optional: true, mcpServersOptional: true })
20361
+ };
20362
+ },
20363
+ user: ({ root, io }) => {
20364
+ const file = join24(providerHome("qwen"), "settings.json");
20365
+ return {
20366
+ hooks: discoverQwenHooksFile(file, root, io),
20367
+ mcp: parseProfiledMcpFile(file, root, io, "qwen-json", { optional: true, mcpServersOptional: true })
20368
+ };
20369
+ }
20370
+ },
20371
+ {
20372
+ id: "factory",
20373
+ project: (dir, { io }) => discoverFactoryProject(dir, io),
20374
+ user: ({ root, io }) => discoverFactoryUser(root, io)
20375
+ },
20376
+ {
20377
+ id: "goose",
20378
+ user: ({ root, io }) => ({ mcp: discoverGooseUserMcp(root, io) })
20379
+ },
19634
20380
  {
19635
20381
  id: "mcp-paths",
19636
20382
  project: (dir, { config, io }) => ({
@@ -19677,12 +20423,12 @@ function discoverAgentSurface(ctx, config, opts = {}) {
19677
20423
  continue;
19678
20424
  }
19679
20425
  configuredRoots.add(abs);
19680
- const source = isInside(abs, homedir4()) ? "global" : "project";
20426
+ const source = isInside(abs, homedir6()) ? "global" : "project";
19681
20427
  skills.push(...discoverSkillsInDir(abs, source, io, root, undefined, "agent-skills"));
19682
20428
  }
19683
20429
  if (includeGlobal) {
19684
20430
  for (const provider of USER_SKILL_PROVIDERS) {
19685
- const abs = join20(providerHome(provider), "skills");
20431
+ const abs = join24(providerHome(provider), "skills");
19686
20432
  const resolved = resolve12(abs);
19687
20433
  if (configuredRoots.has(resolved)) {
19688
20434
  continue;
@@ -19690,8 +20436,13 @@ function discoverAgentSurface(ctx, config, opts = {}) {
19690
20436
  configuredRoots.add(resolved);
19691
20437
  skills.push(...discoverSkillsInDir(abs, "global", io, root, provider));
19692
20438
  }
20439
+ const configAgentsSkills = resolve12(homedir6(), ".config", "agents", "skills");
20440
+ if (!configuredRoots.has(configAgentsSkills)) {
20441
+ configuredRoots.add(configAgentsSkills);
20442
+ skills.push(...discoverSkillsInDir(configAgentsSkills, "global", io, root, "agent-skills"));
20443
+ }
19693
20444
  }
19694
- const agentsSkillsRoots = commandcodeAgentsSkillsRoots(startDir, scanBoundary, homedir4());
20445
+ const agentsSkillsRoots = commandcodeAgentsSkillsRoots(startDir, scanBoundary, homedir6());
19695
20446
  const lock = discoverSkillsLocks(scanBoundary, root, io);
19696
20447
  const agents = discoverAgents(scanBoundary, io, startDir, includeGlobal, commandcodeProjectRoot);
19697
20448
  const hooks = [];
@@ -19770,13 +20521,14 @@ function discoverAgentSurface(ctx, config, opts = {}) {
19770
20521
  skillLockRoots: lock.lockRoots,
19771
20522
  configErrors: io.errors,
19772
20523
  codexProjectDocMaxBytes: codex.projectDocMaxBytes ?? CODEX_PROJECT_DOC_MAX_BYTES,
19773
- codexInstructionChain: codexInstructionChain(policyFiles, startDir, codexProjectRoot, fallbacks)
20524
+ codexInstructionChain: codexInstructionChain(policyFiles, startDir, codexProjectRoot, fallbacks),
20525
+ ...includeGlobal ? { claudeDesktopExtensions: discoverClaudeDesktopExtensions(io) } : {}
19774
20526
  };
19775
20527
  }
19776
20528
 
19777
20529
  // src/discover/extract.ts
19778
20530
  function readPackageJson(root, io) {
19779
- const path = join21(root, "package.json");
20531
+ const path = join25(root, "package.json");
19780
20532
  const raw = readConfigFile(path, io, { optional: true });
19781
20533
  if (raw === undefined) {
19782
20534
  return {};
@@ -19854,16 +20606,16 @@ function inferPackageManager(root, pkg) {
19854
20606
  return prefix;
19855
20607
  }
19856
20608
  }
19857
- if (existsSync7(join21(root, "bun.lock")) || existsSync7(join21(root, "bun.lockb"))) {
20609
+ if (existsSync9(join25(root, "bun.lock")) || existsSync9(join25(root, "bun.lockb"))) {
19858
20610
  return "bun";
19859
20611
  }
19860
- if (existsSync7(join21(root, "pnpm-lock.yaml"))) {
20612
+ if (existsSync9(join25(root, "pnpm-lock.yaml"))) {
19861
20613
  return "pnpm";
19862
20614
  }
19863
- if (existsSync7(join21(root, "yarn.lock"))) {
20615
+ if (existsSync9(join25(root, "yarn.lock"))) {
19864
20616
  return "yarn";
19865
20617
  }
19866
- if (existsSync7(join21(root, "package-lock.json"))) {
20618
+ if (existsSync9(join25(root, "package-lock.json"))) {
19867
20619
  return "npm";
19868
20620
  }
19869
20621
  return "unknown";
@@ -19908,7 +20660,7 @@ function sortFindings(findings) {
19908
20660
  // src/analyze.ts
19909
20661
  function loadProject(options = {}) {
19910
20662
  const requested = resolve13(options.dir ?? process.cwd());
19911
- const location = resolveScanContext(requested);
20663
+ const location = resolveScanContext(requested, { allowNoProject: options.global === true });
19912
20664
  const root = location.root;
19913
20665
  const loaded = loadConfig(root, options.configPath);
19914
20666
  const config = {
@@ -20298,7 +21050,7 @@ function formatSummary(summary, ink) {
20298
21050
  }
20299
21051
 
20300
21052
  // src/version.ts
20301
- var VERSION = "2.0.0";
21053
+ var VERSION = "2.3.1";
20302
21054
 
20303
21055
  // src/commands/check.ts
20304
21056
  function runCheck(options) {
@@ -20345,13 +21097,13 @@ import {
20345
21097
  writeFileSync
20346
21098
  } from "node:fs";
20347
21099
  import { tmpdir } from "node:os";
20348
- import { join as join22 } from "node:path";
21100
+ import { join as join26 } from "node:path";
20349
21101
  function runDemo() {
20350
- const root = mkdtempSync(join22(tmpdir(), "agentscan-demo-"));
21102
+ const root = mkdtempSync(join26(tmpdir(), "agentscan-demo-"));
20351
21103
  try {
20352
- const claude = join22(root, ".claude");
21104
+ const claude = join26(root, ".claude");
20353
21105
  mkdirSync(claude, { recursive: true });
20354
- writeFileSync(join22(claude, "settings.json"), `${JSON.stringify({
21106
+ writeFileSync(join26(claude, "settings.json"), `${JSON.stringify({
20355
21107
  hooks: {
20356
21108
  PreToolUse: [
20357
21109
  {
@@ -20422,12 +21174,12 @@ function formatExplain(f) {
20422
21174
  }
20423
21175
 
20424
21176
  // src/commands/init.ts
20425
- import { existsSync as existsSync8, writeFileSync as writeFileSync2 } from "node:fs";
20426
- import { join as join23, resolve as resolve14 } from "node:path";
21177
+ import { existsSync as existsSync10, writeFileSync as writeFileSync2 } from "node:fs";
21178
+ import { join as join27, resolve as resolve14 } from "node:path";
20427
21179
  function runInit(dir, options = {}) {
20428
21180
  const root = resolve14(dir ?? process.cwd());
20429
- const path = join23(root, ".agentscanrc.json");
20430
- if (existsSync8(path) && !options.force) {
21181
+ const path = join27(root, ".agentscanrc.json");
21182
+ if (existsSync10(path) && !options.force) {
20431
21183
  return {
20432
21184
  exitCode: 2,
20433
21185
  stdout: "",
@@ -20481,7 +21233,7 @@ function runRulesCommand(options = {}) {
20481
21233
  // src/report/clipboard.ts
20482
21234
  import { spawn } from "node:child_process";
20483
21235
  import { accessSync, constants } from "node:fs";
20484
- import { delimiter, join as join24 } from "node:path";
21236
+ import { delimiter, join as join28 } from "node:path";
20485
21237
  var TOOLS = [
20486
21238
  ["wl-copy"],
20487
21239
  ["xclip", "-selection", "clipboard"],
@@ -20498,7 +21250,7 @@ function resolve15(name) {
20498
21250
  if (dir.length === 0) {
20499
21251
  continue;
20500
21252
  }
20501
- const candidate = join24(dir, name);
21253
+ const candidate = join28(dir, name);
20502
21254
  try {
20503
21255
  accessSync(candidate, constants.X_OK);
20504
21256
  return candidate;