@agentxm/workspace-lint 0.30.1 → 0.30.2

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 (26) hide show
  1. package/dist/src/catalog/workspace/agent-content-has-settings.d.ts +4 -0
  2. package/dist/src/catalog/workspace/agent-content-has-settings.js +51 -0
  3. package/dist/src/catalog/workspace/authored-package-declared.d.ts +4 -0
  4. package/dist/src/catalog/workspace/authored-package-declared.js +26 -0
  5. package/dist/src/catalog/workspace/display-paths.d.ts +2 -0
  6. package/dist/src/catalog/workspace/display-paths.js +5 -0
  7. package/dist/src/catalog/workspace/helpers/agent-scope-display.d.ts +7 -0
  8. package/dist/src/catalog/workspace/helpers/agent-scope-display.js +10 -0
  9. package/dist/src/catalog/workspace/install-root-entries-recognized.d.ts +4 -0
  10. package/dist/src/catalog/workspace/install-root-entries-recognized.js +23 -0
  11. package/dist/src/catalog/workspace/installed-but-not-configured.d.ts +4 -0
  12. package/dist/src/catalog/workspace/installed-but-not-configured.js +27 -0
  13. package/dist/src/catalog/workspace/project-outputs-not-shadowed.d.ts +4 -0
  14. package/dist/src/catalog/workspace/project-outputs-not-shadowed.js +49 -0
  15. package/dist/src/catalog/workspace/user-outputs-have-settings.d.ts +4 -0
  16. package/dist/src/catalog/workspace/user-outputs-have-settings.js +35 -0
  17. package/dist/src/catalog/workspace.js +18 -0
  18. package/dist/src/run/agent-scopes.d.ts +47 -0
  19. package/dist/src/run/agent-scopes.js +112 -0
  20. package/dist/src/run/authored-packages.d.ts +32 -0
  21. package/dist/src/run/authored-packages.js +58 -0
  22. package/dist/src/run/lint-workspace.d.ts +2 -2
  23. package/dist/src/run/lint-workspace.js +37 -1
  24. package/dist/src/testing.js +1 -0
  25. package/dist/src/workspace-context.d.ts +11 -1
  26. package/package.json +10 -10
@@ -0,0 +1,4 @@
1
+ import type { AdvisoryRule } from "@agentxm/extension-content/lint";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ export declare const agentContentHasSettingsRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=agent-content-has-settings.d.ts.map
@@ -0,0 +1,51 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import * as Result from "effect/Result";
4
+ import { workspaceDisplayPath } from "./display-paths.js";
5
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
6
+ import { agentsDisplay } from "./helpers/agent-scope-display.js";
7
+ const RULE_ID = "workspace/agent-content-has-settings";
8
+ const describeEntry = (entry, display) => {
9
+ if (entry.kind === "instructions")
10
+ return `Agent instruction file ${display}`;
11
+ const count = entry.entryCount ?? 0;
12
+ const entries = `${count} ${count === 1 ? "entry" : "entries"}`;
13
+ switch (entry.kind) {
14
+ case "skill":
15
+ return `Agent skills directory ${display} with ${entries}`;
16
+ case "subagent":
17
+ return `Agent subagents directory ${display} with ${entries}`;
18
+ case "mcp-server":
19
+ return `Agent MCP configuration ${display} with ${entries}`;
20
+ case "hook":
21
+ return `Agent hook configuration ${display} with ${entries}`;
22
+ }
23
+ };
24
+ export const agentContentHasSettingsRule = {
25
+ id: RULE_ID,
26
+ description: "Agent content in a project folder is explained by project workspace settings.",
27
+ kind: "advisory",
28
+ severity: "warning",
29
+ check: (context) => {
30
+ const { agentContent } = context;
31
+ if (context.subject.scope !== "project" || agentContent === undefined)
32
+ return Effect.succeed(EMPTY_LINT_FINDINGS);
33
+ return Effect.gen(function* () {
34
+ const settings = yield* Effect.result(context.workspace.state.raw("settings"));
35
+ if (Result.isSuccess(settings) && Option.isSome(settings.success))
36
+ return EMPTY_LINT_FINDINGS;
37
+ const entries = yield* agentContent;
38
+ return entries.map((entry) => {
39
+ const display = workspaceDisplayPath(context.subject.root, entry.path);
40
+ return {
41
+ kind: "advisory",
42
+ ruleId: RULE_ID,
43
+ severity: "warning",
44
+ message: `${describeEntry(entry, display)} for ${agentsDisplay(entry.agentIds)} exists in a folder without project workspace settings (axm.json).`,
45
+ location: { file: display },
46
+ };
47
+ });
48
+ });
49
+ },
50
+ };
51
+ //# sourceMappingURL=agent-content-has-settings.js.map
@@ -0,0 +1,4 @@
1
+ import type { AdvisoryRule } from "@agentxm/extension-content/lint";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ export declare const authoredPackageDeclaredRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=authored-package-declared.d.ts.map
@@ -0,0 +1,26 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { extensionTypeSentenceLabels, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions/common";
3
+ import { settingsDisplayPath, workspaceDisplayPath } from "./display-paths.js";
4
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
5
+ const RULE_ID = "workspace/authored-package-declared";
6
+ export const authoredPackageDeclaredRule = {
7
+ id: RULE_ID,
8
+ description: "Authored packages in standard authoring folders are declared in workspace settings.",
9
+ kind: "advisory",
10
+ severity: "warning",
11
+ check: (context) => context.authoredPackages === undefined
12
+ ? Effect.succeed(EMPTY_LINT_FINDINGS)
13
+ : context.authoredPackages.pipe(Effect.map((packages) => packages
14
+ .filter((authored) => !authored.declared)
15
+ .map((authored) => {
16
+ const display = workspaceDisplayPath(context.subject.root, authored.path);
17
+ return {
18
+ kind: "advisory",
19
+ ruleId: RULE_ID,
20
+ severity: "warning",
21
+ message: `Authored ${extensionTypeSentenceLabels[authored.type]} '${authored.owner}/${toExtensionTypePlural(authored.type)}/${authored.name}' version ${authored.version} at ${display} is not declared in ${settingsDisplayPath(context.subject.scope)}.`,
22
+ location: { file: display },
23
+ };
24
+ }))),
25
+ };
26
+ //# sourceMappingURL=authored-package-declared.js.map
@@ -2,6 +2,8 @@ import type { WorkspaceRuleContext } from "../../workspace-context.js";
2
2
  type WorkspaceScope = WorkspaceRuleContext["subject"]["scope"];
3
3
  export declare const settingsDisplayPath: (scope: WorkspaceScope) => string;
4
4
  export declare const lockfileDisplayPath: (scope: WorkspaceScope) => string;
5
+ /** A path under the workspace root, relative to it; any other path unchanged. */
6
+ export declare const workspaceDisplayPath: (root: string, file: string) => string;
5
7
  export declare const canonicalDisplayRoot: (scope: WorkspaceScope) => string;
6
8
  export {};
7
9
  //# sourceMappingURL=display-paths.d.ts.map
@@ -1,4 +1,9 @@
1
1
  export const settingsDisplayPath = (scope) => scope === "project" ? "axm.json" : ".axm/workspace/axm.json";
2
2
  export const lockfileDisplayPath = (scope) => scope === "project" ? "axm-lock.yaml" : ".axm/workspace/axm-lock.yaml";
3
+ /** A path under the workspace root, relative to it; any other path unchanged. */
4
+ export const workspaceDisplayPath = (root, file) => {
5
+ const prefix = `${root}/`;
6
+ return file.startsWith(prefix) ? file.slice(prefix.length) : file;
7
+ };
3
8
  export const canonicalDisplayRoot = (scope) => scope === "project" ? "agent_extensions" : ".axm/workspace/agent_extensions";
4
9
  //# sourceMappingURL=display-paths.js.map
@@ -0,0 +1,7 @@
1
+ import type { AgentOutputObservation } from "@agentxm/workspace-projection";
2
+ /** A path under the user home as `~/<relative>`; any other path unchanged. */
3
+ export declare const userDisplayPath: (home: string, file: string) => string;
4
+ /** Up to three agent ids by name, otherwise their count. */
5
+ export declare const agentsDisplay: (agentIds: ReadonlyArray<string>) => string;
6
+ export declare const outputTypeLabel: (type: AgentOutputObservation["extensionType"]) => string;
7
+ //# sourceMappingURL=agent-scope-display.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { extensionTypeSentenceLabels } from "@agentxm/extension-model/unstable/extensions/common";
2
+ /** A path under the user home as `~/<relative>`; any other path unchanged. */
3
+ export const userDisplayPath = (home, file) => {
4
+ const prefix = `${home}/`;
5
+ return file.startsWith(prefix) ? `~/${file.slice(prefix.length)}` : file;
6
+ };
7
+ /** Up to three agent ids by name, otherwise their count. */
8
+ export const agentsDisplay = (agentIds) => agentIds.length <= 3 ? agentIds.join(", ") : `${agentIds.length} agents`;
9
+ export const outputTypeLabel = (type) => extensionTypeSentenceLabels[type];
10
+ //# sourceMappingURL=agent-scope-display.js.map
@@ -0,0 +1,4 @@
1
+ import type { AdvisoryRule } from "@agentxm/extension-content/lint";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ export declare const installRootEntriesRecognizedRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=install-root-entries-recognized.d.ts.map
@@ -0,0 +1,23 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { workspaceDisplayPath } from "./display-paths.js";
3
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
4
+ const RULE_ID = "workspace/install-root-entries-recognized";
5
+ export const installRootEntriesRecognizedRule = {
6
+ id: RULE_ID,
7
+ description: "Install root entries are installed packages or AXM staging.",
8
+ kind: "advisory",
9
+ severity: "warning",
10
+ check: (context) => context.installRoot === undefined
11
+ ? Effect.succeed(EMPTY_LINT_FINDINGS)
12
+ : context.installRoot.pipe(Effect.map((inventory) => inventory.unrecognized.map((entry) => {
13
+ const display = workspaceDisplayPath(context.subject.root, entry.path);
14
+ return {
15
+ kind: "advisory",
16
+ ruleId: RULE_ID,
17
+ severity: "warning",
18
+ message: `Unrecognized ${entry.entryKind} ${display} in the ${context.subject.scope}-scope install root is not an installed package or AXM staging.`,
19
+ location: { file: display },
20
+ };
21
+ }))),
22
+ };
23
+ //# sourceMappingURL=install-root-entries-recognized.js.map
@@ -0,0 +1,4 @@
1
+ import type { AdvisoryRule } from "@agentxm/extension-content/lint";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ export declare const installedButNotConfiguredRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=installed-but-not-configured.d.ts.map
@@ -0,0 +1,27 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { extensionTypeSentenceLabels, toExtensionTypePlural, } from "@agentxm/extension-model/unstable/extensions/common";
3
+ import { workspaceDisplayPath } from "./display-paths.js";
4
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
5
+ const RULE_ID = "workspace/installed-but-not-configured";
6
+ const identityOf = (entry) => entry.owner === undefined
7
+ ? entry.name
8
+ : `${entry.owner}/${toExtensionTypePlural(entry.type)}/${entry.name}`;
9
+ export const installedButNotConfiguredRule = {
10
+ id: RULE_ID,
11
+ description: "Installed packages in the install root are reached by desired state.",
12
+ kind: "advisory",
13
+ severity: "warning",
14
+ check: (context) => context.installRoot === undefined
15
+ ? Effect.succeed(EMPTY_LINT_FINDINGS)
16
+ : context.installRoot.pipe(Effect.map((inventory) => inventory.leftovers.map((entry) => {
17
+ const display = workspaceDisplayPath(context.subject.root, entry.path);
18
+ return {
19
+ kind: "advisory",
20
+ ruleId: RULE_ID,
21
+ severity: "warning",
22
+ message: `Installed ${extensionTypeSentenceLabels[entry.type]} '${identityOf(entry)}' is not configured in ${context.subject.scope} scope: canonical path ${display}, source directory ${entry.sourceDirectory ?? "none"}, ${entry.lockKey === undefined ? "no lock row" : "lock row present"}.`,
23
+ location: { file: display },
24
+ };
25
+ }))),
26
+ };
27
+ //# sourceMappingURL=installed-but-not-configured.js.map
@@ -0,0 +1,4 @@
1
+ import type { AdvisoryRule } from "@agentxm/extension-content/lint";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ export declare const projectOutputsNotShadowedRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=project-outputs-not-shadowed.d.ts.map
@@ -0,0 +1,49 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { workspaceDisplayPath } from "./display-paths.js";
3
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
4
+ import { agentsDisplay, outputTypeLabel, userDisplayPath } from "./helpers/agent-scope-display.js";
5
+ const RULE_ID = "workspace/project-outputs-not-shadowed";
6
+ /** Types whose agent outputs are named directory or file entries an agent loads by name. */
7
+ const NAMED_OUTPUT_TYPES = new Set(["skill", "subagent"]);
8
+ export const projectOutputsNotShadowedRule = {
9
+ id: RULE_ID,
10
+ description: "Project agent outputs have no same-named user-scope output for the same agent.",
11
+ kind: "advisory",
12
+ severity: "warning",
13
+ check: (context) => {
14
+ const { agentOutputs, userScope } = context;
15
+ if (context.subject.scope !== "project" ||
16
+ agentOutputs === undefined ||
17
+ userScope === undefined)
18
+ return Effect.succeed(EMPTY_LINT_FINDINGS);
19
+ return Effect.gen(function* () {
20
+ const project = yield* agentOutputs;
21
+ const user = yield* userScope;
22
+ const findings = [];
23
+ for (const output of project.outputs) {
24
+ if (!NAMED_OUTPUT_TYPES.has(output.extensionType))
25
+ continue;
26
+ for (const other of user.outputs) {
27
+ if (other.extensionType !== output.extensionType ||
28
+ other.entryName !== output.entryName ||
29
+ other.containerPath === output.containerPath)
30
+ continue;
31
+ const shared = output.claimantAgentIds.filter((id) => other.claimantAgentIds.includes(id));
32
+ if (shared.length === 0)
33
+ continue;
34
+ const display = workspaceDisplayPath(context.subject.root, output.path);
35
+ const label = outputTypeLabel(output.extensionType);
36
+ findings.push({
37
+ kind: "advisory",
38
+ ruleId: RULE_ID,
39
+ severity: "warning",
40
+ message: `Project ${label} '${output.entryName}' at ${display} has a same-named user-scope ${label} at ${userDisplayPath(user.home, other.path)} for ${agentsDisplay(shared)}; the agent decides which one it loads.`,
41
+ location: { file: display },
42
+ });
43
+ }
44
+ }
45
+ return findings;
46
+ });
47
+ },
48
+ };
49
+ //# sourceMappingURL=project-outputs-not-shadowed.js.map
@@ -0,0 +1,4 @@
1
+ import type { AdvisoryRule } from "@agentxm/extension-content/lint";
2
+ import type { WorkspaceRuleContext } from "../../workspace-context.js";
3
+ export declare const userOutputsHaveSettingsRule: AdvisoryRule<WorkspaceRuleContext>;
4
+ //# sourceMappingURL=user-outputs-have-settings.d.ts.map
@@ -0,0 +1,35 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { EMPTY_LINT_FINDINGS } from "./helpers/empty.js";
3
+ import { outputTypeLabel, userDisplayPath } from "./helpers/agent-scope-display.js";
4
+ const RULE_ID = "workspace/user-outputs-have-settings";
5
+ const proofText = {
6
+ "storage-root-symlink": "links into AXM storage",
7
+ "managed-banner": "carries an AXM ownership marker",
8
+ "managed-mcp-entry": "is an AXM-managed MCP entry",
9
+ "managed-hook-group": "is an AXM-managed hook group",
10
+ };
11
+ export const userOutputsHaveSettingsRule = {
12
+ id: RULE_ID,
13
+ description: "AXM-owned user-scope agent outputs have readable user workspace settings.",
14
+ kind: "advisory",
15
+ severity: "warning",
16
+ check: (context) => context.userScope === undefined
17
+ ? Effect.succeed(EMPTY_LINT_FINDINGS)
18
+ : context.userScope.pipe(Effect.map((user) => user.settingsReadable
19
+ ? EMPTY_LINT_FINDINGS
20
+ : user.outputs.flatMap((output) => {
21
+ if (output.ownership !== "owned" || output.proof === undefined)
22
+ return [];
23
+ const display = userDisplayPath(user.home, output.path);
24
+ return [
25
+ {
26
+ kind: "advisory",
27
+ ruleId: RULE_ID,
28
+ severity: "warning",
29
+ message: `User-scope agent ${outputTypeLabel(output.extensionType)} ${display} ${proofText[output.proof]}, but the user workspace has no readable settings at ${userDisplayPath(user.home, user.settingsPath)}.`,
30
+ location: { file: display },
31
+ },
32
+ ];
33
+ }))),
34
+ };
35
+ //# sourceMappingURL=user-outputs-have-settings.js.map
@@ -41,7 +41,13 @@ import { axmSkillDeclaredRule } from "./workspace/axm-skill-declared.js";
41
41
  import { axmSkillCompatibleRule } from "./workspace/axm-skill-compatible.js";
42
42
  import { hookOwnershipAmbiguousRule } from "./workspace/hook-ownership-ambiguous.js";
43
43
  import { managedFileUnownedRule } from "./workspace/managed-file-unowned.js";
44
+ import { installedButNotConfiguredRule } from "./workspace/installed-but-not-configured.js";
45
+ import { authoredPackageDeclaredRule } from "./workspace/authored-package-declared.js";
46
+ import { installRootEntriesRecognizedRule } from "./workspace/install-root-entries-recognized.js";
44
47
  import { sourceEndpointsAlignedRule } from "./workspace/source-endpoints-aligned.js";
48
+ import { projectOutputsNotShadowedRule } from "./workspace/project-outputs-not-shadowed.js";
49
+ import { userOutputsHaveSettingsRule } from "./workspace/user-outputs-have-settings.js";
50
+ import { agentContentHasSettingsRule } from "./workspace/agent-content-has-settings.js";
45
51
  /**
46
52
  * Ordered repository-safe `workspace/*` rule catalog. Declaration order is
47
53
  * the evaluation order within a single `evaluateContexts` call. It groups
@@ -90,6 +96,12 @@ export const liveOnlyWorkspaceRules = [
90
96
  projectionContributorsRenderedRule,
91
97
  hookOwnershipAmbiguousRule,
92
98
  managedFileUnownedRule,
99
+ installedButNotConfiguredRule,
100
+ authoredPackageDeclaredRule,
101
+ installRootEntriesRecognizedRule,
102
+ projectOutputsNotShadowedRule,
103
+ userOutputsHaveSettingsRule,
104
+ agentContentHasSettingsRule,
93
105
  mcpServerAgentDriftRule,
94
106
  mcpServerAgentOrphanedRule,
95
107
  skillsArtifactsCorrectRule,
@@ -117,6 +129,12 @@ export const workspaceRules = [
117
129
  projectionContributorsRenderedRule,
118
130
  hookOwnershipAmbiguousRule,
119
131
  managedFileUnownedRule,
132
+ installedButNotConfiguredRule,
133
+ authoredPackageDeclaredRule,
134
+ installRootEntriesRecognizedRule,
135
+ projectOutputsNotShadowedRule,
136
+ userOutputsHaveSettingsRule,
137
+ agentContentHasSettingsRule,
120
138
  skillsDeclarationsValidRule,
121
139
  packsDeclarationsValidRule,
122
140
  configuredButNotInstalledRule,
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Agent facts that cross the selected scope's boundary.
3
+ *
4
+ * A coding agent reads the user scope and the project folder together, so a
5
+ * lint run reports what the other side contributes to the same agent context:
6
+ * the user scope's agent outputs and whether its workspace has readable
7
+ * settings, and agent content in a project folder that has no workspace
8
+ * settings to explain it.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import * as FileSystem from "effect/FileSystem";
14
+ import * as Path from "effect/Path";
15
+ import { CodingAgentRepository, type AgentOutputObservation } from "@agentxm/workspace-projection";
16
+ /** The user scope as a project-scope run's agents also see it. */
17
+ export interface UserScopeObservation {
18
+ readonly home: string;
19
+ readonly settingsPath: string;
20
+ readonly settingsReadable: boolean;
21
+ readonly outputs: ReadonlyArray<AgentOutputObservation>;
22
+ }
23
+ /** One agent-native file or directory in a project folder. */
24
+ export interface AgentContentEntry {
25
+ readonly kind: "instructions" | AgentOutputObservation["extensionType"];
26
+ readonly path: string;
27
+ /** Entries inside a directory container; absent for a single file. */
28
+ readonly entryCount?: number;
29
+ readonly agentIds: ReadonlyArray<string>;
30
+ }
31
+ /**
32
+ * Observe the user scope's agent outputs under `userHome`.
33
+ *
34
+ * Containers resolve relative to `userHome` rather than the process home, so
35
+ * the observation follows the home the run selected. Anything under the user
36
+ * AXM home counts as AXM storage, including storage left by an earlier layout.
37
+ */
38
+ export declare const observeUserScope: (userHome: string) => Effect.Effect<UserScopeObservation, never, CodingAgentRepository | FileSystem.FileSystem | Path.Path>;
39
+ /**
40
+ * Observe agent instruction files and non-empty agent output containers in a
41
+ * project folder, grouped by path with the agents that read each one.
42
+ */
43
+ export declare const observeProjectAgentContent: (args: {
44
+ readonly projectRoot: string;
45
+ readonly outputs: ReadonlyArray<AgentOutputObservation>;
46
+ }) => Effect.Effect<ReadonlyArray<AgentContentEntry>, never, FileSystem.FileSystem | Path.Path>;
47
+ //# sourceMappingURL=agent-scopes.d.ts.map
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Agent facts that cross the selected scope's boundary.
3
+ *
4
+ * A coding agent reads the user scope and the project folder together, so a
5
+ * lint run reports what the other side contributes to the same agent context:
6
+ * the user scope's agent outputs and whether its workspace has readable
7
+ * settings, and agent content in a project folder that has no workspace
8
+ * settings to explain it.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import * as FileSystem from "effect/FileSystem";
14
+ import * as Option from "effect/Option";
15
+ import * as Path from "effect/Path";
16
+ import { AGENTS } from "@agentxm/extension-model/unstable/agents/registry";
17
+ import { decodeAbsolutePathSync } from "@agentxm/extension-model/unstable/path-types";
18
+ import { CodingAgentRepository, observeAgentOutputs, } from "@agentxm/workspace-projection";
19
+ import { resolveUserWorkspaceLayout } from "@agentxm/workspace-state";
20
+ import { loadSettingsDocument } from "./settings.js";
21
+ const NO_EXPECTED_NAMES = {
22
+ skill: new Set(),
23
+ subagent: new Set(),
24
+ "mcp-server": new Set(),
25
+ hook: new Set(),
26
+ };
27
+ /**
28
+ * Observe the user scope's agent outputs under `userHome`.
29
+ *
30
+ * Containers resolve relative to `userHome` rather than the process home, so
31
+ * the observation follows the home the run selected. Anything under the user
32
+ * AXM home counts as AXM storage, including storage left by an earlier layout.
33
+ */
34
+ export const observeUserScope = (userHome) => Effect.gen(function* () {
35
+ const layout = yield* resolveUserWorkspaceLayout(decodeAbsolutePathSync(userHome));
36
+ const settings = yield* loadSettingsDocument(userHome, "user");
37
+ const inventory = yield* observeAgentOutputs({
38
+ workspaceRoot: userHome,
39
+ scope: "project",
40
+ desiredAgentIds: new Set(),
41
+ expectedNames: NO_EXPECTED_NAMES,
42
+ skillOwnershipRoots: [layout.axmHome],
43
+ authoredSkills: { layout, entries: {} },
44
+ });
45
+ return {
46
+ home: userHome,
47
+ settingsPath: layout.settingsPath,
48
+ settingsReadable: Option.isSome(settings),
49
+ outputs: inventory.outputs,
50
+ };
51
+ });
52
+ const instructionPath = (descriptor) => {
53
+ const instructions = descriptor.instructions;
54
+ if (instructions === undefined)
55
+ return undefined;
56
+ switch (instructions.kind) {
57
+ case "agents-md":
58
+ return "AGENTS.md";
59
+ case "own-file":
60
+ return instructions.file;
61
+ case "rules-dir":
62
+ return instructions.dir;
63
+ }
64
+ };
65
+ /**
66
+ * Observe agent instruction files and non-empty agent output containers in a
67
+ * project folder, grouped by path with the agents that read each one.
68
+ */
69
+ export const observeProjectAgentContent = (args) => Effect.gen(function* () {
70
+ const fs = yield* FileSystem.FileSystem;
71
+ const path = yield* Path.Path;
72
+ const instructionAgents = new Map();
73
+ for (const descriptor of Object.values(AGENTS)) {
74
+ const relative = instructionPath(descriptor);
75
+ if (relative === undefined)
76
+ continue;
77
+ const agents = instructionAgents.get(relative) ?? new Set();
78
+ agents.add(descriptor.id);
79
+ instructionAgents.set(relative, agents);
80
+ }
81
+ const instructions = yield* Effect.forEach([...instructionAgents], ([relative, agents]) => fs.exists(path.join(args.projectRoot, relative)).pipe(Effect.orElseSucceed(() => false), Effect.map((exists) => exists
82
+ ? [
83
+ {
84
+ kind: "instructions",
85
+ path: path.join(args.projectRoot, relative),
86
+ agentIds: [...agents].sort(),
87
+ },
88
+ ]
89
+ : [])), { concurrency: 8 });
90
+ const containers = new Map();
91
+ for (const output of args.outputs) {
92
+ const container = containers.get(output.containerPath) ?? {
93
+ kind: output.extensionType,
94
+ count: 0,
95
+ agents: new Set(),
96
+ };
97
+ container.count += 1;
98
+ for (const agentId of output.claimantAgentIds)
99
+ container.agents.add(agentId);
100
+ containers.set(output.containerPath, container);
101
+ }
102
+ return [
103
+ ...instructions.flat(),
104
+ ...[...containers].map(([containerPath, container]) => ({
105
+ kind: container.kind,
106
+ path: containerPath,
107
+ entryCount: container.count,
108
+ agentIds: [...container.agents].sort(),
109
+ })),
110
+ ].sort((left, right) => left.path.localeCompare(right.path));
111
+ });
112
+ //# sourceMappingURL=agent-scopes.js.map
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Authored packages in each type's standard authoring folder.
3
+ *
4
+ * A directory there is an authored package only when its manifest resolves
5
+ * with the identity its path names; anything else is a lookalike and is not
6
+ * observed here. Declaration is read from local settings, including disabled
7
+ * entries, so an undeclared package is visible without resolving desired
8
+ * state. User scope has no authoring folders.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import * as FileSystem from "effect/FileSystem";
14
+ import * as Path from "effect/Path";
15
+ import { type ExtensionType } from "@agentxm/extension-model/unstable/extensions/common";
16
+ import { type Settings, type WorkspaceLayout } from "@agentxm/workspace-state";
17
+ /** One valid authored package at its authoring location. */
18
+ export interface AuthoredPackageObservation {
19
+ readonly type: ExtensionType;
20
+ readonly name: string;
21
+ readonly owner: string;
22
+ readonly version: string;
23
+ /** Absolute authoring path. */
24
+ readonly path: string;
25
+ /** Whether settings declare this type and name with a workspace source. */
26
+ readonly declared: boolean;
27
+ }
28
+ export declare const observeAuthoredPackages: (args: {
29
+ readonly layout: WorkspaceLayout;
30
+ readonly settings: Settings;
31
+ }) => Effect.Effect<ReadonlyArray<AuthoredPackageObservation>, never, FileSystem.FileSystem | Path.Path>;
32
+ //# sourceMappingURL=authored-packages.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Authored packages in each type's standard authoring folder.
3
+ *
4
+ * A directory there is an authored package only when its manifest resolves
5
+ * with the identity its path names; anything else is a lookalike and is not
6
+ * observed here. Declaration is read from local settings, including disabled
7
+ * entries, so an undeclared package is visible without resolving desired
8
+ * state. User scope has no authoring folders.
9
+ *
10
+ * @experimental This API is unstable and may change without notice.
11
+ */
12
+ import * as Effect from "effect/Effect";
13
+ import * as FileSystem from "effect/FileSystem";
14
+ import * as Option from "effect/Option";
15
+ import * as Path from "effect/Path";
16
+ import * as Schema from "effect/Schema";
17
+ import { ExtensionNameSchema, extensionTypes, } from "@agentxm/extension-model/unstable/extensions/common";
18
+ import { resolveWorkspaceExtensionRef, settingsEntries, } from "@agentxm/workspace-state";
19
+ export const observeAuthoredPackages = (args) => Effect.gen(function* () {
20
+ const layout = args.layout;
21
+ if (layout.scope !== "project")
22
+ return [];
23
+ const fs = yield* FileSystem.FileSystem;
24
+ const path = yield* Path.Path;
25
+ const perType = yield* Effect.forEach(extensionTypes, (type) => Effect.gen(function* () {
26
+ const root = layout.authoredRoot(type);
27
+ const names = yield* fs.readDirectory(root).pipe(Effect.orElseSucceed(() => []));
28
+ const observed = [];
29
+ for (const name of [...names].sort()) {
30
+ if (!Schema.is(ExtensionNameSchema)(name))
31
+ continue;
32
+ const packagePath = path.join(root, name);
33
+ const isLink = yield* fs.readLink(packagePath).pipe(Effect.as(true), Effect.orElseSucceed(() => false));
34
+ if (isLink)
35
+ continue;
36
+ const ref = yield* resolveWorkspaceExtensionRef({
37
+ settingsName: name,
38
+ source: "workspace",
39
+ expectedType: type,
40
+ layout,
41
+ scope: "project",
42
+ }).pipe(Effect.option);
43
+ if (Option.isNone(ref))
44
+ continue;
45
+ observed.push({
46
+ type,
47
+ name,
48
+ owner: ref.value.owner,
49
+ version: ref.value.version,
50
+ path: packagePath,
51
+ declared: settingsEntries[type].entries(args.settings)[name]?.source === "workspace",
52
+ });
53
+ }
54
+ return observed;
55
+ }));
56
+ return perType.flat();
57
+ });
58
+ //# sourceMappingURL=authored-packages.js.map
@@ -26,7 +26,7 @@ import * as Path from "effect/Path";
26
26
  import type { WorkspaceScope } from "@agentxm/extension-model/unstable/workspace-scope";
27
27
  import { AxmSkillCompatibilityPolicy } from "@agentxm/cli-maintenance/official-skill/application";
28
28
  import { CodingAgentRepository, WorkspaceInvariantFacts } from "@agentxm/workspace-projection";
29
- import { WorkspaceMutations, acceptedCanonicalObservation } from "@agentxm/workspace-state";
29
+ import { LockfileReader, WorkspaceMutations, acceptedCanonicalObservation } from "@agentxm/workspace-state";
30
30
  import { buildLintWorkspace } from "../catalog/index.js";
31
31
  import type { LintView } from "../catalog-contexts.js";
32
32
  import { type LintExitCategory, type LintSummary } from "../cli.js";
@@ -95,7 +95,7 @@ export interface LintWorkspaceResult {
95
95
  readonly outcome: "success" | "fail";
96
96
  }
97
97
  /** Every service a lint run reads the workspace and its projections through. */
98
- export type LintWorkspaceRequirements = AxmSkillCompatibilityPolicy | CodingAgentRepository | FileSystem.FileSystem | Path.Path | WorkspaceInvariantFacts | WorkspaceMutations;
98
+ export type LintWorkspaceRequirements = AxmSkillCompatibilityPolicy | CodingAgentRepository | FileSystem.FileSystem | LockfileReader | Path.Path | WorkspaceInvariantFacts | WorkspaceMutations;
99
99
  /** Every failure a lint run can settle into. */
100
100
  export type LintWorkspaceFailure = LintStagingFailed | Effect.Error<ReturnType<typeof buildLintWorkspace>> | Effect.Error<ReturnType<typeof acceptedCanonicalObservation>> | Effect.Error<ReturnType<typeof applyDeterminedRepairs>>;
101
101
  /** Report the workspace's facts without changing it. */
@@ -28,9 +28,11 @@ import { buildPackRuleContexts, buildSkillRuleContexts } from "@agentxm/extensio
28
28
  import { decodeAbsolutePathSync } from "@agentxm/extension-model/unstable/path-types";
29
29
  import { AxmSkillCompatibilityPolicy } from "@agentxm/cli-maintenance/official-skill/application";
30
30
  import { CodingAgentRepository, WorkspaceInvariantFacts, expectedProjectionNames, observeAgentOutputs, observeWorkspaceOwnershipIssues, } from "@agentxm/workspace-projection";
31
- import { WorkspaceMutations, acceptedCanonicalObservation, } from "@agentxm/workspace-state";
31
+ import { LockfileReader, WorkspaceMutations, acceptedCanonicalObservation, observeInstallRoot, } from "@agentxm/workspace-state";
32
32
  import { buildLintWorkspace } from "../catalog/index.js";
33
33
  import { evaluateAllCatalogs, resolveLintExitCategory, summarizeEvaluations, toLintJsonDocument, } from "../cli.js";
34
+ import { observeAuthoredPackages } from "./authored-packages.js";
35
+ import { observeProjectAgentContent, observeUserScope, } from "./agent-scopes.js";
34
36
  import { LintStagingFailed } from "./errors.js";
35
37
  import { applyDeterminedRepairs, lintConfigFromSettings, loadSettingsDocument, remapLintSummaryPaths, resolveLintRoot, } from "./settings.js";
36
38
  import { materializeGitIndexWorkspace } from "./staged-workspace.js";
@@ -135,6 +137,32 @@ const runLint = (selection, options) => Effect.gen(function* () {
135
137
  skillOwnershipRoots,
136
138
  authoredSkills,
137
139
  });
140
+ // Cross-scope agent facts. The agents that read this workspace also read
141
+ // the user scope; a project folder that is the user home has no separate
142
+ // user scope, and a Git-index snapshot has no live agent folders.
143
+ const liveView = selection.input.view === "workspace";
144
+ const realRoot = (root) => fileSystem.realPath(root).pipe(Effect.orElseSucceed(() => path.resolve(root)));
145
+ const projectIsUserHome = selection.scope === "project" &&
146
+ (yield* realRoot(selection.workspaceRoot)) === (yield* realRoot(userHome));
147
+ const userScope = liveView && !projectIsUserHome
148
+ ? Option.some(yield* observeUserScope(userHome))
149
+ : Option.none();
150
+ const agentContent = liveView && selection.scope === "project" && !projectIsUserHome && Option.isNone(settings)
151
+ ? Option.some(yield* observeProjectAgentContent({
152
+ projectRoot: workspace.baseDir,
153
+ outputs: agentOutputs.outputs,
154
+ }))
155
+ : Option.none();
156
+ // Install-root and authoring-folder facts. An unreadable lockfile leaves
157
+ // the install root unobserved; its own rule reports the lockfile.
158
+ const installRoot = yield* observeInstallRoot({
159
+ layout: workspace.layout,
160
+ graph: desiredGraph,
161
+ locks: yield* LockfileReader,
162
+ }).pipe(Effect.option);
163
+ const authoredPackages = Option.isSome(settings)
164
+ ? yield* observeAuthoredPackages({ layout: workspace.layout, settings: settings.value })
165
+ : [];
138
166
  const canonicalObservations = Effect.gen(function* () {
139
167
  const graph = yield* workspace.getDesiredStateGraph();
140
168
  return yield* Effect.forEach(graph.nodes, (node) => acceptedCanonicalObservation({
@@ -163,6 +191,14 @@ const runLint = (selection, options) => Effect.gen(function* () {
163
191
  ...workspaceContext,
164
192
  ownership: Effect.succeed(ownership),
165
193
  agentOutputs: Effect.succeed(agentOutputs),
194
+ ...(Option.isSome(installRoot)
195
+ ? { installRoot: Effect.succeed(installRoot.value) }
196
+ : {}),
197
+ authoredPackages: Effect.succeed(authoredPackages),
198
+ ...(Option.isSome(userScope) ? { userScope: Effect.succeed(userScope.value) } : {}),
199
+ ...(Option.isSome(agentContent)
200
+ ? { agentContent: Effect.succeed(agentContent.value) }
201
+ : {}),
166
202
  health: {
167
203
  desiredState: workspace.getDesiredStateGraph(),
168
204
  canonicalObservations,
@@ -65,6 +65,7 @@ export const lintWorkspaceServices = (args) => {
65
65
  const state = WorkspaceStateLive({
66
66
  scope: "project",
67
67
  projectRoot: decodeAbsolutePathSync(args.workspaceRoot),
68
+ allowUninitialized: true,
68
69
  });
69
70
  const agents = Layer.provideMerge(Layer.mergeAll(CodingAgentRepositoryLive, ConfiguredAgentOutcomesProviderTest), state);
70
71
  return Layer.provideMerge(Layer.mergeAll(WorkspaceInvariantFactsLive.pipe(Layer.provide(NativeWriteAuthorityLive)), makeAxmSkillCompatibilityPolicyLayer(args.cliVersion ?? null)), agents);
@@ -14,7 +14,9 @@ import type { ExtensionType } from "@agentxm/extension-model/unstable/extensions
14
14
  import type { Handle } from "@agentxm/extension-model/unstable/extensions/handle";
15
15
  import type { PackDependencyReachability, AgentOutputInventory, WorkspaceOwnershipIssue, InstructionProjectionSnapshot } from "@agentxm/workspace-projection";
16
16
  import type { ProjectionInvariantFact } from "@agentxm/workspace-projection";
17
- import type { CanonicalObservation } from "@agentxm/workspace-state";
17
+ import type { CanonicalObservation, InstallRootInventory } from "@agentxm/workspace-state";
18
+ import type { AuthoredPackageObservation } from "./run/authored-packages.js";
19
+ import type { AgentContentEntry, UserScopeObservation } from "./run/agent-scopes.js";
18
20
  import type { DesiredExtensionNode, DesiredStateGraph } from "@agentxm/workspace-state";
19
21
  import type { LockfileReadError, SettingsReadError } from "@agentxm/workspace-state";
20
22
  import type { WorkspaceReadModel } from "@agentxm/workspace-state";
@@ -50,6 +52,14 @@ export interface WorkspaceRuleContext {
50
52
  readonly ownership?: Effect.Effect<ReadonlyArray<WorkspaceOwnershipIssue>>;
51
53
  /** Owned and unowned agent-native outputs observed from resolved containers. */
52
54
  readonly agentOutputs?: Effect.Effect<AgentOutputInventory>;
55
+ /** The scope's install root: installed packages, leftovers, and unrecognized entries. */
56
+ readonly installRoot?: Effect.Effect<InstallRootInventory>;
57
+ /** Valid authored packages in each type's standard authoring folder (project scope). */
58
+ readonly authoredPackages?: Effect.Effect<ReadonlyArray<AuthoredPackageObservation>>;
59
+ /** The user scope's agent outputs and settings readability; absent when it is the project itself. */
60
+ readonly userScope?: Effect.Effect<UserScopeObservation>;
61
+ /** Agent-native content in a project folder without workspace settings. */
62
+ readonly agentContent?: Effect.Effect<ReadonlyArray<AgentContentEntry>>;
53
63
  /**
54
64
  * Installed non-pack extension manifests, keyed by nothing — rules walk the
55
65
  * list. Landed for `workspace/recommended-packs-retained`, which needs the
package/package.json CHANGED
@@ -4,15 +4,15 @@
4
4
  "url": "https://github.com/agentxm/axm/issues"
5
5
  },
6
6
  "dependencies": {
7
- "@agentxm/agent-integration": "^0.30.1",
8
- "@agentxm/cli-maintenance": "^0.30.1",
9
- "@agentxm/extension-content": "^0.30.1",
10
- "@agentxm/extension-model": "^0.30.1",
11
- "@agentxm/extension-resolution": "^0.30.1",
12
- "@agentxm/extension-sources": "^0.30.1",
13
- "@agentxm/workspace-operations": "^0.30.1",
14
- "@agentxm/workspace-projection": "^0.30.1",
15
- "@agentxm/workspace-state": "^0.30.1",
7
+ "@agentxm/agent-integration": "^0.30.2",
8
+ "@agentxm/cli-maintenance": "^0.30.2",
9
+ "@agentxm/extension-content": "^0.30.2",
10
+ "@agentxm/extension-model": "^0.30.2",
11
+ "@agentxm/extension-resolution": "^0.30.2",
12
+ "@agentxm/extension-sources": "^0.30.2",
13
+ "@agentxm/workspace-operations": "^0.30.2",
14
+ "@agentxm/workspace-projection": "^0.30.2",
15
+ "@agentxm/workspace-state": "^0.30.2",
16
16
  "effect": "4.0.0-rc.115"
17
17
  },
18
18
  "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.",
@@ -57,5 +57,5 @@
57
57
  },
58
58
  "sideEffects": false,
59
59
  "type": "module",
60
- "version": "0.30.1"
60
+ "version": "0.30.2"
61
61
  }