@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.
- package/CHANGELOG.md +84 -0
- package/README.md +54 -22
- package/dist/cli.js +1017 -265
- 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-
|
|
6956
|
-
claudeHooks: { url: "https://code.claude.com/docs/en/hooks", lastVerified: "2026-
|
|
6957
|
-
claudeSubagents: { url: "https://code.claude.com/docs/en/sub-agents", lastVerified: "2026-
|
|
6958
|
-
claudeSkills: { url: "https://code.claude.com/docs/en/skills", lastVerified: "2026-
|
|
6959
|
-
claudeMcp: { url: "https://code.claude.com/docs/en/mcp", lastVerified: "2026-
|
|
6960
|
-
claudeMemory: { url: "https://code.claude.com/docs/en/memory", lastVerified: "2026-
|
|
6961
|
-
opencodeMcp: { url: "https://opencode.ai/v2/docs/mcp-servers", lastVerified: "2026-
|
|
6962
|
-
copilotHooks: { url: "https://docs.github.com/en/copilot/reference/hooks-reference", lastVerified: "2026-
|
|
6963
|
-
grokHooks: { url: "https://docs.x.ai/build/features/hooks", lastVerified: "2026-
|
|
6964
|
-
grokMcp: { url: "https://docs.x.ai/build/features/mcp-servers", lastVerified: "2026-
|
|
6965
|
-
grokSkills: { url: "https://docs.x.ai/build/features/skills-plugins-marketplaces", lastVerified: "2026-
|
|
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-
|
|
6968
|
-
commandcodeAgents: { url: "https://commandcode.ai/docs/agents", lastVerified: "2026-
|
|
6969
|
-
kiroHooks: { url: "https://kiro.dev/docs/hooks/", lastVerified: "2026-
|
|
6970
|
-
kiroHooksV1: { url: "https://kiro.dev/docs/ide/whats-new-v1/hooks/", lastVerified: "2026-
|
|
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-
|
|
6973
|
-
vscodeHooks: { url: "https://code.visualstudio.com/docs/agents/reference/hooks-reference", lastVerified: "2026-11-
|
|
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
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
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-
|
|
6983
|
-
codexAgentsMd: { url: "https://learn.chatgpt.com/docs/agent-configuration/agents-md", lastVerified: "2026-
|
|
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
|
-
|
|
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-
|
|
6989
|
-
devinMcp: { url: "https://docs.devin.ai/cli/extensibility/mcp/configuration", lastVerified: "2026-11-
|
|
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
|
|
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
|
|
7339
|
-
severity:
|
|
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: "
|
|
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
|
|
7796
|
-
severity: "
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
8392
|
-
|
|
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
|
|
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
|
-
|
|
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"
|
|
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 ? [] : [
|
|
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
|
-
|
|
8499
|
-
|
|
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
|
-
"
|
|
8551
|
-
"
|
|
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
|
-
|
|
9045
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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(
|
|
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
|
-
...
|
|
9922
|
-
...
|
|
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
|
|
13793
|
-
import { join as
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
|
15731
|
-
import { dirname as
|
|
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
|
|
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 &&
|
|
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 && !
|
|
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
|
-
|
|
16296
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
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
|
|
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(
|
|
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 :
|
|
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(
|
|
18387
|
-
...hooksFromCodexToml(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
19061
|
+
...parseProfiledMcpFile(join14(dir, "config.json"), projectRoot, io, "devin-json", {
|
|
18464
19062
|
optional: true,
|
|
18465
19063
|
mcpServersOptional: true
|
|
18466
19064
|
}),
|
|
18467
|
-
...parseProfiledMcpFile(
|
|
19065
|
+
...parseProfiledMcpFile(join14(dir, "config.local.json"), projectRoot, io, "devin-json", {
|
|
18468
19066
|
optional: true,
|
|
18469
19067
|
mcpServersOptional: true
|
|
18470
19068
|
}),
|
|
18471
|
-
...parseProfiledMcpFile(
|
|
19069
|
+
...parseProfiledMcpFile(join14(dir, "mcp_config.json"), projectRoot, io, "devin-json", {
|
|
18472
19070
|
optional: true
|
|
18473
19071
|
}),
|
|
18474
|
-
...parseProfiledMcpFile(
|
|
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 =
|
|
19078
|
+
const root = join14(dir, ".devin");
|
|
18481
19079
|
return {
|
|
18482
19080
|
hooks: [
|
|
18483
|
-
...discoverDevinV1Hooks(
|
|
18484
|
-
...discoverDevinSettingsHooks(
|
|
18485
|
-
...discoverDevinSettingsHooks(
|
|
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(
|
|
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
|
|
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(
|
|
19151
|
+
return parseGrokTomlFile(join15(home, "config.toml"), home, io, { optional: true });
|
|
18554
19152
|
}
|
|
18555
19153
|
function applyGrokMcpPrecedence(mcp, startDir) {
|
|
18556
|
-
const userFile = resolve8(
|
|
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
|
|
18569
|
-
import { join as
|
|
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
|
|
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
|
|
18593
|
-
|
|
18594
|
-
|
|
18595
|
-
|
|
18596
|
-
|
|
18597
|
-
|
|
18598
|
-
const
|
|
18599
|
-
|
|
18600
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
18714
|
-
if (
|
|
18715
|
-
fact.
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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 =
|
|
18854
|
-
if (
|
|
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(
|
|
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(
|
|
19486
|
+
const skillsDir = resolve10(join17(pluginRoot, "skills"));
|
|
18881
19487
|
if (!configuredSkillRoots.has(skillsDir)) {
|
|
18882
19488
|
configuredSkillRoots.add(skillsDir);
|
|
18883
|
-
skills.push(...discoverSkillsInDir(
|
|
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(
|
|
18890
|
-
commands.push(...discoverClaudeCommandsInDir(
|
|
18891
|
-
mcp.push(...parseClaudePluginMcpFile(
|
|
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
|
|
18898
|
-
import { basename as basename9, dirname as
|
|
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 ??
|
|
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) =>
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 (!
|
|
19038
|
-
addUnique(out, seen, readPolicyFile(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
19086
|
-
return !
|
|
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:
|
|
19703
|
+
...commandcodeMemory ? { hopsDir: dirname9(dirname9(abs)) } : {}
|
|
19098
19704
|
}));
|
|
19099
19705
|
}
|
|
19100
|
-
addUnique(out, seen, readPolicyFile(
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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(
|
|
19824
|
+
lockRoots.push(dirname9(resolved));
|
|
19216
19825
|
locked.push(...parsed.entries);
|
|
19217
19826
|
};
|
|
19218
|
-
consider(
|
|
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
|
|
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(
|
|
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 =
|
|
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(
|
|
19391
|
-
out.push(...collectMdRules(
|
|
19392
|
-
out.push(...collectMdRules(
|
|
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(
|
|
20004
|
+
out.push(...collectMdRules(join19(root, ".windsurf", "rules"), "windsurf", io, {
|
|
19396
20005
|
windsurfScope: "workspace"
|
|
19397
20006
|
}));
|
|
19398
|
-
const legacy =
|
|
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(
|
|
19404
|
-
pushAntigravity(out, readAntigravityRule(
|
|
19405
|
-
for (const dir of [
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
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(
|
|
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
|
|
20068
|
+
import { dirname as dirname11, join as join20 } from "node:path";
|
|
19460
20069
|
function windsurfUserHooksPath() {
|
|
19461
|
-
return
|
|
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
|
|
20093
|
+
return join20(providerHome("windsurf"), "mcp_config.json");
|
|
19485
20094
|
}
|
|
19486
20095
|
function windsurfGlobalRulesPath() {
|
|
19487
|
-
return
|
|
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,
|
|
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 }) => ({
|
|
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(
|
|
20250
|
+
hooks: discoverGrokHooks(join24(dir, ".grok", "hooks"), dir, io)
|
|
19547
20251
|
}),
|
|
19548
20252
|
user: ({ io }) => ({
|
|
19549
|
-
hooks: discoverGrokHooks(
|
|
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(
|
|
20268
|
+
hooks: discoverAntigravityHooksFile(join24(dir, ".agents", "hooks.json"), dir, io)
|
|
19565
20269
|
}),
|
|
19566
20270
|
user: ({ root, io }) => ({
|
|
19567
|
-
hooks: discoverAntigravityHooksFile(
|
|
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(
|
|
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,
|
|
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(
|
|
20316
|
+
hooks: discoverCursorHooksFile(join24(dir, ".cursor", "hooks.json"), dir, io)
|
|
19613
20317
|
}),
|
|
19614
20318
|
user: ({ root, io }) => ({
|
|
19615
|
-
hooks: discoverCursorHooksFile(
|
|
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(
|
|
20326
|
+
hooks: discoverKiroHooksDir(join24(dir, ".kiro", "hooks"), dir, io)
|
|
19623
20327
|
}),
|
|
19624
20328
|
user: ({ root, io }) => ({
|
|
19625
|
-
hooks: discoverKiroHooksDir(
|
|
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,
|
|
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 =
|
|
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,
|
|
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 =
|
|
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 (
|
|
20609
|
+
if (existsSync9(join25(root, "bun.lock")) || existsSync9(join25(root, "bun.lockb"))) {
|
|
19858
20610
|
return "bun";
|
|
19859
20611
|
}
|
|
19860
|
-
if (
|
|
20612
|
+
if (existsSync9(join25(root, "pnpm-lock.yaml"))) {
|
|
19861
20613
|
return "pnpm";
|
|
19862
20614
|
}
|
|
19863
|
-
if (
|
|
20615
|
+
if (existsSync9(join25(root, "yarn.lock"))) {
|
|
19864
20616
|
return "yarn";
|
|
19865
20617
|
}
|
|
19866
|
-
if (
|
|
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.
|
|
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
|
|
21100
|
+
import { join as join26 } from "node:path";
|
|
20349
21101
|
function runDemo() {
|
|
20350
|
-
const root = mkdtempSync(
|
|
21102
|
+
const root = mkdtempSync(join26(tmpdir(), "agentscan-demo-"));
|
|
20351
21103
|
try {
|
|
20352
|
-
const claude =
|
|
21104
|
+
const claude = join26(root, ".claude");
|
|
20353
21105
|
mkdirSync(claude, { recursive: true });
|
|
20354
|
-
writeFileSync(
|
|
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
|
|
20426
|
-
import { join as
|
|
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 =
|
|
20430
|
-
if (
|
|
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
|
|
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 =
|
|
21253
|
+
const candidate = join28(dir, name);
|
|
20502
21254
|
try {
|
|
20503
21255
|
accessSync(candidate, constants.X_OK);
|
|
20504
21256
|
return candidate;
|