@agentxm/workspace-lint 0.28.4-bootstrap.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/LICENSE +110 -0
  2. package/dist/src/catalog/index.d.ts +31 -0
  3. package/dist/src/catalog/index.js +60 -0
  4. package/dist/src/catalog/workspace/agents-detected-declared.d.ts +23 -0
  5. package/dist/src/catalog/workspace/agents-detected-declared.js +61 -0
  6. package/dist/src/catalog/workspace/agents-recognized.d.ts +23 -0
  7. package/dist/src/catalog/workspace/agents-recognized.js +64 -0
  8. package/dist/src/catalog/workspace/axm-skill-compatible.d.ts +4 -0
  9. package/dist/src/catalog/workspace/axm-skill-compatible.js +51 -0
  10. package/dist/src/catalog/workspace/configured-but-not-installed.d.ts +4 -0
  11. package/dist/src/catalog/workspace/configured-but-not-installed.js +88 -0
  12. package/dist/src/catalog/workspace/desired-state-reconcilable.d.ts +4 -0
  13. package/dist/src/catalog/workspace/desired-state-reconcilable.js +96 -0
  14. package/dist/src/catalog/workspace/display-paths.d.ts +7 -0
  15. package/dist/src/catalog/workspace/display-paths.js +4 -0
  16. package/dist/src/catalog/workspace/helpers/empty.d.ts +18 -0
  17. package/dist/src/catalog/workspace/helpers/empty.js +16 -0
  18. package/dist/src/catalog/workspace/helpers/finding.d.ts +10 -0
  19. package/dist/src/catalog/workspace/helpers/finding.js +11 -0
  20. package/dist/src/catalog/workspace/helpers/source-categorize.d.ts +16 -0
  21. package/dist/src/catalog/workspace/helpers/source-categorize.js +51 -0
  22. package/dist/src/catalog/workspace/hook-ownership-ambiguous.d.ts +4 -0
  23. package/dist/src/catalog/workspace/hook-ownership-ambiguous.js +25 -0
  24. package/dist/src/catalog/workspace/initialized.d.ts +22 -0
  25. package/dist/src/catalog/workspace/initialized.js +58 -0
  26. package/dist/src/catalog/workspace/instructions-agent-supported.d.ts +4 -0
  27. package/dist/src/catalog/workspace/instructions-agent-supported.js +38 -0
  28. package/dist/src/catalog/workspace/instructions-gitignore-current.d.ts +4 -0
  29. package/dist/src/catalog/workspace/instructions-gitignore-current.js +49 -0
  30. package/dist/src/catalog/workspace/instructions-source-present.d.ts +4 -0
  31. package/dist/src/catalog/workspace/instructions-source-present.js +41 -0
  32. package/dist/src/catalog/workspace/instructions-target-current.d.ts +4 -0
  33. package/dist/src/catalog/workspace/instructions-target-current.js +51 -0
  34. package/dist/src/catalog/workspace/instructions-target-stale.d.ts +9 -0
  35. package/dist/src/catalog/workspace/instructions-target-stale.js +41 -0
  36. package/dist/src/catalog/workspace/instructions-target-unowned.d.ts +10 -0
  37. package/dist/src/catalog/workspace/instructions-target-unowned.js +59 -0
  38. package/dist/src/catalog/workspace/knowledge-state-valid.d.ts +5 -0
  39. package/dist/src/catalog/workspace/knowledge-state-valid.js +65 -0
  40. package/dist/src/catalog/workspace/lockfile-valid.d.ts +5 -0
  41. package/dist/src/catalog/workspace/lockfile-valid.js +63 -0
  42. package/dist/src/catalog/workspace/managed-file-unowned.d.ts +4 -0
  43. package/dist/src/catalog/workspace/managed-file-unowned.js +25 -0
  44. package/dist/src/catalog/workspace/mcps-agent-drift.d.ts +4 -0
  45. package/dist/src/catalog/workspace/mcps-agent-drift.js +139 -0
  46. package/dist/src/catalog/workspace/mcps-agent-orphaned.d.ts +4 -0
  47. package/dist/src/catalog/workspace/mcps-agent-orphaned.js +64 -0
  48. package/dist/src/catalog/workspace/mcps-no-secret-literal.d.ts +4 -0
  49. package/dist/src/catalog/workspace/mcps-no-secret-literal.js +67 -0
  50. package/dist/src/catalog/workspace/mcps-shared-target-compatible.d.ts +4 -0
  51. package/dist/src/catalog/workspace/mcps-shared-target-compatible.js +58 -0
  52. package/dist/src/catalog/workspace/mcps-transport-exclusivity.d.ts +4 -0
  53. package/dist/src/catalog/workspace/mcps-transport-exclusivity.js +67 -0
  54. package/dist/src/catalog/workspace/packs-declarations-valid.d.ts +19 -0
  55. package/dist/src/catalog/workspace/packs-declarations-valid.js +110 -0
  56. package/dist/src/catalog/workspace/packs-dependencies-resolved.d.ts +5 -0
  57. package/dist/src/catalog/workspace/packs-dependencies-resolved.js +49 -0
  58. package/dist/src/catalog/workspace/projections-current.d.ts +15 -0
  59. package/dist/src/catalog/workspace/projections-current.js +35 -0
  60. package/dist/src/catalog/workspace/settings-keys-recognized.d.ts +17 -0
  61. package/dist/src/catalog/workspace/settings-keys-recognized.js +90 -0
  62. package/dist/src/catalog/workspace/settings-schema-valid.d.ts +27 -0
  63. package/dist/src/catalog/workspace/settings-schema-valid.js +68 -0
  64. package/dist/src/catalog/workspace/skills-artifacts-correct.d.ts +28 -0
  65. package/dist/src/catalog/workspace/skills-artifacts-correct.js +143 -0
  66. package/dist/src/catalog/workspace/skills-declarations-valid.d.ts +27 -0
  67. package/dist/src/catalog/workspace/skills-declarations-valid.js +102 -0
  68. package/dist/src/catalog/workspace/skills-integrity-valid.d.ts +28 -0
  69. package/dist/src/catalog/workspace/skills-integrity-valid.js +95 -0
  70. package/dist/src/catalog/workspace/skills-lockfile-aligned.d.ts +5 -0
  71. package/dist/src/catalog/workspace/skills-lockfile-aligned.js +64 -0
  72. package/dist/src/catalog/workspace/source-endpoints-aligned.d.ts +5 -0
  73. package/dist/src/catalog/workspace/source-endpoints-aligned.js +75 -0
  74. package/dist/src/catalog/workspace-fixtures/fixture-state.d.ts +8 -0
  75. package/dist/src/catalog/workspace-fixtures/fixture-state.js +120 -0
  76. package/dist/src/catalog/workspace-fixtures/interpret-ops.d.ts +11 -0
  77. package/dist/src/catalog/workspace-fixtures/interpret-ops.js +9 -0
  78. package/dist/src/catalog/workspace-read-model/lint-workspace.d.ts +191 -0
  79. package/dist/src/catalog/workspace-read-model/lint-workspace.js +651 -0
  80. package/dist/src/catalog/workspace-read-model/manifest-json.d.ts +14 -0
  81. package/dist/src/catalog/workspace-read-model/manifest-json.js +27 -0
  82. package/dist/src/catalog/workspace.d.ts +58 -0
  83. package/dist/src/catalog/workspace.js +164 -0
  84. package/dist/src/catalog-contexts.d.ts +106 -0
  85. package/dist/src/catalog-contexts.js +95 -0
  86. package/dist/src/cli.d.ts +239 -0
  87. package/dist/src/cli.js +1077 -0
  88. package/dist/src/index.d.ts +10 -0
  89. package/dist/src/index.js +21 -0
  90. package/dist/src/json-schema.d.ts +121 -0
  91. package/dist/src/json-schema.js +99 -0
  92. package/dist/src/run/errors.d.ts +23 -0
  93. package/dist/src/run/errors.js +21 -0
  94. package/dist/src/run/settings.d.ts +63 -0
  95. package/dist/src/run/settings.js +139 -0
  96. package/dist/src/run/staged-workspace.d.ts +31 -0
  97. package/dist/src/run/staged-workspace.js +273 -0
  98. package/dist/src/workspace-context.d.ts +129 -0
  99. package/dist/src/workspace-context.js +12 -0
  100. package/package.json +55 -0
@@ -0,0 +1,63 @@
1
+ /** Reports whether accepted external-resolution state is readable and present when required. */
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
4
+ import * as Result from "effect/Result";
5
+ import { isWorkspaceSourceLocator } from "@agentxm/extension-model/unstable/sources/workspace";
6
+ import { lockfileDisplayPath } from "./display-paths.js";
7
+ const RULE_ID = "workspace/lockfile-valid";
8
+ const isExternalSource = (source) => !isWorkspaceSourceLocator(source);
9
+ const finding = (message, path) => ({
10
+ kind: "advisory",
11
+ ruleId: RULE_ID,
12
+ severity: "error",
13
+ message,
14
+ location: { file: path },
15
+ });
16
+ export const lockfileValidRule = {
17
+ id: RULE_ID,
18
+ description: "Accepted external-resolution state is current, readable, and present when required.",
19
+ kind: "advisory",
20
+ severity: "error",
21
+ check: (context) => Effect.gen(function* () {
22
+ const lockfileResult = yield* Effect.result(context.workspace.state.lockfile);
23
+ const lockfilePath = lockfileDisplayPath(context.subject.scope);
24
+ if (Result.isFailure(lockfileResult)) {
25
+ if (lockfileResult.failure._tag === "LockfileDecodeError") {
26
+ return lockfileResult.failure.issues.map((issue) => finding(`The accepted external-resolution lockfile does not match the current schema: ${issue}`, lockfilePath));
27
+ }
28
+ return [
29
+ finding(`The accepted external-resolution lockfile is unreadable: ${lockfileResult.failure._tag}`, lockfilePath),
30
+ ];
31
+ }
32
+ if (Option.isSome(lockfileResult.success))
33
+ return [];
34
+ if (context.health !== undefined) {
35
+ const graph = yield* Effect.result(context.health.desiredState);
36
+ if (Result.isSuccess(graph) &&
37
+ graph.success.nodes.some((node) => node.source !== undefined && isExternalSource(node.source))) {
38
+ return [
39
+ finding("Accepted external-resolution state is missing for desired external content.", lockfilePath),
40
+ ];
41
+ }
42
+ return [];
43
+ }
44
+ const settings = yield* Effect.result(context.workspace.state.settings);
45
+ if (Result.isFailure(settings) || Option.isNone(settings.success))
46
+ return [];
47
+ const declarations = [
48
+ ...Object.values(settings.success.value.skills ?? {}),
49
+ ...Object.values(settings.success.value.packs ?? {}),
50
+ ...Object.values(settings.success.value.subagents ?? {}),
51
+ ...Object.values(settings.success.value.mcpServers ?? {}),
52
+ ...Object.values(settings.success.value.rules ?? {}),
53
+ ...Object.values(settings.success.value.hooks ?? {}),
54
+ ...Object.values(settings.success.value.knowledge ?? {}),
55
+ ];
56
+ return declarations.some((entry) => entry.source !== undefined && isExternalSource(entry.source))
57
+ ? [
58
+ finding("Accepted external-resolution state is missing for desired external content.", lockfilePath),
59
+ ]
60
+ : [];
61
+ }),
62
+ };
63
+ //# sourceMappingURL=lockfile-valid.js.map
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ export declare const managedFileUnownedRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=managed-file-unowned.d.ts.map
@@ -0,0 +1,25 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
3
+ const RULE_ID = "workspace/managed-file-unowned";
4
+ const relativeToRoot = (root, file) => {
5
+ const prefix = `${root}/`;
6
+ return file.startsWith(prefix) ? file.slice(prefix.length) : file;
7
+ };
8
+ export const managedFileUnownedRule = {
9
+ id: RULE_ID,
10
+ description: "Agent-directory artifacts have a resolvable AXM ownership proof.",
11
+ kind: "advisory",
12
+ severity: "warning",
13
+ check: (context) => context.ownership === undefined
14
+ ? Effect.succeed(EMPTY_LINT_FINDINGS)
15
+ : context.ownership.pipe(Effect.map((issues) => issues
16
+ .filter((issue) => issue.kind === "managed-file-unowned")
17
+ .map((issue) => ({
18
+ kind: "advisory",
19
+ ruleId: RULE_ID,
20
+ severity: "warning",
21
+ message: issue.detail,
22
+ location: { file: relativeToRoot(context.subject.root, issue.path) },
23
+ })))),
24
+ };
25
+ //# sourceMappingURL=managed-file-unowned.js.map
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ export declare const mcpServerAgentDriftRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=mcps-agent-drift.d.ts.map
@@ -0,0 +1,139 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { CONFIGURABLE_AGENTS_BY_ID, } from "@agentxm/extension-model/unstable/agent-capabilities";
5
+ import { isAxmManagedMcpEntry } from "@agentxm/workspace-state";
6
+ import { diffAgentEntry, inferInlineRemoteTransport, projectExpectedEntry, resolveSharedMcpTarget, planMcpTargetGroups, } from "@agentxm/extension-workspace";
7
+ const RULE_ID = "workspace/mcps-agent-drift";
8
+ const relativeToRoot = (root, file) => {
9
+ if (file === root)
10
+ return "";
11
+ const prefix = root.endsWith("/") ? root : `${root}/`;
12
+ return file.startsWith(prefix) ? file.slice(prefix.length) : file;
13
+ };
14
+ const hasMcpConfig = (capability) => capability.axm.writer !== null && "transports" in capability.native;
15
+ const isCapabilityAgentId = (agentId) => agentId in CONFIGURABLE_AGENTS_BY_ID;
16
+ const isInlineEntry = (entry) => entry.command !== undefined || entry.url !== undefined;
17
+ const configuredEntry = (row) => row.installationOrigin._tag === "direct" ? row.installationOrigin.declared.entry : undefined;
18
+ const managedConfigActuals = (actuals) => actuals.filter((actual) => (actual.origin._tag === "agent-mcp-config" ||
19
+ actual.origin._tag === "workspace-mcp-config") &&
20
+ actual.config !== null &&
21
+ isAxmManagedMcpEntry(actual.config));
22
+ const configuredAgentIds = (context) => Effect.gen(function* () {
23
+ const settings = yield* Effect.result(context.workspace.state.settings);
24
+ if (Result.isFailure(settings) || Option.isNone(settings.success)) {
25
+ return new Set();
26
+ }
27
+ return new Set(settings.success.value.agents ?? []);
28
+ });
29
+ const findingFor = (args) => {
30
+ const finding = {
31
+ kind: "advisory",
32
+ ruleId: RULE_ID,
33
+ severity: "warning",
34
+ message: `MCP server '${args.name}' has drifted ${args.actual.origin._tag === "agent-mcp-config" ? `agent config for ${args.actual.origin.agentId}` : `shared config for ${args.consumers.join(", ")}`} ` +
35
+ `(${args.fields.join(", ")}).`,
36
+ };
37
+ return args.actual.configFile === null
38
+ ? finding
39
+ : { ...finding, location: { file: relativeToRoot(args.root, args.actual.configFile) } };
40
+ };
41
+ const configFileMatchesTarget = (configFile, targetPath) => configFile === targetPath ||
42
+ configFile.endsWith(`/${targetPath}`) ||
43
+ (targetPath.startsWith("~/") && configFile.endsWith(`/${targetPath.slice(2)}`));
44
+ const checkActual = (args) => {
45
+ if (args.actual.config === null || args.actual.configFile === null)
46
+ return undefined;
47
+ const groups = planMcpTargetGroups({
48
+ configuredAgentIds: [...args.configuredAgents],
49
+ entry: args.entry,
50
+ scope: args.row.key.scope,
51
+ });
52
+ const agentId = args.actual.origin._tag === "agent-mcp-config" ? args.actual.origin.agentId : undefined;
53
+ const group = groups.find((candidate) => {
54
+ const [first] = candidate.members;
55
+ if (first === undefined)
56
+ return false;
57
+ if (!configFileMatchesTarget(args.actual.configFile ?? "", first.target.path))
58
+ return false;
59
+ return agentId !== undefined
60
+ ? candidate.members.some((member) => member.agentId === agentId)
61
+ : first.target.attribution === "shared";
62
+ });
63
+ if (group === undefined)
64
+ return undefined;
65
+ const projectionMember = agentId !== undefined
66
+ ? group.members.find((member) => member.agentId === agentId)
67
+ : group.members[0];
68
+ if (projectionMember === undefined || !isCapabilityAgentId(projectionMember.agentId)) {
69
+ return undefined;
70
+ }
71
+ const capability = CONFIGURABLE_AGENTS_BY_ID[projectionMember.agentId].capabilities["mcp-server"];
72
+ if (!hasMcpConfig(capability))
73
+ return undefined;
74
+ const inference = args.entry.url === undefined ? undefined : inferInlineRemoteTransport(args.entry.url);
75
+ const transport = args.entry.command !== undefined
76
+ ? "stdio"
77
+ : inference?._tag === "supported"
78
+ ? inference.transport
79
+ : undefined;
80
+ if (transport === undefined)
81
+ return undefined;
82
+ const resolution = resolveSharedMcpTarget({ members: group.members, transport });
83
+ if (resolution._tag === "conflict")
84
+ return undefined;
85
+ const config = resolution.config;
86
+ const projected = projectExpectedEntry({
87
+ serverName: args.row.key.name,
88
+ entry: args.entry,
89
+ stdio: config.stdio,
90
+ remote: config.remote,
91
+ activationField: config.activationField,
92
+ envExpansion: capability.native.mcpEnvExpansion,
93
+ });
94
+ if (projected._tag !== "projected")
95
+ return undefined;
96
+ const drift = diffAgentEntry(projected, args.actual.config);
97
+ return drift._tag === "drift"
98
+ ? {
99
+ row: args.row,
100
+ actual: args.actual,
101
+ fields: drift.fields,
102
+ consumers: group.members.map((member) => member.agentId),
103
+ }
104
+ : undefined;
105
+ };
106
+ const driftedAgentConfigs = (rows, configuredAgents) => rows.flatMap((row) => {
107
+ const entry = configuredEntry(row);
108
+ if (entry === undefined || !isInlineEntry(entry))
109
+ return [];
110
+ return managedConfigActuals(row.actual).flatMap((actual) => {
111
+ if (actual.origin._tag === "agent-mcp-config" &&
112
+ !configuredAgents.has(actual.origin.agentId)) {
113
+ return [];
114
+ }
115
+ const drift = checkActual({ row, entry, actual, configuredAgents });
116
+ return drift === undefined ? [] : [drift];
117
+ });
118
+ });
119
+ const findingsForRows = (rows, configuredAgents, root) => driftedAgentConfigs(rows, configuredAgents).map((drift) => findingFor({
120
+ name: drift.row.key.name,
121
+ actual: drift.actual,
122
+ fields: drift.fields,
123
+ consumers: drift.consumers,
124
+ root,
125
+ }));
126
+ export const mcpServerAgentDriftRule = {
127
+ id: RULE_ID,
128
+ description: "Managed MCP server agent configs match AXM settings.",
129
+ kind: "advisory",
130
+ severity: "warning",
131
+ check: (context) => Effect.gen(function* () {
132
+ const rows = yield* Effect.result(context.workspace.mcpServers.installed);
133
+ if (Result.isFailure(rows))
134
+ return [];
135
+ const agents = yield* configuredAgentIds(context);
136
+ return findingsForRows(rows.success, agents, context.subject.root);
137
+ }),
138
+ };
139
+ //# sourceMappingURL=mcps-agent-drift.js.map
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ export declare const mcpServerAgentOrphanedRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=mcps-agent-orphaned.d.ts.map
@@ -0,0 +1,64 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { isAxmManagedMcpEntry } from "@agentxm/workspace-state";
5
+ import { groupConfiguredMcpTargets } from "@agentxm/extension-workspace";
6
+ const RULE_ID = "workspace/mcps-agent-orphaned";
7
+ const isManagedConfigEntry = (row) => (row.actual.origin._tag === "agent-mcp-config" ||
8
+ row.actual.origin._tag === "workspace-mcp-config") &&
9
+ row.actual.config !== null &&
10
+ isAxmManagedMcpEntry(row.actual.config);
11
+ const configuredAgentIds = (context) => Effect.gen(function* () {
12
+ const settings = yield* Effect.result(context.workspace.state.settings);
13
+ if (Result.isFailure(settings) || Option.isNone(settings.success)) {
14
+ return new Set();
15
+ }
16
+ return new Set(settings.success.value.agents ?? []);
17
+ });
18
+ const findingFor = (row) => {
19
+ const target = row.actual.origin._tag === "agent-mcp-config"
20
+ ? `agent config for ${row.actual.origin.agentId}`
21
+ : "shared config";
22
+ const finding = {
23
+ kind: "advisory",
24
+ ruleId: RULE_ID,
25
+ severity: "warning",
26
+ message: `MCP server '${row.key.name}' has an orphaned AXM-owned ${target}.`,
27
+ };
28
+ return row.actual.configFile === null
29
+ ? finding
30
+ : { ...finding, location: { file: row.actual.configFile } };
31
+ };
32
+ const orphanedRows = (rows) => rows.filter(isManagedConfigEntry);
33
+ const configFileMatchesTarget = (configFile, targetPath) => configFile === targetPath ||
34
+ configFile.endsWith(`/${targetPath}`) ||
35
+ (targetPath.startsWith("~/") && configFile.endsWith(`/${targetPath.slice(2)}`));
36
+ const isConfiguredAgentRow = (row, configuredAgents) => row.actual.origin._tag === "agent-mcp-config"
37
+ ? configuredAgents.has(row.actual.origin.agentId)
38
+ : row.actual.configFile !== null &&
39
+ groupConfiguredMcpTargets({
40
+ agentIds: [...configuredAgents],
41
+ scope: row.key.scope,
42
+ }).some((group) => {
43
+ const [first] = group.members;
44
+ return (first !== undefined &&
45
+ first.target.attribution === "shared" &&
46
+ configFileMatchesTarget(row.actual.configFile ?? "", first.target.path));
47
+ });
48
+ const findingsForRows = (rows, configuredAgents) => orphanedRows(rows)
49
+ .filter((row) => isConfiguredAgentRow(row, configuredAgents))
50
+ .map(findingFor);
51
+ export const mcpServerAgentOrphanedRule = {
52
+ id: RULE_ID,
53
+ description: "Managed MCP server agent configs are declared in AXM settings.",
54
+ kind: "advisory",
55
+ severity: "warning",
56
+ check: (context) => Effect.gen(function* () {
57
+ const rows = yield* Effect.result(context.workspace.mcpServers.unmanaged);
58
+ if (Result.isFailure(rows))
59
+ return [];
60
+ const agents = yield* configuredAgentIds(context);
61
+ return findingsForRows(rows.success, agents);
62
+ }),
63
+ };
64
+ //# sourceMappingURL=mcps-agent-orphaned.js.map
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ export declare const mcpServerNoSecretLiteralRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=mcps-no-secret-literal.d.ts.map
@@ -0,0 +1,67 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { settingsDisplayPath } from "./display-paths.js";
5
+ import { EMPTY_ADVISORY_FINDINGS } from "./helpers/empty.js";
6
+ const RULE_ID = "workspace/mcps-no-secret-literal";
7
+ const SECRET_KEY_RE = /(authorization|bearer|token|secret|password|api[-_]?key|access[-_]?key)/i;
8
+ const TOKEN_VALUE_RE = /^(gh[pousr]_|sk-[A-Za-z0-9]|xox[baprs]-|eyJ[A-Za-z0-9_-]{20,}|[A-Za-z0-9_-]{32,})/;
9
+ const ENV_REF_RE = /\$\{[A-Za-z_][A-Za-z0-9_]*\}/;
10
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
11
+ const parseSettings = (bytes) => {
12
+ try {
13
+ const parsed = JSON.parse(bytes);
14
+ return isRecord(parsed) ? parsed : undefined;
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ };
20
+ const isSecretLiteral = (name, value) => {
21
+ if (value.trim().length === 0)
22
+ return false;
23
+ if (ENV_REF_RE.test(value))
24
+ return false;
25
+ return SECRET_KEY_RE.test(name) || TOKEN_VALUE_RE.test(value);
26
+ };
27
+ const findingFor = (serverName, field, settingsPath) => ({
28
+ kind: "advisory",
29
+ ruleId: RULE_ID,
30
+ severity: "warning",
31
+ message: `MCP server '${serverName}' stores a secret-looking literal in ${field}. ` +
32
+ "Use a `${VAR}` reference so axm.json does not contain the secret.",
33
+ location: { file: settingsPath },
34
+ });
35
+ const collectRecordFindings = (serverName, fieldPrefix, value, settingsPath) => {
36
+ if (!isRecord(value))
37
+ return [];
38
+ return Object.entries(value).flatMap(([key, item]) => typeof item === "string" && isSecretLiteral(key, item)
39
+ ? [findingFor(serverName, `${fieldPrefix}.${key}`, settingsPath)]
40
+ : []);
41
+ };
42
+ export const mcpServerNoSecretLiteralRule = {
43
+ id: RULE_ID,
44
+ description: "MCP server settings use env references instead of secret literals.",
45
+ kind: "advisory",
46
+ severity: "warning",
47
+ check: (context) => Effect.gen(function* () {
48
+ const raw = yield* Effect.result(context.workspace.state.raw("settings"));
49
+ if (Result.isFailure(raw) || Option.isNone(raw.success))
50
+ return EMPTY_ADVISORY_FINDINGS;
51
+ const settings = parseSettings(raw.success.value.bytes);
52
+ if (settings === undefined)
53
+ return EMPTY_ADVISORY_FINDINGS;
54
+ const mcpServers = settings["mcpServers"];
55
+ if (!isRecord(mcpServers))
56
+ return EMPTY_ADVISORY_FINDINGS;
57
+ return Object.entries(mcpServers).flatMap(([serverName, entry]) => {
58
+ if (!isRecord(entry))
59
+ return [];
60
+ return [
61
+ ...collectRecordFindings(serverName, "env", entry["env"], settingsDisplayPath(context.subject.scope)),
62
+ ...collectRecordFindings(serverName, "headers", entry["headers"], settingsDisplayPath(context.subject.scope)),
63
+ ];
64
+ });
65
+ }),
66
+ };
67
+ //# sourceMappingURL=mcps-no-secret-literal.js.map
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ export declare const mcpServerSharedTargetCompatibleRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=mcps-shared-target-compatible.d.ts.map
@@ -0,0 +1,58 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { inferInlineRemoteTransport, resolveSharedMcpTarget, groupConfiguredMcpTargets, } from "@agentxm/extension-workspace";
5
+ import { settingsDisplayPath } from "./display-paths.js";
6
+ const RULE_ID = "workspace/mcps-shared-target-compatible";
7
+ const transportFor = (entry) => {
8
+ if (entry.command !== undefined)
9
+ return "stdio";
10
+ if (entry.url === undefined)
11
+ return undefined;
12
+ const inference = inferInlineRemoteTransport(entry.url);
13
+ return inference._tag === "supported" ? inference.transport : undefined;
14
+ };
15
+ const findingFor = (serverName, path, reason, settingsPath) => ({
16
+ kind: "advisory",
17
+ ruleId: RULE_ID,
18
+ severity: "error",
19
+ message: "MCP server '" +
20
+ serverName +
21
+ "' projects a value that another configured agent rejects in shared target '" +
22
+ path +
23
+ "'. " +
24
+ reason +
25
+ " Update the configured agents or their MCP compatibility metadata before syncing.",
26
+ location: { file: settingsPath },
27
+ });
28
+ export const mcpServerSharedTargetCompatibleRule = {
29
+ id: RULE_ID,
30
+ description: "Configured agents accept the values projected into shared MCP config targets.",
31
+ kind: "advisory",
32
+ severity: "error",
33
+ check: (context) => Effect.gen(function* () {
34
+ const settings = yield* Effect.result(context.workspace.state.settings);
35
+ if (Result.isFailure(settings) || Option.isNone(settings.success))
36
+ return [];
37
+ const agentIds = settings.success.value.agents ?? [];
38
+ const entries = settings.success.value.mcpServers ?? {};
39
+ const groups = groupConfiguredMcpTargets({
40
+ agentIds,
41
+ scope: context.subject.scope,
42
+ }).filter((group) => group.members.length > 1);
43
+ return Object.entries(entries).flatMap(([serverName, entry]) => {
44
+ const transport = transportFor(entry);
45
+ if (transport === undefined)
46
+ return [];
47
+ return groups.flatMap((group) => {
48
+ const resolution = resolveSharedMcpTarget({ members: group.members, transport });
49
+ return resolution._tag === "conflict"
50
+ ? [
51
+ findingFor(serverName, resolution.path, resolution.reason, settingsDisplayPath(context.subject.scope)),
52
+ ]
53
+ : [];
54
+ });
55
+ });
56
+ }),
57
+ };
58
+ //# sourceMappingURL=mcps-shared-target-compatible.js.map
@@ -0,0 +1,4 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ export declare const mcpServerTransportExclusivityRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=mcps-transport-exclusivity.d.ts.map
@@ -0,0 +1,67 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { settingsDisplayPath } from "./display-paths.js";
5
+ import { EMPTY_ADVISORY_FINDINGS } from "./helpers/empty.js";
6
+ const RULE_ID = "workspace/mcps-transport-exclusivity";
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const hasOwnValue = (entry, key) => Object.hasOwn(entry, key) && entry[key] !== undefined;
9
+ const parseSettings = (bytes) => {
10
+ try {
11
+ const parsed = JSON.parse(bytes);
12
+ return isRecord(parsed) ? parsed : undefined;
13
+ }
14
+ catch {
15
+ return undefined;
16
+ }
17
+ };
18
+ const transportError = (entry) => {
19
+ const transports = ["source", "command", "url"].filter((key) => hasOwnValue(entry, key));
20
+ if (transports.length !== 1) {
21
+ return "must include exactly one of source, command, or url";
22
+ }
23
+ if (hasOwnValue(entry, "source") &&
24
+ (hasOwnValue(entry, "args") || hasOwnValue(entry, "headers"))) {
25
+ return "source entries cannot include args or headers";
26
+ }
27
+ if (hasOwnValue(entry, "command") && hasOwnValue(entry, "headers")) {
28
+ return "command entries cannot include headers";
29
+ }
30
+ if (hasOwnValue(entry, "url") && hasOwnValue(entry, "args")) {
31
+ return "URL entries cannot include args";
32
+ }
33
+ return undefined;
34
+ };
35
+ const findingFor = (name, reason, settingsPath) => ({
36
+ kind: "advisory",
37
+ ruleId: RULE_ID,
38
+ severity: "warning",
39
+ message: `MCP server '${name}' ${reason}. Edit \`${settingsPath}\` so each MCP server uses one transport.`,
40
+ location: { file: settingsPath },
41
+ });
42
+ export const mcpServerTransportExclusivityRule = {
43
+ id: RULE_ID,
44
+ description: "MCP server settings entries use exactly one transport.",
45
+ kind: "advisory",
46
+ severity: "warning",
47
+ check: (context) => Effect.gen(function* () {
48
+ const raw = yield* Effect.result(context.workspace.state.raw("settings"));
49
+ if (Result.isFailure(raw) || Option.isNone(raw.success))
50
+ return EMPTY_ADVISORY_FINDINGS;
51
+ const settings = parseSettings(raw.success.value.bytes);
52
+ if (settings === undefined)
53
+ return EMPTY_ADVISORY_FINDINGS;
54
+ const mcpServers = settings["mcpServers"];
55
+ if (!isRecord(mcpServers))
56
+ return EMPTY_ADVISORY_FINDINGS;
57
+ return Object.entries(mcpServers).flatMap(([name, entry]) => {
58
+ if (!isRecord(entry))
59
+ return [];
60
+ const reason = transportError(entry);
61
+ return reason === undefined
62
+ ? []
63
+ : [findingFor(name, reason, settingsDisplayPath(context.subject.scope))];
64
+ });
65
+ }),
66
+ };
67
+ //# sourceMappingURL=mcps-transport-exclusivity.js.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `workspace/packs-declarations-valid` — declared packs name a resolvable,
3
+ * owner-qualified, unique source.
4
+ *
5
+ * Mirrors `workspace/skills-declarations-valid` against the packs map:
6
+ *
7
+ * 1. Source resolvability — pack source is not a bare name.
8
+ * 2. Owner qualification — registry-shaped pack sources carry `@owner/`.
9
+ * 3. Duplicate FQNs — two settings entries normalize to the same FQN.
10
+ *
11
+ * One finding per affected entity. Advisory.
12
+ *
13
+ * @experimental This API is unstable and may change without notice.
14
+ * @packageDocumentation
15
+ */
16
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
17
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
18
+ export declare const packsDeclarationsValidRule: AdvisoryRule<WorkspaceRuleContext>;
19
+ //# sourceMappingURL=packs-declarations-valid.d.ts.map
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `workspace/packs-declarations-valid` — declared packs name a resolvable,
3
+ * owner-qualified, unique source.
4
+ *
5
+ * Mirrors `workspace/skills-declarations-valid` against the packs map:
6
+ *
7
+ * 1. Source resolvability — pack source is not a bare name.
8
+ * 2. Owner qualification — registry-shaped pack sources carry `@owner/`.
9
+ * 3. Duplicate FQNs — two settings entries normalize to the same FQN.
10
+ *
11
+ * One finding per affected entity. Advisory.
12
+ *
13
+ * @experimental This API is unstable and may change without notice.
14
+ * @packageDocumentation
15
+ */
16
+ import * as Effect from "effect/Effect";
17
+ import * as Option from "effect/Option";
18
+ import * as Result from "effect/Result";
19
+ import { settingsDisplayPath } from "./display-paths.js";
20
+ import { EMPTY_ADVISORY_FINDINGS } from "./helpers/empty.js";
21
+ import { categorizeEntry } from "./helpers/source-categorize.js";
22
+ const RULE_ID = "workspace/packs-declarations-valid";
23
+ const bareNameFinding = (entry, settingsPath) => ({
24
+ kind: "advisory",
25
+ ruleId: RULE_ID,
26
+ severity: "error",
27
+ message: `Pack '${entry.name}' uses ambiguous bare source '${entry.source}'.`,
28
+ location: { file: settingsPath },
29
+ });
30
+ const nonRegistryFinding = (entry, settingsPath) => ({
31
+ kind: "advisory",
32
+ ruleId: RULE_ID,
33
+ severity: "error",
34
+ message: `Pack '${entry.name}' source '${entry.source}' is not a supported Pack source reference.`,
35
+ location: { file: settingsPath },
36
+ });
37
+ const missingOwnerFinding = (entry, settingsPath) => ({
38
+ kind: "advisory",
39
+ ruleId: RULE_ID,
40
+ severity: "error",
41
+ message: `Pack '${entry.name}' source '${entry.source}' is missing its owner-qualified identity.`,
42
+ location: { file: settingsPath },
43
+ });
44
+ const duplicateFinding = (entry, duplicates, settingsPath) => ({
45
+ kind: "advisory",
46
+ ruleId: RULE_ID,
47
+ severity: "error",
48
+ message: `Pack '${entry.name}' duplicates the same Pack identity as: ${duplicates.join(", ")}.`,
49
+ location: { file: settingsPath },
50
+ });
51
+ export const packsDeclarationsValidRule = {
52
+ id: RULE_ID,
53
+ description: "Declared packs use unique, resolvable registry sources.",
54
+ kind: "advisory",
55
+ severity: "error",
56
+ check: (context) => Effect.gen(function* () {
57
+ const scoped = context.workspace;
58
+ const declaredResult = yield* Effect.result(scoped.packs.declared);
59
+ if (Result.isFailure(declaredResult) || Option.isNone(declaredResult.success)) {
60
+ return EMPTY_ADVISORY_FINDINGS;
61
+ }
62
+ const entries = declaredResult.success.value.map(({ name, entry }) => categorizeEntry(name, entry.source));
63
+ const byFqn = new Map();
64
+ for (const entry of entries) {
65
+ if ((entry.kind === "registry" || entry.kind === "workspace") &&
66
+ entry.registryFqn !== undefined) {
67
+ const group = byFqn.get(entry.registryFqn) ?? [];
68
+ group.push(entry);
69
+ byFqn.set(entry.registryFqn, group);
70
+ }
71
+ }
72
+ const findings = [];
73
+ const settingsPath = settingsDisplayPath(context.subject.scope);
74
+ for (const entry of entries) {
75
+ switch (entry.kind) {
76
+ case "bare":
77
+ findings.push(bareNameFinding(entry, settingsPath));
78
+ break;
79
+ case "non-registry":
80
+ findings.push(nonRegistryFinding(entry, settingsPath));
81
+ break;
82
+ case "registry-no-owner":
83
+ findings.push(missingOwnerFinding(entry, settingsPath));
84
+ break;
85
+ case "registry": {
86
+ if (entry.registryFqn === undefined) {
87
+ break;
88
+ }
89
+ const group = byFqn.get(entry.registryFqn) ?? [];
90
+ if (group.length > 1) {
91
+ findings.push(duplicateFinding(entry, group.map((g) => g.name).sort(), settingsPath));
92
+ }
93
+ break;
94
+ }
95
+ case "workspace": {
96
+ if (entry.registryFqn === undefined) {
97
+ break;
98
+ }
99
+ const group = byFqn.get(entry.registryFqn) ?? [];
100
+ if (group.length > 1) {
101
+ findings.push(duplicateFinding(entry, group.map((g) => g.name).sort(), settingsPath));
102
+ }
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ return findings;
108
+ }),
109
+ };
110
+ //# sourceMappingURL=packs-declarations-valid.js.map
@@ -0,0 +1,5 @@
1
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
3
+ /** Pack membership comes from the desired graph; lock rows only prove external resolution. */
4
+ export declare const packsDependenciesResolvedRule: AdvisoryRule<WorkspaceRuleContext>;
5
+ //# sourceMappingURL=packs-dependencies-resolved.d.ts.map