@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.
- package/LICENSE +110 -0
- package/dist/src/catalog/index.d.ts +31 -0
- package/dist/src/catalog/index.js +60 -0
- package/dist/src/catalog/workspace/agents-detected-declared.d.ts +23 -0
- package/dist/src/catalog/workspace/agents-detected-declared.js +61 -0
- package/dist/src/catalog/workspace/agents-recognized.d.ts +23 -0
- package/dist/src/catalog/workspace/agents-recognized.js +64 -0
- package/dist/src/catalog/workspace/axm-skill-compatible.d.ts +4 -0
- package/dist/src/catalog/workspace/axm-skill-compatible.js +51 -0
- package/dist/src/catalog/workspace/configured-but-not-installed.d.ts +4 -0
- package/dist/src/catalog/workspace/configured-but-not-installed.js +88 -0
- package/dist/src/catalog/workspace/desired-state-reconcilable.d.ts +4 -0
- package/dist/src/catalog/workspace/desired-state-reconcilable.js +96 -0
- package/dist/src/catalog/workspace/display-paths.d.ts +7 -0
- package/dist/src/catalog/workspace/display-paths.js +4 -0
- package/dist/src/catalog/workspace/helpers/empty.d.ts +18 -0
- package/dist/src/catalog/workspace/helpers/empty.js +16 -0
- package/dist/src/catalog/workspace/helpers/finding.d.ts +10 -0
- package/dist/src/catalog/workspace/helpers/finding.js +11 -0
- package/dist/src/catalog/workspace/helpers/source-categorize.d.ts +16 -0
- package/dist/src/catalog/workspace/helpers/source-categorize.js +51 -0
- package/dist/src/catalog/workspace/hook-ownership-ambiguous.d.ts +4 -0
- package/dist/src/catalog/workspace/hook-ownership-ambiguous.js +25 -0
- package/dist/src/catalog/workspace/initialized.d.ts +22 -0
- package/dist/src/catalog/workspace/initialized.js +58 -0
- package/dist/src/catalog/workspace/instructions-agent-supported.d.ts +4 -0
- package/dist/src/catalog/workspace/instructions-agent-supported.js +38 -0
- package/dist/src/catalog/workspace/instructions-gitignore-current.d.ts +4 -0
- package/dist/src/catalog/workspace/instructions-gitignore-current.js +49 -0
- package/dist/src/catalog/workspace/instructions-source-present.d.ts +4 -0
- package/dist/src/catalog/workspace/instructions-source-present.js +41 -0
- package/dist/src/catalog/workspace/instructions-target-current.d.ts +4 -0
- package/dist/src/catalog/workspace/instructions-target-current.js +51 -0
- package/dist/src/catalog/workspace/instructions-target-stale.d.ts +9 -0
- package/dist/src/catalog/workspace/instructions-target-stale.js +41 -0
- package/dist/src/catalog/workspace/instructions-target-unowned.d.ts +10 -0
- package/dist/src/catalog/workspace/instructions-target-unowned.js +59 -0
- package/dist/src/catalog/workspace/knowledge-state-valid.d.ts +5 -0
- package/dist/src/catalog/workspace/knowledge-state-valid.js +65 -0
- package/dist/src/catalog/workspace/lockfile-valid.d.ts +5 -0
- package/dist/src/catalog/workspace/lockfile-valid.js +63 -0
- package/dist/src/catalog/workspace/managed-file-unowned.d.ts +4 -0
- package/dist/src/catalog/workspace/managed-file-unowned.js +25 -0
- package/dist/src/catalog/workspace/mcps-agent-drift.d.ts +4 -0
- package/dist/src/catalog/workspace/mcps-agent-drift.js +139 -0
- package/dist/src/catalog/workspace/mcps-agent-orphaned.d.ts +4 -0
- package/dist/src/catalog/workspace/mcps-agent-orphaned.js +64 -0
- package/dist/src/catalog/workspace/mcps-no-secret-literal.d.ts +4 -0
- package/dist/src/catalog/workspace/mcps-no-secret-literal.js +67 -0
- package/dist/src/catalog/workspace/mcps-shared-target-compatible.d.ts +4 -0
- package/dist/src/catalog/workspace/mcps-shared-target-compatible.js +58 -0
- package/dist/src/catalog/workspace/mcps-transport-exclusivity.d.ts +4 -0
- package/dist/src/catalog/workspace/mcps-transport-exclusivity.js +67 -0
- package/dist/src/catalog/workspace/packs-declarations-valid.d.ts +19 -0
- package/dist/src/catalog/workspace/packs-declarations-valid.js +110 -0
- package/dist/src/catalog/workspace/packs-dependencies-resolved.d.ts +5 -0
- package/dist/src/catalog/workspace/packs-dependencies-resolved.js +49 -0
- package/dist/src/catalog/workspace/projections-current.d.ts +15 -0
- package/dist/src/catalog/workspace/projections-current.js +35 -0
- package/dist/src/catalog/workspace/settings-keys-recognized.d.ts +17 -0
- package/dist/src/catalog/workspace/settings-keys-recognized.js +90 -0
- package/dist/src/catalog/workspace/settings-schema-valid.d.ts +27 -0
- package/dist/src/catalog/workspace/settings-schema-valid.js +68 -0
- package/dist/src/catalog/workspace/skills-artifacts-correct.d.ts +28 -0
- package/dist/src/catalog/workspace/skills-artifacts-correct.js +143 -0
- package/dist/src/catalog/workspace/skills-declarations-valid.d.ts +27 -0
- package/dist/src/catalog/workspace/skills-declarations-valid.js +102 -0
- package/dist/src/catalog/workspace/skills-integrity-valid.d.ts +28 -0
- package/dist/src/catalog/workspace/skills-integrity-valid.js +95 -0
- package/dist/src/catalog/workspace/skills-lockfile-aligned.d.ts +5 -0
- package/dist/src/catalog/workspace/skills-lockfile-aligned.js +64 -0
- package/dist/src/catalog/workspace/source-endpoints-aligned.d.ts +5 -0
- package/dist/src/catalog/workspace/source-endpoints-aligned.js +75 -0
- package/dist/src/catalog/workspace-fixtures/fixture-state.d.ts +8 -0
- package/dist/src/catalog/workspace-fixtures/fixture-state.js +120 -0
- package/dist/src/catalog/workspace-fixtures/interpret-ops.d.ts +11 -0
- package/dist/src/catalog/workspace-fixtures/interpret-ops.js +9 -0
- package/dist/src/catalog/workspace-read-model/lint-workspace.d.ts +191 -0
- package/dist/src/catalog/workspace-read-model/lint-workspace.js +651 -0
- package/dist/src/catalog/workspace-read-model/manifest-json.d.ts +14 -0
- package/dist/src/catalog/workspace-read-model/manifest-json.js +27 -0
- package/dist/src/catalog/workspace.d.ts +58 -0
- package/dist/src/catalog/workspace.js +164 -0
- package/dist/src/catalog-contexts.d.ts +106 -0
- package/dist/src/catalog-contexts.js +95 -0
- package/dist/src/cli.d.ts +239 -0
- package/dist/src/cli.js +1077 -0
- package/dist/src/index.d.ts +10 -0
- package/dist/src/index.js +21 -0
- package/dist/src/json-schema.d.ts +121 -0
- package/dist/src/json-schema.js +99 -0
- package/dist/src/run/errors.d.ts +23 -0
- package/dist/src/run/errors.js +21 -0
- package/dist/src/run/settings.d.ts +63 -0
- package/dist/src/run/settings.js +139 -0
- package/dist/src/run/staged-workspace.d.ts +31 -0
- package/dist/src/run/staged-workspace.js +273 -0
- package/dist/src/workspace-context.d.ts +129 -0
- package/dist/src/workspace-context.js +12 -0
- package/package.json +55 -0
|
@@ -0,0 +1,95 @@
|
|
|
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 * as Effect from "effect/Effect";
|
|
26
|
+
import * as Option from "effect/Option";
|
|
27
|
+
import * as Result from "effect/Result";
|
|
28
|
+
import {} from "@agentxm/workspace-state";
|
|
29
|
+
import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
|
|
30
|
+
import { lockfileDisplayPath } from "./display-paths.js";
|
|
31
|
+
const RULE_ID = "workspace/skills-integrity-valid";
|
|
32
|
+
const simpleName = (name) => name.split("/").at(-1) ?? name;
|
|
33
|
+
const hasSourceActual = (actual, name) => actual.some((occurrence) => occurrence.key.name === simpleName(name) &&
|
|
34
|
+
(occurrence.origin._tag === "canonical-axm-skill" ||
|
|
35
|
+
occurrence.origin._tag === "external-axm-skill"));
|
|
36
|
+
const integrityFinding = (name, reason, lockfilePath) => ({
|
|
37
|
+
kind: "advisory",
|
|
38
|
+
ruleId: RULE_ID,
|
|
39
|
+
severity: "error",
|
|
40
|
+
message: `Skill '${name}' has an accepted resolution, but ${reason}.`,
|
|
41
|
+
location: { file: lockfilePath },
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Presence probe for the skill's acquired package tree. Integrity drift is a
|
|
45
|
+
* separate canonical-observation fact, so this helper owns only absence.
|
|
46
|
+
*/
|
|
47
|
+
const integrityReason = (lockEntry, probeExists) => {
|
|
48
|
+
if (!probeExists) {
|
|
49
|
+
return Option.some("its installed source directory is missing");
|
|
50
|
+
}
|
|
51
|
+
return Option.none();
|
|
52
|
+
};
|
|
53
|
+
const collectIntegrityFindings = (desiredNames, probes, lockfilePath) => {
|
|
54
|
+
const findings = [];
|
|
55
|
+
for (const { name, entry, exists } of probes) {
|
|
56
|
+
if (!desiredNames.has(name))
|
|
57
|
+
continue;
|
|
58
|
+
const reason = integrityReason(entry, exists);
|
|
59
|
+
if (Option.isNone(reason)) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
findings.push(integrityFinding(name, reason.value, lockfilePath));
|
|
63
|
+
}
|
|
64
|
+
return findings;
|
|
65
|
+
};
|
|
66
|
+
export const skillsIntegrityValidRule = {
|
|
67
|
+
id: RULE_ID,
|
|
68
|
+
description: "Installed skills listed in the lockfile are present on disk.",
|
|
69
|
+
kind: "advisory",
|
|
70
|
+
severity: "error",
|
|
71
|
+
check: (context) => Effect.gen(function* () {
|
|
72
|
+
const scoped = context.workspace;
|
|
73
|
+
const lockfileResult = yield* Effect.result(scoped.state.lockfile);
|
|
74
|
+
if (Result.isFailure(lockfileResult) || context.health === undefined) {
|
|
75
|
+
return EMPTY_LINT_FINDINGS;
|
|
76
|
+
}
|
|
77
|
+
const graphResult = yield* Effect.result(context.health.desiredState);
|
|
78
|
+
if (Result.isFailure(graphResult)) {
|
|
79
|
+
return EMPTY_LINT_FINDINGS;
|
|
80
|
+
}
|
|
81
|
+
const lockOption = lockfileResult.success;
|
|
82
|
+
if (Option.isNone(lockOption)) {
|
|
83
|
+
return EMPTY_LINT_FINDINGS;
|
|
84
|
+
}
|
|
85
|
+
const actual = yield* scoped.skills.actual;
|
|
86
|
+
const probes = Object.entries(lockOption.value.skills).map(([name, entry]) => ({
|
|
87
|
+
name,
|
|
88
|
+
entry,
|
|
89
|
+
exists: hasSourceActual(actual, name),
|
|
90
|
+
}));
|
|
91
|
+
const desiredNames = new Set(graphResult.success.nodes.filter((node) => node.type === "skill").map((node) => node.name));
|
|
92
|
+
return collectIntegrityFindings(desiredNames, probes, lockfileDisplayPath(context.subject.scope));
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
//# sourceMappingURL=skills-integrity-valid.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Reports alignment between desired external Skills and accepted resolutions. */
|
|
2
|
+
import type { WorkspaceRuleContext } from "../../workspace-context.js";
|
|
3
|
+
import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
|
|
4
|
+
export declare const skillsLockfileAlignedRule: AdvisoryRule<WorkspaceRuleContext>;
|
|
5
|
+
//# sourceMappingURL=skills-lockfile-aligned.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** Reports alignment between desired external Skills and accepted resolutions. */
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Result from "effect/Result";
|
|
5
|
+
import { parseRegistrySourceRef } from "@agentxm/extension-model/unstable/extensions/registry-source";
|
|
6
|
+
import {} from "@agentxm/workspace-state";
|
|
7
|
+
import { isWorkspaceSourceLocator } from "@agentxm/extension-model/unstable/sources/workspace";
|
|
8
|
+
import { versionSatisfiesRange } from "@agentxm/extension-model/unstable/version-constraints";
|
|
9
|
+
import { lockfileDisplayPath } from "./display-paths.js";
|
|
10
|
+
const RULE_ID = "workspace/skills-lockfile-aligned";
|
|
11
|
+
const finding = (message, path) => ({
|
|
12
|
+
kind: "advisory",
|
|
13
|
+
ruleId: RULE_ID,
|
|
14
|
+
severity: "error",
|
|
15
|
+
message,
|
|
16
|
+
location: { file: path },
|
|
17
|
+
});
|
|
18
|
+
const externalSkills = (nodes) => nodes.filter((node) => node.type === "skill" && node.source !== undefined && !isWorkspaceSourceLocator(node.source));
|
|
19
|
+
const collectFindings = (nodes, lockfile, lockfilePath) => {
|
|
20
|
+
const desired = externalSkills(nodes);
|
|
21
|
+
const desiredNames = new Set(desired.map((node) => node.name));
|
|
22
|
+
const findings = [];
|
|
23
|
+
for (const node of desired) {
|
|
24
|
+
if (node.source === undefined)
|
|
25
|
+
continue;
|
|
26
|
+
const accepted = lockfile.skills[node.name];
|
|
27
|
+
if (accepted === undefined) {
|
|
28
|
+
findings.push(finding(`Skill '${node.name}' has desired external content but no accepted resolution.`, lockfilePath));
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (accepted.type !== "registry")
|
|
32
|
+
continue;
|
|
33
|
+
const parsed = parseRegistrySourceRef(node.source);
|
|
34
|
+
const constraint = parsed?.versionRange;
|
|
35
|
+
if (constraint !== undefined && !versionSatisfiesRange(accepted.resolvedVersion, constraint)) {
|
|
36
|
+
findings.push(finding(`Skill '${node.name}' accepts Registry version ${accepted.resolvedVersion}, which does not satisfy desired constraint ${constraint}.`, lockfilePath));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const name of Object.keys(lockfile.skills)) {
|
|
40
|
+
if (!desiredNames.has(name)) {
|
|
41
|
+
findings.push(finding(`Skill '${name}' has an accepted resolution but is not desired.`, lockfilePath));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return findings;
|
|
45
|
+
};
|
|
46
|
+
export const skillsLockfileAlignedRule = {
|
|
47
|
+
id: RULE_ID,
|
|
48
|
+
description: "Desired external Skills and accepted resolutions stay aligned.",
|
|
49
|
+
kind: "advisory",
|
|
50
|
+
severity: "error",
|
|
51
|
+
check: (context) => Effect.gen(function* () {
|
|
52
|
+
if (context.health === undefined)
|
|
53
|
+
return [];
|
|
54
|
+
const graph = yield* Effect.result(context.health.desiredState);
|
|
55
|
+
const lockfile = yield* Effect.result(context.workspace.state.lockfile);
|
|
56
|
+
if (Result.isFailure(graph) ||
|
|
57
|
+
Result.isFailure(lockfile) ||
|
|
58
|
+
Option.isNone(lockfile.success)) {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
return collectFindings(graph.success.nodes, lockfile.success.value, lockfileDisplayPath(context.subject.scope));
|
|
62
|
+
}),
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=skills-lockfile-aligned.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Reports configured source hosts that diverge from accepted lock authority. */
|
|
2
|
+
import type { WorkspaceRuleContext } from "../../workspace-context.js";
|
|
3
|
+
import type { AdvisoryRule } from "@agentxm/registry-protocol/unstable/lint/rule";
|
|
4
|
+
export declare const sourceEndpointsAlignedRule: AdvisoryRule<WorkspaceRuleContext>;
|
|
5
|
+
//# sourceMappingURL=source-endpoints-aligned.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** Reports configured source hosts that diverge from accepted lock authority. */
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Option from "effect/Option";
|
|
4
|
+
import * as Result from "effect/Result";
|
|
5
|
+
import { settingsDisplayPath } from "./display-paths.js";
|
|
6
|
+
const RULE_ID = "workspace/source-endpoints-aligned";
|
|
7
|
+
const configuredEndpoint = (source) => source.type === "registry" ? source.location : source.url;
|
|
8
|
+
const lockedEntries = (lockfile) => [
|
|
9
|
+
...Object.entries(lockfile.skills).map(([name, entry]) => ({
|
|
10
|
+
extension: `skill:${name}`,
|
|
11
|
+
entry,
|
|
12
|
+
})),
|
|
13
|
+
...Object.entries(lockfile.mcpServers ?? {}).map(([name, entry]) => ({
|
|
14
|
+
extension: `mcp-server:${name}`,
|
|
15
|
+
entry,
|
|
16
|
+
})),
|
|
17
|
+
...Object.entries(lockfile.subagents ?? {}).map(([name, entry]) => ({
|
|
18
|
+
extension: `subagent:${name}`,
|
|
19
|
+
entry,
|
|
20
|
+
})),
|
|
21
|
+
...Object.entries(lockfile.rules ?? {}).map(([name, entry]) => ({
|
|
22
|
+
extension: `rule:${name}`,
|
|
23
|
+
entry,
|
|
24
|
+
})),
|
|
25
|
+
...Object.entries(lockfile.hooks ?? {}).map(([name, entry]) => ({
|
|
26
|
+
extension: `hook:${name}`,
|
|
27
|
+
entry,
|
|
28
|
+
})),
|
|
29
|
+
...Object.entries(lockfile.knowledge ?? {}).map(([name, entry]) => ({
|
|
30
|
+
extension: `knowledge:${name}`,
|
|
31
|
+
entry,
|
|
32
|
+
})),
|
|
33
|
+
...Object.entries(lockfile.packs ?? {}).map(([name, entry]) => ({
|
|
34
|
+
extension: `pack:${name}`,
|
|
35
|
+
entry,
|
|
36
|
+
})),
|
|
37
|
+
];
|
|
38
|
+
const collectFindings = (lockfile, configuredSources, settingsPath) => {
|
|
39
|
+
const findings = [];
|
|
40
|
+
for (const { extension, entry } of lockedEntries(lockfile)) {
|
|
41
|
+
if (entry.type === "local" || entry.type === "git")
|
|
42
|
+
continue;
|
|
43
|
+
const configured = configuredSources.find((source) => source.name === entry.sourceName);
|
|
44
|
+
if (configured === undefined)
|
|
45
|
+
continue;
|
|
46
|
+
const endpoint = configuredEndpoint(configured);
|
|
47
|
+
if (configured.type === entry.sourceType && endpoint.href === entry.endpoint.href)
|
|
48
|
+
continue;
|
|
49
|
+
findings.push({
|
|
50
|
+
kind: "advisory",
|
|
51
|
+
ruleId: RULE_ID,
|
|
52
|
+
severity: "error",
|
|
53
|
+
message: `Accepted resolution '${extension}' binds source '${entry.sourceName}' to ${entry.sourceType} ${entry.endpoint.href}, but the configured source now resolves to ${configured.type} ${endpoint.href}. Use an explicit source transition before syncing.`,
|
|
54
|
+
location: { file: settingsPath },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return findings;
|
|
58
|
+
};
|
|
59
|
+
export const sourceEndpointsAlignedRule = {
|
|
60
|
+
id: RULE_ID,
|
|
61
|
+
description: "Configured source endpoints remain aligned with accepted lock authority.",
|
|
62
|
+
kind: "advisory",
|
|
63
|
+
severity: "error",
|
|
64
|
+
check: (context) => Effect.gen(function* () {
|
|
65
|
+
const lockfile = yield* Effect.result(context.workspace.state.lockfile);
|
|
66
|
+
const sources = yield* Effect.result(context.workspace.sourceHosts.declared);
|
|
67
|
+
if (Result.isFailure(lockfile) ||
|
|
68
|
+
Option.isNone(lockfile.success) ||
|
|
69
|
+
Result.isFailure(sources)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
return collectFindings(lockfile.success.value, sources.success, settingsDisplayPath(context.subject.scope));
|
|
73
|
+
}),
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=source-endpoints-aligned.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FixtureSpec, ScopeFiles } from "@agentxm/workspace-state";
|
|
2
|
+
import type { WorkspaceState } from "./interpret-ops.js";
|
|
3
|
+
export declare const scopeFilesFromWorkspaceState: (state: WorkspaceState) => ScopeFiles;
|
|
4
|
+
export declare const fixtureSpecFromWorkspaceState: (state: WorkspaceState, scope: "project" | "user", roots: {
|
|
5
|
+
readonly workspaceRoot: string;
|
|
6
|
+
readonly userHome: string;
|
|
7
|
+
}) => FixtureSpec;
|
|
8
|
+
//# sourceMappingURL=fixture-state.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
|
|
2
|
+
const pathSegments = (relativePath) => relativePath.split("/").filter((segment) => segment.length > 0);
|
|
3
|
+
const firstPathSegment = (relativePath) => pathSegments(relativePath)[0];
|
|
4
|
+
const agentRootForSkillsDir = (skillsDir) => firstPathSegment(skillsDir);
|
|
5
|
+
const relativeUnderAgentRoot = (agentRoot, relativePath) => {
|
|
6
|
+
if (relativePath === agentRoot)
|
|
7
|
+
return "";
|
|
8
|
+
const prefix = `${agentRoot}/`;
|
|
9
|
+
if (!relativePath.startsWith(prefix))
|
|
10
|
+
return undefined;
|
|
11
|
+
return relativePath.slice(prefix.length);
|
|
12
|
+
};
|
|
13
|
+
const skillNameFromPath = (skillsDir, relativePath) => {
|
|
14
|
+
const prefix = `${skillsDir}/`;
|
|
15
|
+
if (!relativePath.startsWith(prefix))
|
|
16
|
+
return undefined;
|
|
17
|
+
const afterSkillsDir = relativePath.slice(prefix.length);
|
|
18
|
+
return firstPathSegment(afterSkillsDir);
|
|
19
|
+
};
|
|
20
|
+
const literalFileFor = (relativePath) => {
|
|
21
|
+
const segments = pathSegments(relativePath);
|
|
22
|
+
const name = segments.at(-2) ?? segments.at(-1) ?? "fixture";
|
|
23
|
+
const lower = relativePath.toLowerCase();
|
|
24
|
+
if (lower.endsWith(".md"))
|
|
25
|
+
return `# ${name}\n`;
|
|
26
|
+
if (lower.endsWith(".json"))
|
|
27
|
+
return "{}\n";
|
|
28
|
+
if (lower.endsWith(".yaml") || lower.endsWith(".yml"))
|
|
29
|
+
return "{}\n";
|
|
30
|
+
return "";
|
|
31
|
+
};
|
|
32
|
+
const addTreeFile = (tree, relativePath, contents = literalFileFor(relativePath)) => {
|
|
33
|
+
tree[relativePath] = contents;
|
|
34
|
+
};
|
|
35
|
+
const addAgentSkillArtifact = (agentDirs, agentId, relativePath) => {
|
|
36
|
+
const agent = AGENTS[agentId];
|
|
37
|
+
const skills = agent.skills;
|
|
38
|
+
if (skills === undefined)
|
|
39
|
+
return;
|
|
40
|
+
const agentRoot = agentRootForSkillsDir(skills.dir);
|
|
41
|
+
if (agentRoot === undefined)
|
|
42
|
+
return;
|
|
43
|
+
const relative = relativeUnderAgentRoot(agentRoot, relativePath);
|
|
44
|
+
if (relative === undefined)
|
|
45
|
+
return;
|
|
46
|
+
const skillName = skillNameFromPath(skills.dir, relativePath);
|
|
47
|
+
const materializedPath = skillName === undefined
|
|
48
|
+
? relative
|
|
49
|
+
: `${skills.dir.slice(agentRoot.length + 1)}/${skillName}/SKILL.md`;
|
|
50
|
+
if (materializedPath.length === 0)
|
|
51
|
+
return;
|
|
52
|
+
const tree = agentDirs[agentId] ?? {};
|
|
53
|
+
agentDirs[agentId] = tree;
|
|
54
|
+
addTreeFile(tree, materializedPath);
|
|
55
|
+
};
|
|
56
|
+
const isKnownAgentId = (id) => Object.hasOwn(AGENTS, id);
|
|
57
|
+
const fileSpecFor = (raw) => {
|
|
58
|
+
if (raw === undefined)
|
|
59
|
+
return undefined;
|
|
60
|
+
if (typeof raw !== "object" || raw === null) {
|
|
61
|
+
throw new Error(`Fixture state expected an object for settings/lockfile; got ${typeof raw}`);
|
|
62
|
+
}
|
|
63
|
+
return { _tag: "valid", contents: raw };
|
|
64
|
+
};
|
|
65
|
+
export const scopeFilesFromWorkspaceState = (state) => {
|
|
66
|
+
const axmExtensions = {};
|
|
67
|
+
const agentDirs = {};
|
|
68
|
+
const agentSettings = {};
|
|
69
|
+
const agents = Object.values(AGENTS);
|
|
70
|
+
const addExistingPath = (relativePath) => {
|
|
71
|
+
const acquiredPrefix = "agent_extensions/";
|
|
72
|
+
if (relativePath.startsWith(acquiredPrefix)) {
|
|
73
|
+
addTreeFile(axmExtensions, relativePath.slice(acquiredPrefix.length));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const axmPrefix = "agent_extensions/";
|
|
77
|
+
if (relativePath.startsWith(axmPrefix)) {
|
|
78
|
+
addTreeFile(axmExtensions, relativePath.slice(axmPrefix.length));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const agent of agents) {
|
|
82
|
+
if (agent.skills !== undefined &&
|
|
83
|
+
skillNameFromPath(agent.skills.dir, relativePath) !== undefined) {
|
|
84
|
+
addAgentSkillArtifact(agentDirs, agent.id, relativePath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
for (const relativePath of state.existingPaths) {
|
|
89
|
+
addExistingPath(relativePath);
|
|
90
|
+
}
|
|
91
|
+
for (const [dir, entries] of state.listings) {
|
|
92
|
+
for (const entry of entries) {
|
|
93
|
+
addExistingPath(`${dir}/${entry}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const agentId of state.detectedProjectAgents) {
|
|
97
|
+
if (isKnownAgentId(agentId)) {
|
|
98
|
+
agentSettings[agentId] = { _tag: "valid", contents: {} };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const settings = fileSpecFor(state.settings);
|
|
102
|
+
const lockfile = fileSpecFor(state.lockfile);
|
|
103
|
+
return {
|
|
104
|
+
...(settings === undefined ? {} : { settings }),
|
|
105
|
+
...(lockfile === undefined ? {} : { lockfile }),
|
|
106
|
+
...(Object.keys(axmExtensions).length === 0 ? {} : { axmExtensions }),
|
|
107
|
+
...(Object.keys(agentDirs).length === 0 ? {} : { agentDirs }),
|
|
108
|
+
...(Object.keys(agentSettings).length === 0 ? {} : { agentSettings }),
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
export const fixtureSpecFromWorkspaceState = (state, scope, roots) => {
|
|
112
|
+
const scopeFiles = scopeFilesFromWorkspaceState(state);
|
|
113
|
+
return {
|
|
114
|
+
workspaceRoot: roots.workspaceRoot,
|
|
115
|
+
userHome: roots.userHome,
|
|
116
|
+
...(scope === "project" ? { project: scopeFiles } : {}),
|
|
117
|
+
...(scope === "user" ? { user: scopeFiles } : {}),
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=fixture-state.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Mutable workspace-state fixture shared by read-only lint rule tests. */
|
|
2
|
+
export interface WorkspaceState {
|
|
3
|
+
settings: unknown;
|
|
4
|
+
lockfile: unknown;
|
|
5
|
+
readonly existingPaths: Set<string>;
|
|
6
|
+
readonly writablePaths: Set<string>;
|
|
7
|
+
readonly listings: Map<string, Array<string>>;
|
|
8
|
+
readonly detectedProjectAgents: Set<string>;
|
|
9
|
+
}
|
|
10
|
+
export declare const emptyWorkspaceState: () => WorkspaceState;
|
|
11
|
+
//# sourceMappingURL=interpret-ops.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LintWorkspace` — single helper that produces both the per-rule
|
|
3
|
+
* `WorkspaceRuleContext` and the flat `LintWorkspaceView` projection a lint
|
|
4
|
+
* run needs, sharing one `WorkspaceReadModelLive` setup.
|
|
5
|
+
*
|
|
6
|
+
* `LintWorkspaceView` exposes `installedSkills: InstalledSkillInfo[]` and
|
|
7
|
+
* `installedPacks: InstalledPackInfo[]` with per-provenance `displayRoot`s:
|
|
8
|
+
*
|
|
9
|
+
* Registry-installed native skill: `agent_extensions/<source>/<@owner>/skills/<name>/src/`
|
|
10
|
+
* Portable acquired skill: `agent_extensions/<source>/<source-full-name>/`
|
|
11
|
+
* Registry pack: `agent_extensions/<source>/<@owner>/packs/<name>/`
|
|
12
|
+
* (NO `src/` — matches the on-disk layout.)
|
|
13
|
+
*
|
|
14
|
+
* The companion `WorkspaceRuleContext` is scoped to project or user.
|
|
15
|
+
* User-scope resolution uses `.axm/workspace/` under `$AXM_USER_HOME` when set,
|
|
16
|
+
* otherwise under `$HOME`; a follow-up owns the broader XDG story.
|
|
17
|
+
*
|
|
18
|
+
* @experimental This API is unstable and may change without notice.
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
import * as Effect from "effect/Effect";
|
|
22
|
+
import * as FileSystem from "effect/FileSystem";
|
|
23
|
+
import * as Option from "effect/Option";
|
|
24
|
+
import * as Path from "effect/Path";
|
|
25
|
+
import type { LockfileReadError, SettingsReadError, WorkspaceRootEscape } from "@agentxm/workspace-state";
|
|
26
|
+
import type { HookRuleContext, KnowledgeRuleContext, McpServerRuleContext, RuleRuleContext, SubagentRuleContext } from "@agentxm/registry-protocol/unstable/lint/context";
|
|
27
|
+
import type { WorkspaceProjectionsAccessor, WorkspaceRuleContext } from "../../workspace-context.js";
|
|
28
|
+
import type { InstalledSkillInfo } from "@agentxm/registry-protocol/unstable/lint/catalog/skill-accessor/contexts";
|
|
29
|
+
import type { InstalledPackInfo } from "@agentxm/registry-protocol/unstable/lint/catalog/pack-accessor/contexts";
|
|
30
|
+
import type { SkillLockEntry } from "@agentxm/workspace-state";
|
|
31
|
+
import type { KnowledgeInspection } from "@agentxm/registry-protocol/unstable/knowledge/okf";
|
|
32
|
+
import type { AxmSkillCompatibilityPolicyService } from "@agentxm/extension-workspace";
|
|
33
|
+
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
34
|
+
/**
|
|
35
|
+
* Flat projection of the workspace read model that the lint accessor builders
|
|
36
|
+
* consume. `buildSkillRuleContexts` reads `installedSkills`;
|
|
37
|
+
* `buildPackRuleContexts` reads `installedPacks`.
|
|
38
|
+
*
|
|
39
|
+
* @experimental This API is unstable and may change without notice.
|
|
40
|
+
*/
|
|
41
|
+
export interface LintWorkspaceView {
|
|
42
|
+
readonly installedSkills: ReadonlyArray<InstalledSkillInfo>;
|
|
43
|
+
readonly installedPacks: ReadonlyArray<InstalledPackInfo>;
|
|
44
|
+
readonly subagentContexts: ReadonlyArray<SubagentRuleContext>;
|
|
45
|
+
readonly mcpServerContexts: ReadonlyArray<McpServerRuleContext>;
|
|
46
|
+
readonly hookContexts: ReadonlyArray<HookRuleContext>;
|
|
47
|
+
readonly ruleContexts: ReadonlyArray<RuleRuleContext>;
|
|
48
|
+
readonly knowledgeContexts: ReadonlyArray<KnowledgeRuleContext>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Argument shape for `buildLintWorkspace`.
|
|
52
|
+
*
|
|
53
|
+
* @experimental This API is unstable and may change without notice.
|
|
54
|
+
*/
|
|
55
|
+
export interface BuildLintWorkspaceArgs {
|
|
56
|
+
readonly platform: {
|
|
57
|
+
readonly fs: FileSystem.FileSystem;
|
|
58
|
+
readonly path: Path.Path;
|
|
59
|
+
};
|
|
60
|
+
readonly workspaceRoot: string;
|
|
61
|
+
/**
|
|
62
|
+
* User home directory used by {@link makeWorkspaceReadModel} to validate
|
|
63
|
+
* the alternate scope's root against `allowedRoot`. Supplied even when
|
|
64
|
+
* only one scope is queried so root-escape checks stay symmetric.
|
|
65
|
+
*/
|
|
66
|
+
readonly userHome: string;
|
|
67
|
+
readonly scope: "project" | "user";
|
|
68
|
+
/** The filesystem is a materialized Git-index snapshot, not the live workspace. */
|
|
69
|
+
readonly gitIndexView?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Optional `displayRoot` override for the `WorkspaceRuleContext`. Defaults
|
|
72
|
+
* to `""` (accessor-relative paths render under the workspace root).
|
|
73
|
+
*/
|
|
74
|
+
readonly displayRoot?: string;
|
|
75
|
+
/** Runtime-pinned evaluator used by status and the compatibility lint rule. */
|
|
76
|
+
readonly axmSkillCompatibilityPolicy?: AxmSkillCompatibilityPolicyService;
|
|
77
|
+
/** Caller-bound effective workspace owner accessor. */
|
|
78
|
+
readonly owner?: Effect.Effect<Option.Option<Handle>>;
|
|
79
|
+
/** Caller-bound read-back currency for aggregate managed output units. */
|
|
80
|
+
readonly projections?: WorkspaceProjectionsAccessor;
|
|
81
|
+
/** Test seam for proving one package inspection per selected bundle. */
|
|
82
|
+
readonly inspectKnowledge?: (packageRoot: string) => Effect.Effect<{
|
|
83
|
+
readonly inspection: KnowledgeInspection;
|
|
84
|
+
}, unknown, FileSystem.FileSystem | Path.Path>;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The combined output of `buildLintWorkspace`: the per-rule
|
|
88
|
+
* `WorkspaceRuleContext` plus the flat `LintWorkspaceView`.
|
|
89
|
+
*
|
|
90
|
+
* @experimental This API is unstable and may change without notice.
|
|
91
|
+
*/
|
|
92
|
+
export interface LintWorkspace {
|
|
93
|
+
readonly rule: WorkspaceRuleContext;
|
|
94
|
+
readonly view: LintWorkspaceView;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build the per-rule `WorkspaceRuleContext` and the flat `LintWorkspaceView`
|
|
98
|
+
* via a single {@link makeWorkspaceReadModel} invocation.
|
|
99
|
+
*
|
|
100
|
+
* `WorkspaceRootEscape` is surfaced in the error channel by the factory when
|
|
101
|
+
* `workspaceRoot` or `userHome` escape the filesystem root.
|
|
102
|
+
*
|
|
103
|
+
* @experimental This API is unstable and may change without notice.
|
|
104
|
+
*/
|
|
105
|
+
export declare const buildLintWorkspace: (args: BuildLintWorkspaceArgs) => Effect.Effect<LintWorkspace, WorkspaceRootEscape | SettingsReadError | LockfileReadError>;
|
|
106
|
+
/**
|
|
107
|
+
* Compute the `displayRoot` for a registry-installed native skill.
|
|
108
|
+
*
|
|
109
|
+
* @experimental This API is unstable and may change without notice.
|
|
110
|
+
*/
|
|
111
|
+
export declare const registryNativeSkillDisplayRoot: (scope: "project" | "user", sourceName: string, owner: string, name: string) => string;
|
|
112
|
+
/**
|
|
113
|
+
* Compute the content `displayRoot` for a source-qualified acquired skill.
|
|
114
|
+
*
|
|
115
|
+
* @experimental This API is unstable and may change without notice.
|
|
116
|
+
*/
|
|
117
|
+
export declare const acquiredSkillDisplayRoot: (scope: "project" | "user", entry: SkillLockEntry, name: string) => string;
|
|
118
|
+
/**
|
|
119
|
+
* Compute the `displayRoot` for a registry-installed pack.
|
|
120
|
+
*
|
|
121
|
+
* **No `src/` segment** — matches the on-disk layout at
|
|
122
|
+
* `packages/workspace-state/src/workspace/pack-paths.ts#computePackPathsForLayout`.
|
|
123
|
+
*
|
|
124
|
+
* @experimental This API is unstable and may change without notice.
|
|
125
|
+
*/
|
|
126
|
+
export declare const registryPackDisplayRoot: (scope: "project" | "user", sourceName: string, owner: string, name: string) => string;
|
|
127
|
+
/**
|
|
128
|
+
* Options for building an `InstalledSkillInfo` for a registry-installed
|
|
129
|
+
* native skill. `owner` and `name` pin the on-disk layout.
|
|
130
|
+
*/
|
|
131
|
+
export interface BuildInstalledSkillInfoNativeArgs {
|
|
132
|
+
readonly platform: {
|
|
133
|
+
readonly fs: FileSystem.FileSystem;
|
|
134
|
+
readonly path: Path.Path;
|
|
135
|
+
};
|
|
136
|
+
readonly workspaceRoot: string;
|
|
137
|
+
readonly scope: "project" | "user";
|
|
138
|
+
readonly sourceName: string;
|
|
139
|
+
readonly owner: string;
|
|
140
|
+
readonly name: string;
|
|
141
|
+
readonly skillJson: unknown;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build an `InstalledSkillInfo` rooted at `agent_extensions/<owner>/skills/<name>/src/`.
|
|
145
|
+
*
|
|
146
|
+
* @experimental This API is unstable and may change without notice.
|
|
147
|
+
*/
|
|
148
|
+
export declare const buildNativeInstalledSkillInfo: (args: BuildInstalledSkillInfoNativeArgs) => InstalledSkillInfo;
|
|
149
|
+
/**
|
|
150
|
+
* Options for building an `InstalledSkillInfo` from an accepted acquired skill.
|
|
151
|
+
*/
|
|
152
|
+
export interface BuildAcquiredInstalledSkillInfoArgs {
|
|
153
|
+
readonly platform: {
|
|
154
|
+
readonly fs: FileSystem.FileSystem;
|
|
155
|
+
readonly path: Path.Path;
|
|
156
|
+
};
|
|
157
|
+
readonly workspaceRoot: string;
|
|
158
|
+
readonly scope: "project" | "user";
|
|
159
|
+
readonly lockEntry: SkillLockEntry;
|
|
160
|
+
readonly name: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Build an `InstalledSkillInfo` rooted at its exact accepted source-qualified path.
|
|
164
|
+
*
|
|
165
|
+
* @experimental This API is unstable and may change without notice.
|
|
166
|
+
*/
|
|
167
|
+
export declare const buildAcquiredInstalledSkillInfo: (args: BuildAcquiredInstalledSkillInfoArgs) => InstalledSkillInfo;
|
|
168
|
+
/**
|
|
169
|
+
* Options for building an `InstalledPackInfo` for a registry-installed pack.
|
|
170
|
+
*/
|
|
171
|
+
export interface BuildInstalledPackInfoArgs {
|
|
172
|
+
readonly platform: {
|
|
173
|
+
readonly fs: FileSystem.FileSystem;
|
|
174
|
+
readonly path: Path.Path;
|
|
175
|
+
};
|
|
176
|
+
readonly workspaceRoot: string;
|
|
177
|
+
readonly scope: "project" | "user";
|
|
178
|
+
readonly sourceName: string;
|
|
179
|
+
readonly owner: string;
|
|
180
|
+
readonly name: string;
|
|
181
|
+
readonly packJson: unknown;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Build an `InstalledPackInfo` rooted at `agent_extensions/<owner>/packs/<name>/`.
|
|
185
|
+
*
|
|
186
|
+
* **No `src/` segment** — matches the on-disk pack layout.
|
|
187
|
+
*
|
|
188
|
+
* @experimental This API is unstable and may change without notice.
|
|
189
|
+
*/
|
|
190
|
+
export declare const buildInstalledPackInfo: (args: BuildInstalledPackInfoArgs) => InstalledPackInfo;
|
|
191
|
+
//# sourceMappingURL=lint-workspace.d.ts.map
|