@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,49 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { lockfileDisplayPath } from "./display-paths.js";
5
+ const RULE_ID = "workspace/packs-dependencies-resolved";
6
+ /** Pack membership comes from the desired graph; lock rows only prove external resolution. */
7
+ export const packsDependenciesResolvedRule = {
8
+ id: RULE_ID,
9
+ description: "Pack-declared external dependencies have accepted resolutions.",
10
+ kind: "advisory",
11
+ severity: "error",
12
+ check: (context) => Effect.gen(function* () {
13
+ if (context.health === undefined)
14
+ return [];
15
+ const graphResult = yield* Effect.result(context.health.desiredState);
16
+ const lockResult = yield* Effect.result(context.workspace.state.lockfile);
17
+ if (Result.isFailure(graphResult) || Result.isFailure(lockResult))
18
+ return [];
19
+ const lockfile = Option.getOrUndefined(lockResult.success);
20
+ if (lockfile === undefined)
21
+ return [];
22
+ const lockedNames = {
23
+ skill: new Set(Object.keys(lockfile.skills)),
24
+ subagent: new Set(Object.keys(lockfile.subagents ?? {})),
25
+ "mcp-server": new Set(Object.keys(lockfile.mcpServers ?? {})),
26
+ rule: new Set(Object.keys(lockfile.rules ?? {})),
27
+ hook: new Set(Object.keys(lockfile.hooks ?? {})),
28
+ knowledge: new Set(Object.keys(lockfile.knowledge ?? {})),
29
+ };
30
+ const findings = [];
31
+ for (const node of graphResult.success.nodes) {
32
+ if (node.type === "pack" ||
33
+ node.identity.startsWith("workspace:") ||
34
+ !node.origins.some((origin) => origin.type === "pack") ||
35
+ lockedNames[node.type].has(node.name)) {
36
+ continue;
37
+ }
38
+ findings.push({
39
+ kind: "advisory",
40
+ ruleId: RULE_ID,
41
+ severity: "error",
42
+ message: `Pack-declared ${node.type} '${node.identity}' has no accepted external resolution.`,
43
+ location: { file: lockfileDisplayPath(context.subject.scope) },
44
+ });
45
+ }
46
+ return findings;
47
+ }),
48
+ };
49
+ //# sourceMappingURL=packs-dependencies-resolved.js.map
@@ -0,0 +1,15 @@
1
+ import { type ProjectionInvariantFact } from "@agentxm/extension-workspace";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ import type { AdvisoryRule, LintFinding } from "@agentxm/registry-protocol/unstable/lint/rule";
4
+ /**
5
+ * Managed output units render their complete contributor sets and content.
6
+ *
7
+ * Evidence is read back from the outputs themselves: an extension whose
8
+ * canonical content is installed and reachable can still be absent from, or
9
+ * stale in, its projection. The accessor yields no verdict when currency
10
+ * cannot be judged (incomplete desired-state graph, missing canonical
11
+ * content); root-cause rules own those conditions.
12
+ */
13
+ export declare const projectionsCurrentRule: AdvisoryRule<WorkspaceRuleContext>;
14
+ export declare const findingsForProjectionFacts: (facts: ReadonlyArray<ProjectionInvariantFact>) => ReadonlyArray<LintFinding>;
15
+ //# sourceMappingURL=projections-current.d.ts.map
@@ -0,0 +1,35 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { projectionFactIsViolation, } from "@agentxm/extension-workspace";
3
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
4
+ const RULE_ID = "workspace/projections-current";
5
+ /**
6
+ * Managed output units render their complete contributor sets and content.
7
+ *
8
+ * Evidence is read back from the outputs themselves: an extension whose
9
+ * canonical content is installed and reachable can still be absent from, or
10
+ * stale in, its projection. The accessor yields no verdict when currency
11
+ * cannot be judged (incomplete desired-state graph, missing canonical
12
+ * content); root-cause rules own those conditions.
13
+ */
14
+ export const projectionsCurrentRule = {
15
+ id: RULE_ID,
16
+ description: "Managed outputs render every enabled reachable contributor exactly once.",
17
+ kind: "advisory",
18
+ severity: "error",
19
+ check: (context) => Effect.gen(function* () {
20
+ if (context.projections === undefined)
21
+ return EMPTY_LINT_FINDINGS;
22
+ return findingsForProjectionFacts(yield* context.projections.facts);
23
+ }),
24
+ };
25
+ const contributorSuffix = (fact) => fact.affectedContributors.length === 0
26
+ ? ""
27
+ : ` Affected contributors: ${fact.affectedContributors.join(", ")}.`;
28
+ export const findingsForProjectionFacts = (facts) => facts.filter(projectionFactIsViolation).map((fact) => ({
29
+ kind: "advisory",
30
+ ruleId: RULE_ID,
31
+ severity: "error",
32
+ message: fact.observation.message ??
33
+ `The AXM-owned projection at ${fact.subject.path} is ${fact.observation.status}.${contributorSuffix(fact)}`,
34
+ }));
35
+ //# sourceMappingURL=projections-current.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `workspace/settings-keys-recognized` — `axm.json` uses only
3
+ * supported top-level keys.
4
+ *
5
+ * `SettingsSchema` tolerates and preserves unknown top-level keys so a write
6
+ * never destroys data it did not create; this rule is the guardrail that
7
+ * surfaces those keys loudly. It early-returns when the file is missing or
8
+ * unparsable (`workspace/initialized` and `workspace/settings-schema-valid`
9
+ * own those arms).
10
+ *
11
+ * @experimental This API is unstable and may change without notice.
12
+ * @packageDocumentation
13
+ */
14
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
15
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
16
+ export declare const settingsKeysRecognizedRule: AdvisoryRule<WorkspaceRuleContext>;
17
+ //# sourceMappingURL=settings-keys-recognized.d.ts.map
@@ -0,0 +1,90 @@
1
+ /**
2
+ * `workspace/settings-keys-recognized` — `axm.json` uses only
3
+ * supported top-level keys.
4
+ *
5
+ * `SettingsSchema` tolerates and preserves unknown top-level keys so a write
6
+ * never destroys data it did not create; this rule is the guardrail that
7
+ * surfaces those keys loudly. It early-returns when the file is missing or
8
+ * unparsable (`workspace/initialized` and `workspace/settings-schema-valid`
9
+ * own those arms).
10
+ *
11
+ * @experimental This API is unstable and may change without notice.
12
+ * @packageDocumentation
13
+ */
14
+ import * as Effect from "effect/Effect";
15
+ import * as Option from "effect/Option";
16
+ import * as Result from "effect/Result";
17
+ import { SETTINGS_KNOWN_KEYS } from "@agentxm/workspace-state";
18
+ import { settingsDisplayPath } from "./display-paths.js";
19
+ import { EMPTY_ADVISORY_FINDINGS } from "./helpers/empty.js";
20
+ const RULE_ID = "workspace/settings-keys-recognized";
21
+ const levenshteinDistance = (left, right) => {
22
+ const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
23
+ for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
24
+ let diagonal = previous[0] ?? 0;
25
+ previous[0] = leftIndex;
26
+ for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
27
+ const above = previous[rightIndex] ?? 0;
28
+ const substitution = left[leftIndex - 1] === right[rightIndex - 1] ? diagonal : diagonal + 1;
29
+ previous[rightIndex] = Math.min(above + 1, (previous[rightIndex - 1] ?? 0) + 1, substitution);
30
+ diagonal = above;
31
+ }
32
+ }
33
+ return previous[right.length] ?? 0;
34
+ };
35
+ const closestKnownKey = (key) => {
36
+ const lowered = key.toLowerCase();
37
+ let best;
38
+ for (const candidate of SETTINGS_KNOWN_KEYS) {
39
+ if (candidate.toLowerCase() === lowered)
40
+ return candidate;
41
+ const distance = levenshteinDistance(key, candidate);
42
+ if (distance <= 2 && (best === undefined || distance < best.distance)) {
43
+ best = { candidate, distance };
44
+ }
45
+ }
46
+ return best?.candidate;
47
+ };
48
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
49
+ // `workspace/settings-schema-valid` owns the invalid-JSON arm; this rule stays
50
+ // quiet unless the bytes parse to a plain object.
51
+ const parseJsonRecord = (raw) => {
52
+ try {
53
+ const value = JSON.parse(raw);
54
+ return isRecord(value) ? value : undefined;
55
+ }
56
+ catch {
57
+ return undefined;
58
+ }
59
+ };
60
+ export const settingsKeysRecognizedRule = {
61
+ id: RULE_ID,
62
+ description: "Workspace settings use only supported top-level keys.",
63
+ kind: "advisory",
64
+ severity: "error",
65
+ check: (context) => Effect.gen(function* () {
66
+ const raw = yield* Effect.result(context.workspace.state.raw("settings"));
67
+ if (Result.isFailure(raw) || Option.isNone(raw.success)) {
68
+ return EMPTY_ADVISORY_FINDINGS;
69
+ }
70
+ const parsed = parseJsonRecord(raw.success.value.bytes);
71
+ if (parsed === undefined) {
72
+ return EMPTY_ADVISORY_FINDINGS;
73
+ }
74
+ return Object.keys(parsed)
75
+ .filter((key) => !SETTINGS_KNOWN_KEYS.has(key))
76
+ .map((key) => {
77
+ const hint = closestKnownKey(key);
78
+ return {
79
+ kind: "advisory",
80
+ ruleId: RULE_ID,
81
+ severity: "error",
82
+ message: `Workspace settings has unrecognized top-level key '${key}'.` +
83
+ (hint === undefined ? "" : ` Did you mean '${hint}'?`) +
84
+ ` The current settings schema does not recognize this key.`,
85
+ location: { file: settingsDisplayPath(context.subject.scope) },
86
+ };
87
+ });
88
+ }),
89
+ };
90
+ //# sourceMappingURL=settings-keys-recognized.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `workspace/settings-schema-valid` — `axm.json` conforms to
3
+ * `SettingsSchema`.
4
+ *
5
+ * Cascade per the lint design:
6
+ *
7
+ * 1. `axm.json` is present (`workspace/initialized` owns the
8
+ * absence arm; this rule early-returns when the file is missing).
9
+ * 2. The file parses as JSON (the accessor surfaces parse failures via
10
+ * `SettingsReadError`; we emit one finding per read failure).
11
+ * 3. The parsed value is a JSON object (not an array or scalar) — enforced
12
+ * by `Schema.decodeUnknownResult(SettingsSchema)` structurally.
13
+ * 4. `SettingsSchema` decode succeeds; `ParseResult` issues map 1:1 to
14
+ * findings via `schemaDecodeFindings`.
15
+ *
16
+ * Reports the read-failure finding when arm 2 fails; otherwise reports the
17
+ * schema issues produced by arm 3/4. The unknown-key companion
18
+ * (`workspace/settings-keys-recognized`) reports unrecognized top-level keys,
19
+ * which `SettingsSchema` now tolerates and preserves rather than rejecting.
20
+ *
21
+ * @experimental This API is unstable and may change without notice.
22
+ * @packageDocumentation
23
+ */
24
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
25
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
26
+ export declare const settingsSchemaValidRule: AdvisoryRule<WorkspaceRuleContext>;
27
+ //# sourceMappingURL=settings-schema-valid.d.ts.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * `workspace/settings-schema-valid` — `axm.json` conforms to
3
+ * `SettingsSchema`.
4
+ *
5
+ * Cascade per the lint design:
6
+ *
7
+ * 1. `axm.json` is present (`workspace/initialized` owns the
8
+ * absence arm; this rule early-returns when the file is missing).
9
+ * 2. The file parses as JSON (the accessor surfaces parse failures via
10
+ * `SettingsReadError`; we emit one finding per read failure).
11
+ * 3. The parsed value is a JSON object (not an array or scalar) — enforced
12
+ * by `Schema.decodeUnknownResult(SettingsSchema)` structurally.
13
+ * 4. `SettingsSchema` decode succeeds; `ParseResult` issues map 1:1 to
14
+ * findings via `schemaDecodeFindings`.
15
+ *
16
+ * Reports the read-failure finding when arm 2 fails; otherwise reports the
17
+ * schema issues produced by arm 3/4. The unknown-key companion
18
+ * (`workspace/settings-keys-recognized`) reports unrecognized top-level keys,
19
+ * which `SettingsSchema` now tolerates and preserves rather than rejecting.
20
+ *
21
+ * @experimental This API is unstable and may change without notice.
22
+ * @packageDocumentation
23
+ */
24
+ import * as Effect from "effect/Effect";
25
+ import * as Option from "effect/Option";
26
+ import * as Result from "effect/Result";
27
+ import { EMPTY_ADVISORY_FINDINGS } from "./helpers/empty.js";
28
+ import { settingsDisplayPath } from "./display-paths.js";
29
+ const RULE_ID = "workspace/settings-schema-valid";
30
+ export const settingsSchemaValidRule = {
31
+ id: RULE_ID,
32
+ description: "Workspace settings are structurally valid.",
33
+ kind: "advisory",
34
+ severity: "error",
35
+ check: (context) => Effect.gen(function* () {
36
+ const scoped = context.workspace;
37
+ const settingsPath = settingsDisplayPath(context.subject.scope);
38
+ const raw = yield* Effect.result(scoped.state.raw("settings"));
39
+ if (Result.isSuccess(raw) && Option.isNone(raw.success)) {
40
+ return EMPTY_ADVISORY_FINDINGS;
41
+ }
42
+ const settings = yield* Effect.result(scoped.state.settings);
43
+ if (Result.isFailure(settings)) {
44
+ const error = settings.failure;
45
+ if (error._tag === "SettingsDecodeError") {
46
+ return error.issues.map((issue) => ({
47
+ kind: "advisory",
48
+ ruleId: RULE_ID,
49
+ severity: "error",
50
+ message: `The workspace settings file does not match the expected schema. Detail: ${issue}. Edit \`${settingsPath}\` to fix the invalid value.`,
51
+ location: { file: settingsPath },
52
+ }));
53
+ }
54
+ return [
55
+ {
56
+ kind: "advisory",
57
+ ruleId: RULE_ID,
58
+ severity: "error",
59
+ message: `The workspace settings file is not valid JSON. Detail: ${error._tag}. ` +
60
+ `Edit \`${settingsPath}\` to fix the JSON syntax.`,
61
+ location: { file: settingsPath },
62
+ },
63
+ ];
64
+ }
65
+ return EMPTY_ADVISORY_FINDINGS;
66
+ }),
67
+ };
68
+ //# sourceMappingURL=settings-schema-valid.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `workspace/skills-artifacts-correct` — each enabled skill has artifacts in
3
+ * every declared agent's skill target; disabled skills have none.
4
+ *
5
+ * Three symptoms of the same invariant (§10.workspace.Skills note):
6
+ *
7
+ * - **Enabled-but-not-linked** — a skill declared `enabled: true` is missing
8
+ * its per-agent artifact for at least one declared agent. Autofix:
9
+ * `enable-skill` (the handler recreates symlinks across configured agents).
10
+ * - **Disabled-but-still-present** — a skill declared `enabled: false` still
11
+ * has a per-agent artifact for at least one declared agent. Autofix:
12
+ * `disable-skill`.
13
+ * - **Cross-agent-inconsistent** — an enabled skill has artifacts in some
14
+ * declared agents but not others. Autofix: `enable-skill`.
15
+ *
16
+ * One finding per affected skill (per-entity cascade); the first arm that
17
+ * fires for a skill emits its finding and the other arms for the same skill
18
+ * do not. Configured skills keep the existing autofix arms; pack-provided
19
+ * implicit skills emit advisory findings because the repair is a pack-level
20
+ * reinstall.
21
+ *
22
+ * @experimental This API is unstable and may change without notice.
23
+ * @packageDocumentation
24
+ */
25
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
26
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
27
+ export declare const skillsArtifactsCorrectRule: AdvisoryRule<WorkspaceRuleContext>;
28
+ //# sourceMappingURL=skills-artifacts-correct.d.ts.map
@@ -0,0 +1,143 @@
1
+ /**
2
+ * `workspace/skills-artifacts-correct` — each enabled skill has artifacts in
3
+ * every declared agent's skill target; disabled skills have none.
4
+ *
5
+ * Three symptoms of the same invariant (§10.workspace.Skills note):
6
+ *
7
+ * - **Enabled-but-not-linked** — a skill declared `enabled: true` is missing
8
+ * its per-agent artifact for at least one declared agent. Autofix:
9
+ * `enable-skill` (the handler recreates symlinks across configured agents).
10
+ * - **Disabled-but-still-present** — a skill declared `enabled: false` still
11
+ * has a per-agent artifact for at least one declared agent. Autofix:
12
+ * `disable-skill`.
13
+ * - **Cross-agent-inconsistent** — an enabled skill has artifacts in some
14
+ * declared agents but not others. Autofix: `enable-skill`.
15
+ *
16
+ * One finding per affected skill (per-entity cascade); the first arm that
17
+ * fires for a skill emits its finding and the other arms for the same skill
18
+ * do not. Configured skills keep the existing autofix arms; pack-provided
19
+ * implicit skills emit advisory findings because the repair is a pack-level
20
+ * reinstall.
21
+ *
22
+ * @experimental This API is unstable and may change without notice.
23
+ * @packageDocumentation
24
+ */
25
+ import * as Effect from "effect/Effect";
26
+ import * as Option from "effect/Option";
27
+ import * as Result from "effect/Result";
28
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
29
+ import { isUniversalSkillsRelativeDir, resolveUniversalDirPresence, } from "@agentxm/extension-model/unstable/extensions/universal-skills-dir";
30
+ import { isConfigurableAgentId } from "@agentxm/extension-model/unstable/agents/types";
31
+ import { settingsDisplayPath } from "./display-paths.js";
32
+ const RULE_ID = "workspace/skills-artifacts-correct";
33
+ const enableFinding = (name, reason, path) => ({
34
+ kind: "advisory",
35
+ ruleId: RULE_ID,
36
+ severity: "error",
37
+ message: `Skill '${name}' is enabled, but it is missing from declared agents: ${reason}.`,
38
+ location: { file: path },
39
+ });
40
+ const disableFinding = (name, reason, path) => ({
41
+ kind: "advisory",
42
+ ruleId: RULE_ID,
43
+ severity: "error",
44
+ message: `Skill '${name}' is disabled, but it is still present for declared agents: ${reason}.`,
45
+ location: { file: path },
46
+ });
47
+ const inconsistentFinding = (name, details, path) => ({
48
+ kind: "advisory",
49
+ ruleId: RULE_ID,
50
+ severity: "error",
51
+ message: `Skill '${name}' is present for some declared agents but missing from others. ${details}.`,
52
+ location: { file: path },
53
+ });
54
+ const implicitEnableFinding = (name, reason, path) => ({
55
+ kind: "advisory",
56
+ ruleId: RULE_ID,
57
+ severity: "error",
58
+ message: `Pack-provided skill '${name}' is missing from declared agents: ${reason}.`,
59
+ location: { file: path },
60
+ });
61
+ const collectArtifactViolations = (existenceBySkill, settingsPath) => {
62
+ const violations = [];
63
+ for (const { name, enabled, presentAgents, missingAgents, implicit } of existenceBySkill) {
64
+ if (enabled) {
65
+ if (missingAgents.length === 0) {
66
+ continue;
67
+ }
68
+ if (implicit) {
69
+ violations.push({
70
+ finding: implicitEnableFinding(name, missingAgents.join(", "), settingsPath),
71
+ });
72
+ continue;
73
+ }
74
+ if (presentAgents.length === 0) {
75
+ violations.push({
76
+ finding: enableFinding(name, missingAgents.join(", "), settingsPath),
77
+ });
78
+ continue;
79
+ }
80
+ violations.push({
81
+ finding: inconsistentFinding(name, `Present for agents: ${presentAgents.join(", ")}. Missing from agents: ${missingAgents.join(", ")}`, settingsPath),
82
+ });
83
+ continue;
84
+ }
85
+ if (presentAgents.length > 0) {
86
+ violations.push({
87
+ finding: disableFinding(name, presentAgents.join(", "), settingsPath),
88
+ });
89
+ }
90
+ }
91
+ return violations;
92
+ };
93
+ export const skillsArtifactsCorrectRule = {
94
+ id: RULE_ID,
95
+ description: "Skill directories match each skill's enabled state across declared agents.",
96
+ kind: "advisory",
97
+ severity: "error",
98
+ check: (context) => Effect.gen(function* () {
99
+ const scoped = context.workspace;
100
+ const settingsResult = yield* Effect.result(scoped.state.settings);
101
+ // The schema/readability rules own these failures and emit the precise
102
+ // finding. This dependent rule cannot evaluate artifact correctness.
103
+ if (Result.isFailure(settingsResult))
104
+ return EMPTY_LINT_FINDINGS;
105
+ const settings = settingsResult.success;
106
+ if (Option.isNone(settings)) {
107
+ return EMPTY_LINT_FINDINGS;
108
+ }
109
+ const declaredAgentIds = new Set(settings.value.agents ?? []);
110
+ if (declaredAgentIds.size === 0) {
111
+ return EMPTY_LINT_FINDINGS;
112
+ }
113
+ const knownAgents = yield* scoped.agents.known;
114
+ const declaredAgents = knownAgents.filter((agent) => isConfigurableAgentId(agent.id) && declaredAgentIds.has(agent.id));
115
+ const universalAgentIds = new Set(declaredAgents
116
+ .filter((agent) => agent.skills !== undefined && isUniversalSkillsRelativeDir(agent.skills.dir))
117
+ .map((agent) => agent.id));
118
+ const installedResult = yield* Effect.result(scoped.skills.installed);
119
+ // The lockfile/readability rules own this failure; avoid turning it into
120
+ // a defect or duplicating a less precise finding here.
121
+ if (Result.isFailure(installedResult))
122
+ return EMPTY_LINT_FINDINGS;
123
+ const installed = installedResult.success;
124
+ const existenceBySkill = installed.flatMap((row) => {
125
+ const implicit = row.installationOrigin._tag === "pack-member";
126
+ if (implicit && Option.isNone(row.resolved)) {
127
+ return [];
128
+ }
129
+ const present = new Set(row.actual.flatMap((actual) => actual.origin._tag === "agent-skill-dir" ? [actual.origin.agentId] : []));
130
+ const collapsed = resolveUniversalDirPresence(declaredAgents.map((agent) => ({ agentId: agent.id, exists: present.has(agent.id) })), universalAgentIds);
131
+ return {
132
+ name: row.key.name,
133
+ enabled: row.activation === "enabled",
134
+ presentAgents: collapsed.filter((p) => p.exists).map((p) => p.agentId),
135
+ missingAgents: collapsed.filter((p) => !p.exists).map((p) => p.agentId),
136
+ implicit,
137
+ };
138
+ });
139
+ const violations = collectArtifactViolations(existenceBySkill, settingsDisplayPath(context.subject.scope));
140
+ return violations.map((violation) => violation.finding);
141
+ }),
142
+ };
143
+ //# sourceMappingURL=skills-artifacts-correct.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * `workspace/skills-declarations-valid` — declared skills name a resolvable,
3
+ * owner-qualified, unique source.
4
+ *
5
+ * Cascade per the lint design (reports the
6
+ * first failing arm per affected entity):
7
+ *
8
+ * 1. Source resolvability — the source string is shaped like a ref we can
9
+ * route (owner-qualified FQN like `@owner/skills/name`, a known source
10
+ * host, `file://` URL, local path). Bare names (`just-a-name`) fail.
11
+ * 2. Owner qualification — registry-shaped sources carry an `@owner`
12
+ * prefix; entries whose source looks registry-ish but omits the owner
13
+ * fail.
14
+ * 3. Duplicate FQNs — when two settings entries normalize to the same
15
+ * owner/type/name FQN, both entries emit a finding.
16
+ *
17
+ * One finding per affected entity (per-entity cascade). Advisory — no lint
18
+ * autofix; resolve invalid or duplicate declarations with install/uninstall
19
+ * commands.
20
+ *
21
+ * @experimental This API is unstable and may change without notice.
22
+ * @packageDocumentation
23
+ */
24
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
25
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
26
+ export declare const skillsDeclarationsValidRule: AdvisoryRule<WorkspaceRuleContext>;
27
+ //# sourceMappingURL=skills-declarations-valid.d.ts.map
@@ -0,0 +1,102 @@
1
+ /**
2
+ * `workspace/skills-declarations-valid` — declared skills name a resolvable,
3
+ * owner-qualified, unique source.
4
+ *
5
+ * Cascade per the lint design (reports the
6
+ * first failing arm per affected entity):
7
+ *
8
+ * 1. Source resolvability — the source string is shaped like a ref we can
9
+ * route (owner-qualified FQN like `@owner/skills/name`, a known source
10
+ * host, `file://` URL, local path). Bare names (`just-a-name`) fail.
11
+ * 2. Owner qualification — registry-shaped sources carry an `@owner`
12
+ * prefix; entries whose source looks registry-ish but omits the owner
13
+ * fail.
14
+ * 3. Duplicate FQNs — when two settings entries normalize to the same
15
+ * owner/type/name FQN, both entries emit a finding.
16
+ *
17
+ * One finding per affected entity (per-entity cascade). Advisory — no lint
18
+ * autofix; resolve invalid or duplicate declarations with install/uninstall
19
+ * commands.
20
+ *
21
+ * @experimental This API is unstable and may change without notice.
22
+ * @packageDocumentation
23
+ */
24
+ import * as Effect from "effect/Effect";
25
+ import * as Option from "effect/Option";
26
+ import * as Result from "effect/Result";
27
+ import { settingsDisplayPath } from "./display-paths.js";
28
+ import { EMPTY_ADVISORY_FINDINGS } from "./helpers/empty.js";
29
+ import { categorizeEntry } from "./helpers/source-categorize.js";
30
+ const RULE_ID = "workspace/skills-declarations-valid";
31
+ const findingForBareName = (entry, settingsPath) => ({
32
+ kind: "advisory",
33
+ ruleId: RULE_ID,
34
+ severity: "error",
35
+ message: `Skill '${entry.name}' uses ambiguous bare source '${entry.source}'.`,
36
+ location: { file: settingsPath },
37
+ });
38
+ const findingForMissingOwner = (entry, settingsPath) => ({
39
+ kind: "advisory",
40
+ ruleId: RULE_ID,
41
+ severity: "error",
42
+ message: `Skill '${entry.name}' source '${entry.source}' is missing its owner-qualified identity.`,
43
+ location: { file: settingsPath },
44
+ });
45
+ const findingForDuplicate = (entry, duplicates, settingsPath) => ({
46
+ kind: "advisory",
47
+ ruleId: RULE_ID,
48
+ severity: "error",
49
+ message: `Skill '${entry.name}' duplicates the same Skill identity as: ${duplicates.join(", ")}.`,
50
+ location: { file: settingsPath },
51
+ });
52
+ export const skillsDeclarationsValidRule = {
53
+ id: RULE_ID,
54
+ description: "Declared skills use unique, resolvable sources.",
55
+ kind: "advisory",
56
+ severity: "error",
57
+ check: (context) => Effect.gen(function* () {
58
+ const scoped = context.workspace;
59
+ const declaredResult = yield* Effect.result(scoped.skills.declared);
60
+ if (Result.isFailure(declaredResult) || Option.isNone(declaredResult.success)) {
61
+ return EMPTY_ADVISORY_FINDINGS;
62
+ }
63
+ const entries = declaredResult.success.value.map(({ name, entry }) => categorizeEntry(name, entry.source));
64
+ // Group by registry FQN for duplicate detection.
65
+ const byFqn = new Map();
66
+ for (const entry of entries) {
67
+ if ((entry.kind !== "registry" && entry.kind !== "workspace") ||
68
+ entry.registryFqn === undefined) {
69
+ continue;
70
+ }
71
+ const group = byFqn.get(entry.registryFqn) ?? [];
72
+ group.push(entry);
73
+ byFqn.set(entry.registryFqn, group);
74
+ }
75
+ const findings = [];
76
+ const settingsPath = settingsDisplayPath(context.subject.scope);
77
+ for (const entry of entries) {
78
+ if (entry.kind === "bare") {
79
+ findings.push(findingForBareName(entry, settingsPath));
80
+ continue;
81
+ }
82
+ if (entry.kind === "registry-no-owner") {
83
+ findings.push(findingForMissingOwner(entry, settingsPath));
84
+ continue;
85
+ }
86
+ if (entry.kind === "non-registry") {
87
+ // Non-registry sources are allowed for skills (local, git-hosted)
88
+ // and don't fire this rule. Move on.
89
+ continue;
90
+ }
91
+ if ((entry.kind === "registry" || entry.kind === "workspace") &&
92
+ entry.registryFqn !== undefined) {
93
+ const group = byFqn.get(entry.registryFqn) ?? [];
94
+ if (group.length > 1) {
95
+ findings.push(findingForDuplicate(entry, group.map((g) => g.name).sort(), settingsPath));
96
+ }
97
+ }
98
+ }
99
+ return findings;
100
+ }),
101
+ };
102
+ //# sourceMappingURL=skills-declarations-valid.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * `workspace/skills-integrity-valid` — each installed skill listed in the
3
+ * lockfile is present on disk.
4
+ *
5
+ * For each accepted skill lock entry, verify that its canonical acquired
6
+ * package is present in the scope's canonical root. Missing-package entries
7
+ * each emit one finding.
8
+ * Configured findings are autofixable via `install-skill` with
9
+ * `force: true`; pack-provided implicit findings are advisory because the
10
+ * repair is a pack-level reinstall.
11
+ *
12
+ * This rule owns only the missing-package arm. Package-tree integrity is
13
+ * evaluated by canonical observations against `treeIntegrity`; local edits,
14
+ * including formatter changes, are drift and block affected reads and
15
+ * mutation closures until explicit recovery.
16
+ *
17
+ * A skill whose `sourceHash` is undefined in the lockfile (e.g., git-hosted
18
+ * sources without pinned hash) is not checked.
19
+ *
20
+ * One finding per affected entity.
21
+ *
22
+ * @experimental This API is unstable and may change without notice.
23
+ * @packageDocumentation
24
+ */
25
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
26
+ import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
27
+ export declare const skillsIntegrityValidRule: AdvisoryRule<WorkspaceRuleContext>;
28
+ //# sourceMappingURL=skills-integrity-valid.d.ts.map