@agentxm/workspace-lint 0.28.12 → 0.28.13
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/dist/src/catalog/index.d.ts +2 -2
- package/dist/src/catalog/index.js +7 -7
- package/dist/src/catalog/workspace/agents-detected-declared.d.ts +1 -1
- package/dist/src/catalog/workspace/agents-projections-stale.d.ts +1 -1
- package/dist/src/catalog/workspace/agents-recognized.d.ts +2 -2
- package/dist/src/catalog/workspace/agents-recognized.js +1 -1
- package/dist/src/catalog/workspace/axm-skill-compatible.d.ts +1 -1
- package/dist/src/catalog/workspace/axm-skill-compatible.js +1 -1
- package/dist/src/catalog/workspace/axm-skill-declared.d.ts +1 -1
- package/dist/src/catalog/workspace/configured-but-not-installed.d.ts +1 -1
- package/dist/src/catalog/workspace/desired-state-reconcilable.d.ts +1 -1
- package/dist/src/catalog/workspace/desired-state-reconcilable.js +1 -1
- package/dist/src/catalog/workspace/helpers/empty.d.ts +1 -1
- package/dist/src/catalog/workspace/helpers/finding.d.ts +1 -1
- package/dist/src/catalog/workspace/hook-ownership-ambiguous.d.ts +1 -1
- package/dist/src/catalog/workspace/initialized.d.ts +1 -1
- package/dist/src/catalog/workspace/instructions-agent-supported.d.ts +1 -1
- package/dist/src/catalog/workspace/instructions-gitignore-current.d.ts +1 -1
- package/dist/src/catalog/workspace/instructions-source-present.d.ts +1 -1
- package/dist/src/catalog/workspace/instructions-target-current.d.ts +1 -1
- package/dist/src/catalog/workspace/instructions-target-stale.d.ts +1 -1
- package/dist/src/catalog/workspace/instructions-target-unowned.d.ts +1 -1
- package/dist/src/catalog/workspace/knowledge-state-valid.d.ts +1 -1
- package/dist/src/catalog/workspace/lockfile-valid.d.ts +1 -1
- package/dist/src/catalog/workspace/managed-file-unowned.d.ts +1 -1
- package/dist/src/catalog/workspace/mcps-agent-drift.d.ts +1 -1
- package/dist/src/catalog/workspace/mcps-agent-drift.js +3 -2
- package/dist/src/catalog/workspace/mcps-agent-orphaned.d.ts +1 -1
- package/dist/src/catalog/workspace/mcps-agent-orphaned.js +2 -2
- package/dist/src/catalog/workspace/mcps-no-secret-literal.d.ts +1 -1
- package/dist/src/catalog/workspace/mcps-transport-exclusivity.d.ts +1 -1
- package/dist/src/catalog/workspace/packs-declarations-valid.d.ts +1 -1
- package/dist/src/catalog/workspace/packs-dependencies-resolved.d.ts +1 -1
- package/dist/src/catalog/workspace/projection-contributors-rendered.d.ts +2 -2
- package/dist/src/catalog/workspace/projection-contributors-rendered.js +1 -1
- package/dist/src/catalog/workspace/projection-ownership-valid.d.ts +2 -2
- package/dist/src/catalog/workspace/projection-ownership-valid.js +1 -1
- package/dist/src/catalog/workspace/settings-keys-recognized.d.ts +1 -1
- package/dist/src/catalog/workspace/settings-schema-valid.d.ts +1 -1
- package/dist/src/catalog/workspace/skills-artifacts-correct.d.ts +1 -1
- package/dist/src/catalog/workspace/skills-declarations-valid.d.ts +1 -1
- package/dist/src/catalog/workspace/skills-integrity-valid.d.ts +1 -1
- package/dist/src/catalog/workspace/skills-lockfile-aligned.d.ts +1 -1
- package/dist/src/catalog/workspace/source-endpoints-aligned.d.ts +1 -1
- package/dist/src/catalog/workspace-read-model/lint-workspace.d.ts +9 -9
- package/dist/src/catalog/workspace-read-model/lint-workspace.js +7 -5
- package/dist/src/catalog/workspace-read-model/manifest-json.d.ts +1 -1
- package/dist/src/catalog/workspace-read-model/manifest-json.js +1 -1
- package/dist/src/catalog/workspace.d.ts +1 -1
- package/dist/src/catalog-contexts.d.ts +9 -9
- package/dist/src/catalog-contexts.js +7 -7
- package/dist/src/cli.d.ts +6 -6
- package/dist/src/cli.js +5 -4
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +3 -0
- package/dist/src/json-schema.js +1 -1
- package/dist/src/run/lint-workspace.d.ts +144 -0
- package/dist/src/run/lint-workspace.js +204 -0
- package/dist/src/run/settings.d.ts +2 -2
- package/dist/src/run/settings.js +2 -2
- package/dist/src/testing.d.ts +153 -0
- package/dist/src/testing.js +303 -0
- package/dist/src/workspace-context.d.ts +3 -2
- package/dist/src/workspace-context.js +1 -1
- package/package.json +17 -10
- package/dist/src/catalog/workspace-fixtures/fixture-state.d.ts +0 -8
- package/dist/src/catalog/workspace-fixtures/fixture-state.js +0 -120
- package/dist/src/catalog/workspace-fixtures/interpret-ops.d.ts +0 -11
- package/dist/src/catalog/workspace-fixtures/interpret-ops.js +0 -9
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agentxm/workspace-lint deterministic test fixtures.
|
|
3
|
+
*
|
|
4
|
+
* A lint run reads a real workspace through the state, agent-repository and
|
|
5
|
+
* invariant-fact services, so a fixture here composes those over a throwaway
|
|
6
|
+
* directory rather than answering for them. What it does supply is the two
|
|
7
|
+
* things a lint example cannot state for itself without becoming brittle: a
|
|
8
|
+
* configured rule set in which exactly one catalog rule is live, so no
|
|
9
|
+
* unrelated finding can decide the scenario, and a workspace whose files are
|
|
10
|
+
* written as the product writes them.
|
|
11
|
+
*
|
|
12
|
+
* `ProjectionParticipants` stays in the layer's requirements on purpose. Which
|
|
13
|
+
* extension types contribute projections is a fact this package may not
|
|
14
|
+
* invent — `@agentxm/workspace-lint` does not depend on the materialization
|
|
15
|
+
* managers — so the caller states it, either with the real registry from
|
|
16
|
+
* `@agentxm/extension-materialization/live` or with a deliberate stand-in from
|
|
17
|
+
* `@agentxm/workspace-projection/testing`.
|
|
18
|
+
*
|
|
19
|
+
* Production source never imports this module.
|
|
20
|
+
*
|
|
21
|
+
* @experimental This API is unstable and may change without notice.
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
import * as fs from "node:fs";
|
|
25
|
+
import * as os from "node:os";
|
|
26
|
+
import * as nodePath from "node:path";
|
|
27
|
+
import * as Layer from "effect/Layer";
|
|
28
|
+
import { decodeAbsolutePathSync } from "@agentxm/extension-model/unstable/path-types";
|
|
29
|
+
import { AXM_SKILL_CLI_VERSION_METADATA_KEY, AXM_SKILL_CLI_VERSION_RANGE_METADATA_KEY, } from "@agentxm/extension-resolution";
|
|
30
|
+
import { AxmSkillCompatibilityPolicyTest } from "@agentxm/extension-resolution/testing";
|
|
31
|
+
import { CodingAgentRepositoryLive, NativeWriteAuthorityLive, WorkspaceInvariantFactsLive, } from "@agentxm/workspace-projection/live";
|
|
32
|
+
import { WorkspaceStateLive } from "@agentxm/workspace-state/live";
|
|
33
|
+
import { ConfiguredAgentOutcomesProviderTest } from "@agentxm/workspace-state/testing";
|
|
34
|
+
import { allCatalogRuleIds } from "./catalog/index.js";
|
|
35
|
+
/**
|
|
36
|
+
* A rule configuration in which exactly one catalog rule is live and every
|
|
37
|
+
* other is `off`. A lint example that asserts on one rule's finding needs
|
|
38
|
+
* this: without it an unrelated rule's finding can decide the run's exit
|
|
39
|
+
* category and the example proves nothing about its own subject.
|
|
40
|
+
*
|
|
41
|
+
* Pass `undefined` for `severity` to leave the named rule at its catalog
|
|
42
|
+
* default while every other rule is silenced. An unknown rule id throws, so a
|
|
43
|
+
* renamed rule fails the example rather than silently matching nothing.
|
|
44
|
+
*/
|
|
45
|
+
export const isolatedLintRules = (ruleId, severity) => {
|
|
46
|
+
if (!allCatalogRuleIds.includes(ruleId)) {
|
|
47
|
+
throw new Error(`Unknown lint rule '${ruleId}'`);
|
|
48
|
+
}
|
|
49
|
+
const rules = {};
|
|
50
|
+
for (const id of allCatalogRuleIds) {
|
|
51
|
+
if (id !== ruleId)
|
|
52
|
+
rules[id] = "off";
|
|
53
|
+
}
|
|
54
|
+
if (severity !== undefined)
|
|
55
|
+
rules[ruleId] = severity;
|
|
56
|
+
return rules;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Every workspace-facing service a lint run over one root reads through. A
|
|
60
|
+
* Git-index run lints a materialized snapshot rather than the working tree, so
|
|
61
|
+
* the root a run's services are built over is decided by the admitted
|
|
62
|
+
* selection, not by the directory a fixture created.
|
|
63
|
+
*/
|
|
64
|
+
export const lintWorkspaceServices = (args) => {
|
|
65
|
+
const state = WorkspaceStateLive({
|
|
66
|
+
scope: "project",
|
|
67
|
+
projectRoot: decodeAbsolutePathSync(args.workspaceRoot),
|
|
68
|
+
});
|
|
69
|
+
const agents = Layer.provideMerge(Layer.mergeAll(CodingAgentRepositoryLive, ConfiguredAgentOutcomesProviderTest), state);
|
|
70
|
+
return Layer.provideMerge(Layer.mergeAll(WorkspaceInvariantFactsLive.pipe(Layer.provide(NativeWriteAuthorityLive)), AxmSkillCompatibilityPolicyTest(args.cliVersion ?? null)), agents);
|
|
71
|
+
};
|
|
72
|
+
/** A throwaway initialized workspace with the services a lint run reads it through. */
|
|
73
|
+
export const makeLintWorkspace = (options = {}) => {
|
|
74
|
+
const root = fs.realpathSync(fs.mkdtempSync(nodePath.join(os.tmpdir(), "axm-lint-")));
|
|
75
|
+
const absolute = (relativePath) => nodePath.join(root, relativePath);
|
|
76
|
+
const writeFile = (relativePath, contents) => {
|
|
77
|
+
const file = absolute(relativePath);
|
|
78
|
+
fs.mkdirSync(nodePath.dirname(file), { recursive: true });
|
|
79
|
+
fs.writeFileSync(file, contents);
|
|
80
|
+
};
|
|
81
|
+
const writeSettings = (settings) => {
|
|
82
|
+
writeFile("axm.json", `${JSON.stringify({ agents: [], ...settings }, null, 2)}\n`);
|
|
83
|
+
};
|
|
84
|
+
writeSettings(options.settings ?? {});
|
|
85
|
+
for (const [relativePath, contents] of Object.entries(options.files ?? {})) {
|
|
86
|
+
writeFile(relativePath, contents);
|
|
87
|
+
}
|
|
88
|
+
const link = (linkPath, targetPath) => {
|
|
89
|
+
const linkFile = absolute(linkPath);
|
|
90
|
+
fs.mkdirSync(nodePath.dirname(linkFile), { recursive: true });
|
|
91
|
+
fs.symlinkSync(nodePath.relative(nodePath.dirname(linkFile), absolute(targetPath)), linkFile, "dir");
|
|
92
|
+
};
|
|
93
|
+
const snapshot = () => {
|
|
94
|
+
const entries = [];
|
|
95
|
+
const walk = (directory) => {
|
|
96
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
97
|
+
const child = nodePath.join(directory, entry.name);
|
|
98
|
+
const relative = nodePath.relative(root, child);
|
|
99
|
+
if (entry.isSymbolicLink()) {
|
|
100
|
+
entries.push([relative, `symlink:${fs.readlinkSync(child)}`]);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
entries.push([relative, "directory"]);
|
|
105
|
+
walk(child);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
entries.push([relative, fs.readFileSync(child, "utf8")]);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
walk(root);
|
|
112
|
+
return entries.sort((left, right) => left[0].localeCompare(right[0]));
|
|
113
|
+
};
|
|
114
|
+
const layer = lintWorkspaceServices({
|
|
115
|
+
workspaceRoot: root,
|
|
116
|
+
cliVersion: options.cliVersion ?? null,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
root,
|
|
120
|
+
writeFile,
|
|
121
|
+
readFile: (relativePath) => fs.readFileSync(absolute(relativePath), "utf8"),
|
|
122
|
+
exists: (relativePath) => fs.existsSync(absolute(relativePath)),
|
|
123
|
+
remove: (relativePath) => fs.rmSync(absolute(relativePath), { force: true, recursive: true }),
|
|
124
|
+
writeSettings,
|
|
125
|
+
link,
|
|
126
|
+
snapshot,
|
|
127
|
+
layer,
|
|
128
|
+
cleanup: () => fs.rmSync(root, { recursive: true, force: true }),
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
// -----------------------------------------------------------------------------
|
|
132
|
+
// The official AXM skill
|
|
133
|
+
// -----------------------------------------------------------------------------
|
|
134
|
+
/** Where a project workspace keeps the official AXM skill's canonical package. */
|
|
135
|
+
export const OFFICIAL_AXM_SKILL_PACKAGE_ROOT = "agent_extensions/agentxm/@agentxm/skills/axm";
|
|
136
|
+
/** Where a workspace that authors its own `axm` skill keeps it. */
|
|
137
|
+
export const AUTHORED_AXM_SKILL_PACKAGE_ROOT = "skills/axm";
|
|
138
|
+
/** The agent directory entry `claude-code` reads a project skill from. */
|
|
139
|
+
export const CLAUDE_CODE_SKILLS_DIR = ".claude/skills";
|
|
140
|
+
/** The two rules that decide what lint says about the official AXM skill. */
|
|
141
|
+
export const OFFICIAL_AXM_SKILL_RULE_IDS = [
|
|
142
|
+
"workspace/axm-skill-declared",
|
|
143
|
+
"workspace/axm-skill-compatible",
|
|
144
|
+
];
|
|
145
|
+
/**
|
|
146
|
+
* A rule configuration in which only the two official-skill rules are live.
|
|
147
|
+
* An official-skill example is about what lint concludes from the declaration
|
|
148
|
+
* and the installed package, so no unrelated rule may decide its run.
|
|
149
|
+
*/
|
|
150
|
+
export const isolateOfficialAxmSkillRules = () => {
|
|
151
|
+
const live = new Set(OFFICIAL_AXM_SKILL_RULE_IDS);
|
|
152
|
+
const rules = {};
|
|
153
|
+
for (const id of allCatalogRuleIds) {
|
|
154
|
+
if (!live.has(id))
|
|
155
|
+
rules[id] = "off";
|
|
156
|
+
}
|
|
157
|
+
return rules;
|
|
158
|
+
};
|
|
159
|
+
/** The CLI release every official-skill fixture but the prerelease one runs as. */
|
|
160
|
+
export const FIXTURE_CLI_VERSION = "1.2.3";
|
|
161
|
+
/** The prerelease CLI release the in-range prerelease fixture runs as. */
|
|
162
|
+
export const FIXTURE_PRERELEASE_CLI_VERSION = "1.3.0-preview.1";
|
|
163
|
+
/** The bounded, wildcard-free range the fixture skills declare. */
|
|
164
|
+
const FIXTURE_CLI_VERSION_RANGE = ">=1.0.0 <2.0.0";
|
|
165
|
+
const officialSkillManifest = (version) => `${JSON.stringify({
|
|
166
|
+
$schema: "https://axm.sh/schemas/skill.schema.json",
|
|
167
|
+
owner: "@agentxm",
|
|
168
|
+
type: "skill",
|
|
169
|
+
name: "axm",
|
|
170
|
+
version,
|
|
171
|
+
description: "The official AXM skill.",
|
|
172
|
+
}, null, 2)}\n`;
|
|
173
|
+
const officialSkillMd = (metadata) => [
|
|
174
|
+
"---",
|
|
175
|
+
"name: axm",
|
|
176
|
+
"description: The official AXM skill.",
|
|
177
|
+
...(metadata === null
|
|
178
|
+
? []
|
|
179
|
+
: [
|
|
180
|
+
"metadata:",
|
|
181
|
+
` ${AXM_SKILL_CLI_VERSION_METADATA_KEY}: "${metadata.cliVersion}"`,
|
|
182
|
+
` ${AXM_SKILL_CLI_VERSION_RANGE_METADATA_KEY}: "${metadata.cliVersionRange}"`,
|
|
183
|
+
]),
|
|
184
|
+
"---",
|
|
185
|
+
"",
|
|
186
|
+
"# axm",
|
|
187
|
+
"",
|
|
188
|
+
"Operate AXM from the agent.",
|
|
189
|
+
"",
|
|
190
|
+
].join("\n");
|
|
191
|
+
/** The canonical package files a materialized official AXM skill is made of. */
|
|
192
|
+
export const officialAxmSkillPackage = (args) => ({
|
|
193
|
+
[`${args.packageRoot}/skill.json`]: officialSkillManifest(args.version),
|
|
194
|
+
...(args.withSkillMd === false
|
|
195
|
+
? {}
|
|
196
|
+
: { [`${args.packageRoot}/src/SKILL.md`]: officialSkillMd(args.metadata) }),
|
|
197
|
+
});
|
|
198
|
+
const BUNDLED_ENTRY = { source: "workspace", enabled: true, origin: "bundled" };
|
|
199
|
+
const REGISTRY_SOURCE = "agentxm:@agentxm/skills/axm";
|
|
200
|
+
const bundled = (version, metadataVersion) => ({
|
|
201
|
+
cliVersion: FIXTURE_CLI_VERSION,
|
|
202
|
+
skills: { axm: BUNDLED_ENTRY },
|
|
203
|
+
files: officialAxmSkillPackage({
|
|
204
|
+
packageRoot: OFFICIAL_AXM_SKILL_PACKAGE_ROOT,
|
|
205
|
+
version,
|
|
206
|
+
metadata: { cliVersion: metadataVersion, cliVersionRange: FIXTURE_CLI_VERSION_RANGE },
|
|
207
|
+
}),
|
|
208
|
+
realized: [[`${CLAUDE_CODE_SKILLS_DIR}/axm`, `${OFFICIAL_AXM_SKILL_PACKAGE_ROOT}/src`]],
|
|
209
|
+
});
|
|
210
|
+
const arrangeOfficialSkill = (state) => {
|
|
211
|
+
switch (state) {
|
|
212
|
+
case "undeclared":
|
|
213
|
+
return { cliVersion: FIXTURE_CLI_VERSION, files: {}, realized: [] };
|
|
214
|
+
case "non-official":
|
|
215
|
+
// Declared under another owner's name: the workspace never asked for the
|
|
216
|
+
// official skill, so lint has nothing to hold to compatibility.
|
|
217
|
+
return {
|
|
218
|
+
cliVersion: FIXTURE_CLI_VERSION,
|
|
219
|
+
skills: { axm: "@acme/skills/axm" },
|
|
220
|
+
files: {},
|
|
221
|
+
realized: [],
|
|
222
|
+
};
|
|
223
|
+
case "official-missing":
|
|
224
|
+
return {
|
|
225
|
+
cliVersion: FIXTURE_CLI_VERSION,
|
|
226
|
+
skills: { axm: REGISTRY_SOURCE },
|
|
227
|
+
files: {},
|
|
228
|
+
realized: [],
|
|
229
|
+
};
|
|
230
|
+
case "official-registry":
|
|
231
|
+
return {
|
|
232
|
+
cliVersion: FIXTURE_CLI_VERSION,
|
|
233
|
+
skills: { axm: REGISTRY_SOURCE },
|
|
234
|
+
files: officialAxmSkillPackage({
|
|
235
|
+
packageRoot: OFFICIAL_AXM_SKILL_PACKAGE_ROOT,
|
|
236
|
+
version: "0.0.1",
|
|
237
|
+
metadata: { cliVersion: "0.0.1", cliVersionRange: ">=0.0.1 <0.1.0" },
|
|
238
|
+
}),
|
|
239
|
+
realized: [[`${CLAUDE_CODE_SKILLS_DIR}/axm`, `${OFFICIAL_AXM_SKILL_PACKAGE_ROOT}/src`]],
|
|
240
|
+
};
|
|
241
|
+
case "official-skewed":
|
|
242
|
+
// The materialized release and the release its compatibility metadata
|
|
243
|
+
// claims disagree, so nothing about the pair can be trusted.
|
|
244
|
+
return bundled("0.0.1", FIXTURE_CLI_VERSION);
|
|
245
|
+
case "official-authored":
|
|
246
|
+
// A workspace that authors its own `axm` skill publishes under its own
|
|
247
|
+
// handle, so the settings declare the owner authored content resolves
|
|
248
|
+
// against.
|
|
249
|
+
return {
|
|
250
|
+
cliVersion: FIXTURE_CLI_VERSION,
|
|
251
|
+
skills: { axm: "workspace" },
|
|
252
|
+
owner: "@agentxm",
|
|
253
|
+
files: officialAxmSkillPackage({
|
|
254
|
+
packageRoot: AUTHORED_AXM_SKILL_PACKAGE_ROOT,
|
|
255
|
+
version: "0.0.1",
|
|
256
|
+
metadata: null,
|
|
257
|
+
}),
|
|
258
|
+
realized: [[`${CLAUDE_CODE_SKILLS_DIR}/axm`, `${AUTHORED_AXM_SKILL_PACKAGE_ROOT}/src`]],
|
|
259
|
+
};
|
|
260
|
+
case "official-compatible":
|
|
261
|
+
return bundled(FIXTURE_CLI_VERSION, FIXTURE_CLI_VERSION);
|
|
262
|
+
case "official-compatible-prerelease":
|
|
263
|
+
return {
|
|
264
|
+
...bundled(FIXTURE_PRERELEASE_CLI_VERSION, FIXTURE_PRERELEASE_CLI_VERSION),
|
|
265
|
+
cliVersion: FIXTURE_PRERELEASE_CLI_VERSION,
|
|
266
|
+
};
|
|
267
|
+
case "official-unreadable":
|
|
268
|
+
return {
|
|
269
|
+
cliVersion: FIXTURE_CLI_VERSION,
|
|
270
|
+
skills: { axm: BUNDLED_ENTRY },
|
|
271
|
+
files: officialAxmSkillPackage({
|
|
272
|
+
packageRoot: OFFICIAL_AXM_SKILL_PACKAGE_ROOT,
|
|
273
|
+
version: FIXTURE_CLI_VERSION,
|
|
274
|
+
metadata: null,
|
|
275
|
+
withSkillMd: false,
|
|
276
|
+
}),
|
|
277
|
+
realized: [],
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
/**
|
|
282
|
+
* A throwaway workspace whose official AXM skill is arranged in the named
|
|
283
|
+
* state, with the compatibility policy pinned to the CLI release that state
|
|
284
|
+
* is about.
|
|
285
|
+
*/
|
|
286
|
+
export const makeOfficialAxmSkillWorkspace = (state, options = {}) => {
|
|
287
|
+
const arrangement = arrangeOfficialSkill(state);
|
|
288
|
+
const fixture = makeLintWorkspace({
|
|
289
|
+
settings: {
|
|
290
|
+
agents: options.agents ?? ["claude-code"],
|
|
291
|
+
...(arrangement.owner === undefined ? {} : { owner: arrangement.owner }),
|
|
292
|
+
...(arrangement.skills === undefined ? {} : { skills: arrangement.skills }),
|
|
293
|
+
...options.settings,
|
|
294
|
+
},
|
|
295
|
+
files: { ...arrangement.files, ...options.files },
|
|
296
|
+
cliVersion: arrangement.cliVersion,
|
|
297
|
+
});
|
|
298
|
+
for (const [linkPath, targetPath] of arrangement.realized) {
|
|
299
|
+
fixture.link(linkPath, targetPath);
|
|
300
|
+
}
|
|
301
|
+
return { ...fixture, cliVersion: arrangement.cliVersion };
|
|
302
|
+
};
|
|
303
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -12,12 +12,13 @@ import type * as Effect from "effect/Effect";
|
|
|
12
12
|
import type * as Option from "effect/Option";
|
|
13
13
|
import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions/common";
|
|
14
14
|
import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
|
|
15
|
-
import type { PackDependencyReachability, AgentOutputInventory,
|
|
16
|
-
import type { ProjectionInvariantFact } from "@agentxm/
|
|
15
|
+
import type { PackDependencyReachability, AgentOutputInventory, WorkspaceOwnershipIssue, InstructionProjectionSnapshot } from "@agentxm/workspace-projection";
|
|
16
|
+
import type { ProjectionInvariantFact } from "@agentxm/workspace-projection";
|
|
17
17
|
import type { CanonicalObservation } from "@agentxm/workspace-state";
|
|
18
18
|
import type { DesiredExtensionNode, DesiredStateGraph } from "@agentxm/workspace-state";
|
|
19
19
|
import type { LockfileReadError, SettingsReadError } from "@agentxm/workspace-state";
|
|
20
20
|
import type { WorkspaceReadModel } from "@agentxm/workspace-state";
|
|
21
|
+
import { type AxmSkillCompatibility } from "@agentxm/extension-resolution";
|
|
21
22
|
/**
|
|
22
23
|
* Context passed to `workspace/*` rules.
|
|
23
24
|
*
|
package/package.json
CHANGED
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
"url": "https://github.com/agentxm/axm/issues"
|
|
5
5
|
},
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@agentxm/
|
|
8
|
-
"@agentxm/extension-
|
|
9
|
-
"@agentxm/extension-
|
|
10
|
-
"@agentxm/
|
|
11
|
-
"@agentxm/
|
|
12
|
-
"@agentxm/workspace-
|
|
7
|
+
"@agentxm/agent-integration": "^0.28.13",
|
|
8
|
+
"@agentxm/extension-content": "^0.28.13",
|
|
9
|
+
"@agentxm/extension-model": "^0.28.13",
|
|
10
|
+
"@agentxm/extension-resolution": "^0.28.13",
|
|
11
|
+
"@agentxm/extension-sources": "^0.28.13",
|
|
12
|
+
"@agentxm/workspace-operations": "^0.28.13",
|
|
13
|
+
"@agentxm/workspace-projection": "^0.28.13",
|
|
14
|
+
"@agentxm/workspace-state": "^0.28.13",
|
|
13
15
|
"effect": "4.0.0-rc.112"
|
|
14
16
|
},
|
|
15
17
|
"description": "AXM workspace-lint feature: workspace facts, lint rules, findings, normalization, and bounded fix planning for the axm CLI. Unstable and unsupported — use the axm.sh CLI.",
|
|
16
18
|
"devDependencies": {
|
|
17
|
-
"@agentxm/agent-integration": "^0.28.
|
|
19
|
+
"@agentxm/agent-integration": "^0.28.13",
|
|
18
20
|
"@effect/platform-node": "4.0.0-rc.112",
|
|
19
21
|
"@effect/vitest": "4.0.0-rc.112",
|
|
20
22
|
"@types/bun": "^1.3.14",
|
|
@@ -30,6 +32,11 @@
|
|
|
30
32
|
"axm-source": "./src/index.ts",
|
|
31
33
|
"default": "./dist/src/index.js",
|
|
32
34
|
"types": "./dist/src/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./testing": {
|
|
37
|
+
"axm-source": "./src/testing.ts",
|
|
38
|
+
"default": "./dist/src/testing.js",
|
|
39
|
+
"types": "./dist/src/testing.d.ts"
|
|
33
40
|
}
|
|
34
41
|
},
|
|
35
42
|
"files": [
|
|
@@ -46,11 +53,11 @@
|
|
|
46
53
|
"access": "public"
|
|
47
54
|
},
|
|
48
55
|
"repository": {
|
|
49
|
-
"directory": "packages/workspace-lint",
|
|
56
|
+
"directory": "packages/core/workspace-lint",
|
|
50
57
|
"type": "git",
|
|
51
58
|
"url": "https://github.com/agentxm/axm.git"
|
|
52
59
|
},
|
|
53
60
|
"sideEffects": false,
|
|
54
61
|
"type": "module",
|
|
55
|
-
"version": "0.28.
|
|
56
|
-
}
|
|
62
|
+
"version": "0.28.13"
|
|
63
|
+
}
|
|
@@ -1,8 +0,0 @@
|
|
|
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
|
|
@@ -1,120 +0,0 @@
|
|
|
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
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|