@nseng-ai/areg 0.1.1 → 0.1.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 (43) hide show
  1. package/package.json +7 -6
  2. package/src/cli.ts +0 -33
  3. package/src/context.ts +3 -29
  4. package/src/fake-gateways.ts +108 -380
  5. package/src/gateways/errors.ts +2 -2
  6. package/src/gateways/fs-utils.ts +2 -2
  7. package/src/gateways/project-fs.ts +29 -41
  8. package/src/gateways/project-gateway.ts +98 -31
  9. package/src/gateways.ts +34 -150
  10. package/src/index.ts +2 -28
  11. package/src/operations/check.ts +33 -20
  12. package/src/operations/doctor-skills.ts +71 -7
  13. package/src/operations/file-state.ts +4 -4
  14. package/src/operations/manifest-source-findings.ts +45 -0
  15. package/src/operations/manifest-sources.ts +119 -0
  16. package/src/operations/pi-settings.ts +2 -5
  17. package/src/operations/project-inspection.ts +32 -28
  18. package/src/operations/project-mutations.ts +5 -21
  19. package/src/operations/project-resolution.ts +2 -2
  20. package/src/operations/skill-find.ts +23 -3
  21. package/src/operations/skill-kind-apply-plan.ts +2 -2
  22. package/src/operations/skill-kind-frontmatter.ts +1 -1
  23. package/src/operations/skill-kind-inference.ts +30 -14
  24. package/src/operations/skill-kind.ts +34 -12
  25. package/src/sort.ts +3 -3
  26. package/src/gateways/command-constants.ts +0 -1
  27. package/src/gateways/github-gateway.ts +0 -111
  28. package/src/gateways/host-gateway.ts +0 -35
  29. package/src/gateways/mutation-policy.ts +0 -94
  30. package/src/gateways/npx-skills-gateway.ts +0 -53
  31. package/src/gateways/prompt-gateway.ts +0 -21
  32. package/src/gateways/skillx-workspace-gateway.ts +0 -220
  33. package/src/operations/frontmatter.ts +0 -151
  34. package/src/operations/init.ts +0 -548
  35. package/src/operations/lockfile.ts +0 -107
  36. package/src/operations/managed-markdown-block.ts +0 -47
  37. package/src/operations/project-agents.ts +0 -115
  38. package/src/operations/skill-mirror-conventions.ts +0 -126
  39. package/src/operations/skillx.ts +0 -305
  40. package/src/operations/toml-section.ts +0 -53
  41. package/src/operations/update-skills.ts +0 -325
  42. package/src/real-gateways.ts +0 -6
  43. package/src/skill-lookup.ts +0 -254
package/src/gateways.ts CHANGED
@@ -1,135 +1,35 @@
1
1
  import {
2
2
  SKILL_LOOKUP_ROOT_DESCRIPTORS,
3
3
  skillLookupDescriptorForSourceType,
4
- } from "./skill-lookup.ts";
4
+ } from "@nseng-ai/foundation/skill-lookup";
5
5
  import type {
6
6
  SkillLookupRoot,
7
7
  SkillLookupRootDescriptor,
8
8
  SkillLookupSourceType,
9
- } from "./skill-lookup.ts";
9
+ } from "@nseng-ai/foundation/skill-lookup";
10
+ import type { GitGateway } from "@nseng-ai/capability-kit/git";
10
11
  import type { ErrorInfo, Result } from "@nseng-ai/foundation/result";
12
+ import type { PathState, TextFileState } from "@nseng-ai/harness-artifacts/api";
11
13
 
12
- export const AREG_HOST_TOOL_NAMES = ["gh", "npx"] as const;
13
- export type AregHostToolName = (typeof AREG_HOST_TOOL_NAMES)[number];
14
+ import type { AregManifestSkillSourcesInspection } from "./operations/manifest-sources.ts";
14
15
 
15
- export interface AregErrorInfo extends ErrorInfo {
16
- displayCommand?: string;
17
- }
18
-
19
- export type AregOperationResult = Result<undefined, AregErrorInfo>;
20
-
21
- export type AregToolCheckResult =
22
- | { type: "found"; tool: AregHostToolName; path: string }
23
- | { type: "missing"; tool: AregHostToolName; message: string };
24
-
25
- export interface AregHostGateway {
26
- checkTool(options: {
27
- tool: AregHostToolName;
28
- cwd: string;
29
- env: NodeJS.ProcessEnv;
30
- }): Promise<AregToolCheckResult>;
31
- }
32
-
33
- export type AregGithubSkillListResult =
34
- | { type: "ok"; skillNames: readonly string[] }
35
- | { type: "missing"; message: string }
36
- | { type: "auth-error"; message: string }
37
- | { type: "error"; error: AregErrorInfo };
38
-
39
- export type AregGithubSkillFileResult =
40
- | { type: "found" }
41
- | { type: "missing"; message: string }
42
- | { type: "auth-error"; message: string }
43
- | { type: "error"; error: AregErrorInfo };
44
-
45
- export interface AregGithubGateway {
46
- listSkillDirectoryNames(options: {
47
- repo: string;
48
- ref?: string;
49
- env: NodeJS.ProcessEnv;
50
- }): Promise<AregGithubSkillListResult>;
51
- checkSkillFile(options: {
52
- repo: string;
53
- path: string;
54
- ref?: string;
55
- env: NodeJS.ProcessEnv;
56
- }): Promise<AregGithubSkillFileResult>;
57
- }
58
-
59
- export interface AregNpxSkillsAddRequest {
60
- sourceRepo: string;
61
- /** Empty means install all skills from the source repository. */
62
- skillNames: readonly string[];
63
- targetAgents: readonly string[];
64
- cwd: string;
65
- env: NodeJS.ProcessEnv;
66
- }
67
-
68
- export type AregNpxSkillsAddResult = { type: "ok" } | { type: "error"; error: AregErrorInfo };
69
-
70
- export interface AregNpxSkillsGateway {
71
- addSkills(request: AregNpxSkillsAddRequest): Promise<AregNpxSkillsAddResult>;
72
- }
73
-
74
- export interface AregPromptGateway {
75
- confirm(request: { message: string; defaultValue: boolean }): Promise<boolean>;
76
- }
77
-
78
- export interface AregSkillxInstalledSkill {
79
- name: string;
80
- directory: string;
81
- skillFile: string;
82
- relativeFiles: readonly string[];
83
- }
84
-
85
- export interface AregSkillxWorkspaceInstall {
86
- workspaceRoot: string;
87
- installedSkills: readonly AregSkillxInstalledSkill[];
88
- }
89
-
90
- export interface AregSkillxInstallRequest {
91
- sourceRepo: string;
92
- skillName?: string;
93
- cwd: string;
94
- env: NodeJS.ProcessEnv;
95
- }
96
-
97
- export interface AregSkillxWorkspaceCleanupRequest {
98
- workspaceRoot: string;
99
- }
16
+ // The git methods areg consumes: resolve the repo root and materialize
17
+ // worktree-relative git paths (for example `info/exclude`). A full `GitGateway`
18
+ // is assignable to this narrowed surface.
19
+ export type AregGitGateway = Pick<GitGateway, "optionalRepoRoot" | "gitPath">;
100
20
 
101
- export type AregSkillxInstallResult =
102
- | { type: "ok"; workspace: AregSkillxWorkspaceInstall }
103
- | { type: "error"; error: AregErrorInfo };
21
+ export type AregErrorInfo = ErrorInfo;
104
22
 
105
- export interface AregSkillxWorkspaceGateway {
106
- installIntoWorkspace(request: AregSkillxInstallRequest): Promise<AregSkillxInstallResult>;
107
- cleanupWorkspace(request: AregSkillxWorkspaceCleanupRequest): Promise<AregOperationResult>;
108
- }
109
-
110
- export type AregPathState =
111
- | { type: "missing" }
112
- | { type: "file" }
113
- | { type: "directory" }
114
- | { type: "symlink"; target: string }
115
- | { type: "other" };
116
-
117
- export type AregTextFileState =
118
- | { type: "missing" }
119
- | { type: "file"; text: string }
120
- | { type: "directory" }
121
- | { type: "symlink"; target: string }
122
- | { type: "other" }
123
- | { type: "unreadable"; message: string };
23
+ export type AregOperationResult = Result<undefined, AregErrorInfo>;
124
24
 
125
25
  export interface AregCheckSkillInspection {
126
26
  name: string;
127
- skillsPath: AregPathState;
128
- agentsPath: AregPathState;
129
- claudePath: AregPathState;
130
- localSkillMd: AregTextFileState;
131
- remoteSkillMd: AregTextFileState;
132
- openaiPolicy: AregTextFileState;
27
+ skillsPath: PathState;
28
+ agentsPath: PathState;
29
+ claudePath: PathState;
30
+ localSkillMd: TextFileState;
31
+ remoteSkillMd: TextFileState;
32
+ openaiPolicy: TextFileState;
133
33
  }
134
34
 
135
35
  export function missingCheckSkillInspection(name: string): AregCheckSkillInspection {
@@ -178,8 +78,8 @@ export interface AregSkillFindSkillInspection {
178
78
  root: SkillLookupRoot;
179
79
  sourceType: SkillLookupSourceType;
180
80
  baseRelativePath: string;
181
- skillDir: AregPathState;
182
- skillMd: AregTextFileState;
81
+ skillDir: PathState;
82
+ skillMd: TextFileState;
183
83
  }
184
84
 
185
85
  export interface AregSkillFindRootsInspection {
@@ -190,11 +90,11 @@ export interface AregSkillKindSkillInspection {
190
90
  name: string;
191
91
  sourceType: AregSkillKindSourceType;
192
92
  baseRelativePath: string;
193
- skillDir: AregPathState;
194
- skillMd: AregTextFileState;
195
- openaiPolicy: AregTextFileState;
196
- agentsPath: AregPathState;
197
- claudePath: AregPathState;
93
+ skillDir: PathState;
94
+ skillMd: TextFileState;
95
+ openaiPolicy: TextFileState;
96
+ agentsPath: PathState;
97
+ claudePath: PathState;
198
98
  }
199
99
 
200
100
  export interface AregReplacementInspection {
@@ -220,22 +120,15 @@ export interface AregSkillInspectionRequest {
220
120
 
221
121
  export interface AregProjectBaseInspection {
222
122
  projectDir: string;
223
- projectPathState: AregPathState;
224
- lockfile: AregTextFileState;
225
- nsToml: AregTextFileState;
226
- aregJson: AregTextFileState;
227
- }
228
-
229
- export interface AregInstructionFilesInspection {
230
- agentsMd: AregTextFileState;
231
- claudeMd: AregTextFileState;
232
- claudeDir: AregPathState;
233
- claudeSettings: AregTextFileState;
123
+ projectPathState: PathState;
124
+ lockfile: TextFileState;
125
+ nsToml: TextFileState;
126
+ aregJson: TextFileState;
234
127
  }
235
128
 
236
129
  export interface AregPiArtifactsInspection {
237
- piDir: AregPathState;
238
- piSettings: AregTextFileState;
130
+ piDir: PathState;
131
+ piSettings: TextFileState;
239
132
  replacement: AregReplacementInspection;
240
133
  }
241
134
 
@@ -263,15 +156,12 @@ export type AregSkillKindResolveResult =
263
156
  | { type: "ok"; skillName: string }
264
157
  | { type: "error"; error: AregErrorInfo };
265
158
 
266
- export type AregProjectMutationPolicy = "init" | "skill-kind";
267
-
268
159
  export interface AregProjectTextWriteRequest {
269
160
  projectDir: string;
270
161
  relativePath: string;
271
162
  content: string;
272
163
  description: string;
273
164
  createParent: boolean;
274
- policy: AregProjectMutationPolicy;
275
165
  env: NodeJS.ProcessEnv;
276
166
  }
277
167
 
@@ -279,7 +169,6 @@ export interface AregProjectManagedTargetRequest {
279
169
  projectDir: string;
280
170
  relativePath: string;
281
171
  description: string;
282
- policy: "skill-kind";
283
172
  env: NodeJS.ProcessEnv;
284
173
  }
285
174
 
@@ -298,15 +187,17 @@ export type AregProjectRemoveEmptyDirResult =
298
187
  * This is intentionally domain-oriented: it exposes named areg project facts and
299
188
  * project-scoped safe mutation primitives, not a generic filesystem API. Add new
300
189
  * reads here only when they represent stable areg project concepts, and route new
301
- * writes/deletes through project-scoped primitives with an explicit policy. Do not
190
+ * writes/deletes through project-scoped primitives. Do not
302
191
  * add an unrestricted filesystem gateway to areg as a convenience for operation code.
303
192
  */
304
193
  export interface AregProjectGateway {
305
194
  inspectProjectBase(request: AregProjectInspectionRequest): Promise<AregProjectBaseInspection>;
306
- inspectInstructionFiles(request: AregProjectDirRequest): Promise<AregInstructionFilesInspection>;
307
195
  inspectPiArtifacts(request: AregProjectDirRequest): Promise<AregPiArtifactsInspection>;
308
196
  inspectPiSkillInventory(request: AregProjectDirRequest): Promise<AregPiSkillInventoryInspection>;
309
197
  inspectSkillNameInventory(request: AregProjectDirRequest): Promise<AregSkillNameInventory>;
198
+ inspectManifestSkillSources(
199
+ request: AregProjectDirRequest,
200
+ ): Promise<AregManifestSkillSourcesInspection>;
310
201
  inspectSkillFindRoots(request: AregProjectDirRequest): Promise<AregSkillFindRootsInspection>;
311
202
  inspectCheckSkill(request: AregSkillInspectionRequest): Promise<AregCheckSkillInspection>;
312
203
  inspectSkillKindSkill(request: AregSkillInspectionRequest): Promise<AregSkillKindSkillInspection>;
@@ -331,13 +222,6 @@ export interface AregProjectGateway {
331
222
  ): Promise<AregProjectRemoveEmptyDirResult>;
332
223
  }
333
224
 
334
- export interface AregInitTextWritePlan {
335
- relativePath: "ns.toml" | "AGENTS.md" | "CLAUDE.md" | ".claude/settings.local.json";
336
- content: string;
337
- description: string;
338
- createParent: boolean;
339
- }
340
-
341
225
  export interface AregSkillKindTextWritePlan {
342
226
  relativePath: string;
343
227
  content: string;
package/src/index.ts CHANGED
@@ -5,44 +5,18 @@ export type {
5
5
  AregCheckSkillInspection,
6
6
  AregErrorInfo,
7
7
  AregReplacementInspection,
8
- AregGithubGateway,
9
- AregGithubSkillListResult,
10
- AregHostGateway,
11
- AregHostToolName,
12
- AregNpxSkillsAddRequest,
13
- AregNpxSkillsAddResult,
14
- AregNpxSkillsGateway,
15
8
  AregOperationResult,
16
- AregPathState,
17
9
  AregProjectBaseInspection,
18
10
  AregProjectDirRequest,
19
11
  AregProjectGateway,
20
12
  AregProjectInspectionRequest,
21
- AregProjectMutationPolicy,
22
13
  AregProjectMutationResult,
23
14
  AregProjectRemoveEmptyDirResult,
24
15
  AregProjectTextWriteRequest,
25
- AregPromptGateway,
26
16
  AregSkillInspectionRequest,
27
17
  AregSkillKindResolveRequest,
28
18
  AregSkillKindResolveResult,
29
19
  AregSkillKindSkillInspection,
30
- AregSkillxInstallRequest,
31
- AregSkillxInstallResult,
32
- AregSkillxInstalledSkill,
33
- AregSkillxWorkspaceCleanupRequest,
34
- AregSkillxWorkspaceGateway,
35
- AregSkillxWorkspaceInstall,
36
- AregTextFileState,
37
- AregToolCheckResult,
38
20
  } from "./gateways.ts";
39
- export { parseLockfileData, parseSkillFrontmatterText } from "./operations/check.ts";
40
- export { parseSkillInput } from "./operations/skillx.ts";
41
- export {
42
- buildNpxSkillsAddArgs,
43
- RealAregGithubGateway,
44
- RealAregHostGateway,
45
- RealAregNpxSkillsGateway,
46
- RealAregProjectGateway,
47
- RealAregSkillxWorkspaceGateway,
48
- } from "./real-gateways.ts";
21
+ export { parseSkillFrontmatterText } from "./operations/check.ts";
22
+ export { RealAregProjectGateway } from "./gateways/project-gateway.ts";
@@ -2,26 +2,25 @@ import { failure, ok, negative, type ClinkrExit } from "@nseng-ai/clinkr";
2
2
  import type { Result } from "@nseng-ai/foundation/result";
3
3
  import { z } from "zod";
4
4
 
5
- import type { AregCliContext } from "../context.ts";
6
- import { missingCheckSkillInspection } from "../gateways.ts";
7
- import type { AregCheckSkillInspection } from "../gateways.ts";
8
- import { sortStrings } from "../sort.ts";
9
- import { isPathStateError } from "./file-state.ts";
10
- import { parseSkillFrontmatterBlock } from "./frontmatter.ts";
11
5
  import {
6
+ expectedAgentsSkillSymlinkTarget,
7
+ expectedClaudeSkillSymlinkTarget,
8
+ isAgentsSkillMirror,
9
+ isClaudeSkillMirror,
12
10
  parseInspectedLockfile,
13
- parseLockfileData,
11
+ parseSkillFrontmatterBlock,
14
12
  type LockfileSkill,
15
13
  type SkillsLockfile,
16
- } from "./lockfile.ts";
14
+ } from "@nseng-ai/harness-artifacts/api";
15
+
16
+ import type { AregCliContext } from "../context.ts";
17
+ import { missingCheckSkillInspection } from "../gateways.ts";
18
+ import { sortStringsLocaleAware } from "../sort.ts";
19
+ import { manifestSourceFinding } from "./manifest-source-findings.ts";
20
+ import type { AregCheckSkillInspection } from "../gateways.ts";
21
+ import { isPathStateError } from "./file-state.ts";
17
22
  import { verifyPiReplacement } from "./pi-replacement.ts";
18
23
  import { parsePiSettings } from "./pi-settings.ts";
19
- import {
20
- expectedAgentsSkillSymlinkTarget,
21
- expectedClaudeSkillSymlinkTarget,
22
- isAgentsSkillMirror,
23
- isClaudeSkillMirror,
24
- } from "./skill-mirror-conventions.ts";
25
24
  import {
26
25
  inferSkillKindRecord,
27
26
  inspectSkillFrontmatter,
@@ -58,6 +57,9 @@ const CHECK_ISSUE_CODES = [
58
57
  "agents-md-missing-peer",
59
58
  "claude-md-missing-agents-ref",
60
59
  "pi-settings-unusable",
60
+ "invalid-install-manifest",
61
+ "manifest-skill-target-missing",
62
+ "manifest-skill-md-missing",
61
63
  ] as const;
62
64
 
63
65
  const MAX_SKILL_DESCRIPTION_CHARS = 1024;
@@ -71,8 +73,6 @@ interface CheckIssue {
71
73
  message: string;
72
74
  }
73
75
 
74
- export { parseLockfileData };
75
-
76
76
  const checkIssueSchema = z.object({
77
77
  skill: z.string(),
78
78
  code: z.enum(CHECK_ISSUE_CODES),
@@ -151,6 +151,7 @@ export function buildCheckReport(
151
151
  issues.push(...checkSkillInvocationKind(entry, inspected, record));
152
152
  }
153
153
  issues.push(...checkLockfileHashes(lockfile));
154
+ issues.push(...checkManifestSkillSources(inspection));
154
155
  issues.push(...checkOrphansAndDangling(lockfile, inspection));
155
156
  issues.push(...checkPairing(inspection));
156
157
  return {
@@ -175,7 +176,7 @@ export function formatCheckReport(report: Pick<CheckReport, "issues">): string {
175
176
  grouped.set(issue.skill, existing);
176
177
  }
177
178
  const lines: string[] = [];
178
- for (const skill of sortStrings([...grouped.keys()])) {
179
+ for (const skill of sortStringsLocaleAware([...grouped.keys()])) {
179
180
  lines.push("", `${skill}:`);
180
181
  for (const issue of grouped.get(skill) ?? []) lines.push(` ${issue.message}`);
181
182
  }
@@ -183,6 +184,18 @@ export function formatCheckReport(report: Pick<CheckReport, "issues">): string {
183
184
  return lines.join("\n");
184
185
  }
185
186
 
187
+ function checkManifestSkillSources(inspection: CheckProjectInspection): CheckIssue[] {
188
+ const issues: CheckIssue[] = [];
189
+ for (const error of inspection.manifestSkillSources.errors) {
190
+ issues.push(issue("project", "invalid-install-manifest", error.message));
191
+ }
192
+ for (const source of inspection.manifestSkillSources.sources) {
193
+ const finding = manifestSourceFinding(source);
194
+ if (finding !== undefined) issues.push(issue(source.skillName, finding.code, finding.message));
195
+ }
196
+ return issues;
197
+ }
198
+
186
199
  function checkLocalSkill(
187
200
  entry: LockfileSkill,
188
201
  inspected: AregCheckSkillInspection,
@@ -470,7 +483,7 @@ function checkOrphansAndDangling(
470
483
  const excluded = new Set(inspection.excludedSkillNames);
471
484
  const byName = new Map(inspection.skills.map((skill) => [skill.name, skill]));
472
485
  const issues: CheckIssue[] = [];
473
- for (const name of sortStrings(inspection.skillsDirectoryNames)) {
486
+ for (const name of sortStringsLocaleAware(inspection.skillsDirectoryNames)) {
474
487
  if (!lockNames.has(name) && !excluded.has(name))
475
488
  issues.push(
476
489
  issue(
@@ -480,7 +493,7 @@ function checkOrphansAndDangling(
480
493
  ),
481
494
  );
482
495
  }
483
- for (const name of sortStrings(inspection.agentsSkillNames)) {
496
+ for (const name of sortStringsLocaleAware(inspection.agentsSkillNames)) {
484
497
  if (!lockNames.has(name) && !excluded.has(name))
485
498
  issues.push(
486
499
  issue(
@@ -490,7 +503,7 @@ function checkOrphansAndDangling(
490
503
  ),
491
504
  );
492
505
  }
493
- for (const name of sortStrings([...lockNames])) {
506
+ for (const name of sortStringsLocaleAware([...lockNames])) {
494
507
  const inspected = byName.get(name) ?? missingCheckSkillInspection(name);
495
508
  if (
496
509
  inspected.skillsPath.type === "missing" &&
@@ -4,16 +4,25 @@ import { optionalEntries, optionalEntry } from "@nseng-ai/foundation/primitives"
4
4
  import { z } from "zod";
5
5
 
6
6
  import type { AregCliContext } from "../context.ts";
7
+ import type { PathState, TextFileState } from "@nseng-ai/harness-artifacts/api";
7
8
  import type {
8
9
  AregCheckSkillInspection,
9
- AregPathState,
10
10
  AregPiSkillInventoryInspection,
11
11
  AregProjectBaseInspection,
12
12
  AregReplacementInspection,
13
13
  AregSkillKindSkillInspection,
14
14
  AregSkillNameInventory,
15
- AregTextFileState,
16
15
  } from "../gateways.ts";
16
+ import {
17
+ MANIFEST_FAILURE_CODE,
18
+ MANIFEST_FAILURE_REMEDIATION,
19
+ manifestSourceFinding,
20
+ } from "./manifest-source-findings.ts";
21
+ import {
22
+ manifestSkillKindNames,
23
+ toManifestSkillSourceView,
24
+ type AregManifestSkillSourcesInspection,
25
+ } from "./manifest-sources.ts";
17
26
  import { uniqueSortedStrings } from "../sort.ts";
18
27
  import { renderDoctorSkills } from "./doctor-skills-report.ts";
19
28
  import {
@@ -24,7 +33,7 @@ import {
24
33
  import { parsePiSettings } from "./pi-settings.ts";
25
34
  import { collectCheckSkillInspections, collectSkillKindInspections } from "./project-inspection.ts";
26
35
  import { buildSkillKindRecords, type SkillKindRecord } from "./skill-kind-inference.ts";
27
- import { isAgentsSkillMirror, isClaudeSkillMirror } from "./skill-mirror-conventions.ts";
36
+ import { isAgentsSkillMirror, isClaudeSkillMirror } from "@nseng-ai/harness-artifacts/api";
28
37
 
29
38
  const doctorStatusValues = ["ok", "warning", "error"] as const;
30
39
 
@@ -57,8 +66,9 @@ export interface DoctorSkillsInspection {
57
66
  skillInventory: AregSkillNameInventory;
58
67
  piSkillInventory: AregPiSkillInventoryInspection;
59
68
  replacement: AregReplacementInspection;
60
- piDir: AregPathState;
61
- piSettings: AregTextFileState;
69
+ manifestSkillSources: AregManifestSkillSourcesInspection;
70
+ piDir: PathState;
71
+ piSettings: TextFileState;
62
72
  checkSkills: readonly AregCheckSkillInspection[];
63
73
  skillKindSkills: readonly AregSkillKindSkillInspection[];
64
74
  }
@@ -162,11 +172,13 @@ export function buildDoctorSkillFindings(
162
172
  piDir: inspection.piDir,
163
173
  piSettings: inspection.piSettings,
164
174
  replacement: inspection.replacement,
175
+ manifestSkillSources: inspection.manifestSkillSources,
165
176
  skills: inspection.skillKindSkills,
166
177
  });
167
178
  const records = skillKindRecords.ok ? skillKindRecords.value : [];
168
179
  const findings: DoctorSkillFinding[] = [];
169
180
  findings.push(...filesystemFindings(inspection));
181
+ findings.push(...manifestFindings(inspection));
170
182
  findings.push(...piInventoryFindings(inspection));
171
183
  findings.push(...replacementFindings(records, piSettings.value.exclusions));
172
184
  if (!skillKindRecords.ok) {
@@ -206,9 +218,21 @@ async function inspectDoctorSkillsProject(
206
218
  const piArtifacts = await ctx.project.inspectPiArtifacts({ projectDir, env: ctx.env });
207
219
  const skillInventory = await ctx.project.inspectSkillNameInventory({ projectDir, env: ctx.env });
208
220
  const piSkillInventory = await ctx.project.inspectPiSkillInventory({ projectDir, env: ctx.env });
209
- const skillNames = allKnownSkillNames(skillInventory, { includeSkillKindNames: true });
221
+ const manifestSkillSources = await ctx.project.inspectManifestSkillSources({
222
+ projectDir,
223
+ env: ctx.env,
224
+ });
225
+ const baseSkillNames = allKnownSkillNames(skillInventory, { includeSkillKindNames: true });
226
+ const skillNames = uniqueSortedStrings([
227
+ ...baseSkillNames,
228
+ ...manifestSkillSources.sources.map((source) => source.skillName),
229
+ ]);
210
230
  const checkSkills = await collectCheckSkillInspections(ctx, projectDir, skillNames);
211
- const skillKindSkills = await collectSkillKindInspections(ctx, projectDir, skillNames);
231
+ const skillKindNames = uniqueSortedStrings([
232
+ ...baseSkillNames,
233
+ ...manifestSkillKindNames(manifestSkillSources.sources),
234
+ ]);
235
+ const skillKindSkills = await collectSkillKindInspections(ctx, projectDir, skillKindNames);
212
236
  return {
213
237
  type: "ok",
214
238
  value: {
@@ -217,6 +241,7 @@ async function inspectDoctorSkillsProject(
217
241
  skillInventory,
218
242
  piSkillInventory,
219
243
  replacement: piArtifacts.replacement,
244
+ manifestSkillSources,
220
245
  piDir: piArtifacts.piDir,
221
246
  piSettings: piArtifacts.piSettings,
222
247
  checkSkills,
@@ -283,6 +308,45 @@ function filesystemFindings(inspection: DoctorSkillsInspection): readonly Doctor
283
308
  return findings;
284
309
  }
285
310
 
311
+ function manifestFindings(inspection: DoctorSkillsInspection): readonly DoctorSkillFinding[] {
312
+ const findings: DoctorSkillFinding[] = [];
313
+ for (const error of inspection.manifestSkillSources.errors) {
314
+ findings.push({
315
+ code: MANIFEST_FAILURE_CODE,
316
+ severity: "error",
317
+ message: error.message,
318
+ remediation: MANIFEST_FAILURE_REMEDIATION,
319
+ path: error.type === "manifest" ? error.manifestPath : error.harness,
320
+ });
321
+ }
322
+ for (const source of inspection.manifestSkillSources.sources) {
323
+ const evidence = toManifestSkillSourceView(source);
324
+ const finding = manifestSourceFinding(source);
325
+ if (finding !== undefined) {
326
+ findings.push({
327
+ code: finding.code,
328
+ severity: "warning",
329
+ skill: source.skillName,
330
+ path: finding.path,
331
+ message: finding.message,
332
+ remediation: finding.remediation,
333
+ evidence,
334
+ });
335
+ } else {
336
+ findings.push({
337
+ code: "manifest-skill-source",
338
+ severity: "info",
339
+ skill: source.skillName,
340
+ path: source.targetSkillRelativePath,
341
+ message: `${source.skillName} is tracked by shared manifest entry ${source.manifestKey} from ${source.provenance.packageName}@${source.provenance.version}.`,
342
+ remediation: "No action required unless this manifest provenance is stale.",
343
+ evidence,
344
+ });
345
+ }
346
+ }
347
+ return findings;
348
+ }
349
+
286
350
  function piInventoryFindings(inspection: DoctorSkillsInspection): readonly DoctorSkillFinding[] {
287
351
  const findings: DoctorSkillFinding[] = [];
288
352
  const expected = new Set(inspection.skillInventory.skillKindNames);
@@ -1,10 +1,10 @@
1
1
  import { optionalEntry } from "@nseng-ai/foundation/primitives";
2
2
  import { resultErr, type Result } from "@nseng-ai/foundation/result";
3
3
 
4
- import type { AregPathState, AregTextFileState } from "../gateways.ts";
4
+ import type { PathState, TextFileState } from "@nseng-ai/harness-artifacts/api";
5
5
 
6
- type NonUsableTextFileState = Exclude<AregTextFileState, { type: "file" } | { type: "missing" }>;
7
- type NonUsableDirectoryState = Exclude<AregPathState, { type: "directory" } | { type: "missing" }>;
6
+ type NonUsableTextFileState = Exclude<TextFileState, { type: "file" } | { type: "missing" }>;
7
+ type NonUsableDirectoryState = Exclude<PathState, { type: "directory" } | { type: "missing" }>;
8
8
 
9
9
  export function isPathStateError(error: { code: string }): boolean {
10
10
  return (
@@ -16,7 +16,7 @@ export function isPathStateError(error: { code: string }): boolean {
16
16
 
17
17
  export function validateOptionalDirectoryState(options: {
18
18
  pathLabel: string;
19
- state: AregPathState;
19
+ state: PathState;
20
20
  action: string;
21
21
  symlinkSubject?: string;
22
22
  }): Result<undefined> {
@@ -0,0 +1,45 @@
1
+ import {
2
+ classifyManifestSkillSource,
3
+ type AregManifestSkillSourceInspection,
4
+ } from "./manifest-sources.ts";
5
+
6
+ export interface ManifestSourceFinding {
7
+ code: "manifest-skill-target-missing" | "manifest-skill-md-missing";
8
+ message: string;
9
+ path: string;
10
+ remediation: string;
11
+ }
12
+
13
+ export const MANIFEST_FAILURE_CODE = "invalid-install-manifest";
14
+ export const MANIFEST_FAILURE_REMEDIATION =
15
+ "Fix the shared harness artifact manifest or run ns update to reconcile manifest-tracked artifacts.";
16
+ export const MANIFEST_SOURCE_REMEDIATION =
17
+ "Run ns update to reconcile manifest-tracked harness artifacts, or remove/fix the stale manifest entry through the owning provisioning workflow.";
18
+
19
+ export function manifestSourceLabel(source: AregManifestSkillSourceInspection): string {
20
+ return `Shared manifest entry ${source.manifestKey} from ${source.provenance.packageName}@${source.provenance.version}`;
21
+ }
22
+
23
+ export function manifestSourceFinding(
24
+ source: AregManifestSkillSourceInspection,
25
+ ): ManifestSourceFinding | undefined {
26
+ const label = manifestSourceLabel(source);
27
+ const status = classifyManifestSkillSource(source);
28
+ if (status === "target-missing") {
29
+ return {
30
+ code: "manifest-skill-target-missing",
31
+ path: source.targetSkillRelativePath,
32
+ message: `${label} targets ${source.targetSkillRelativePath}, but the skill directory is missing.`,
33
+ remediation: MANIFEST_SOURCE_REMEDIATION,
34
+ };
35
+ }
36
+ if (status === "md-missing") {
37
+ return {
38
+ code: "manifest-skill-md-missing",
39
+ path: `${source.targetSkillRelativePath}/SKILL.md`,
40
+ message: `${label} targets ${source.targetSkillRelativePath}, but SKILL.md is missing.`,
41
+ remediation: MANIFEST_SOURCE_REMEDIATION,
42
+ };
43
+ }
44
+ return undefined;
45
+ }