@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
@@ -0,0 +1,119 @@
1
+ import { z } from "zod";
2
+
3
+ import type {
4
+ HarnessId,
5
+ HarnessScope,
6
+ PathState,
7
+ TextFileState,
8
+ } from "@nseng-ai/harness-artifacts/api";
9
+
10
+ export type AregManifestSourceType = "first-party" | "npm-module";
11
+
12
+ export interface AregManifestSkillSourceProvenance {
13
+ type: AregManifestSourceType;
14
+ packageName: string;
15
+ relativePath: string;
16
+ version: string;
17
+ }
18
+
19
+ export interface AregManifestSkillSourceInspection {
20
+ skillName: string;
21
+ harness: HarnessId;
22
+ scope: HarnessScope;
23
+ manifestPath: string;
24
+ manifestKey: string;
25
+ provenance: AregManifestSkillSourceProvenance;
26
+ targetRootRelativePath: string;
27
+ targetSkillRelativePath: string;
28
+ skillDir: PathState;
29
+ skillMd: TextFileState;
30
+ }
31
+
32
+ export const aregManifestSkillSourceViewSchema = z.object({
33
+ harness: z.string(),
34
+ scope: z.string(),
35
+ manifestPath: z.string(),
36
+ manifestKey: z.string(),
37
+ sourceType: z.enum(["first-party", "npm-module"]),
38
+ packageName: z.string(),
39
+ sourceRelativePath: z.string(),
40
+ version: z.string(),
41
+ targetSkillRelativePath: z.string(),
42
+ });
43
+
44
+ export type AregManifestSkillSourceView = z.infer<typeof aregManifestSkillSourceViewSchema>;
45
+
46
+ export function toManifestSkillSourceView(
47
+ source: AregManifestSkillSourceInspection,
48
+ ): AregManifestSkillSourceView {
49
+ return {
50
+ harness: source.harness,
51
+ scope: source.scope,
52
+ manifestPath: source.manifestPath,
53
+ manifestKey: source.manifestKey,
54
+ sourceType: source.provenance.type,
55
+ packageName: source.provenance.packageName,
56
+ sourceRelativePath: source.provenance.relativePath,
57
+ version: source.provenance.version,
58
+ targetSkillRelativePath: source.targetSkillRelativePath,
59
+ };
60
+ }
61
+
62
+ export function groupBySkillName(
63
+ sources: readonly AregManifestSkillSourceInspection[],
64
+ ): ReadonlyMap<string, readonly AregManifestSkillSourceInspection[]> {
65
+ const grouped = new Map<string, AregManifestSkillSourceInspection[]>();
66
+ for (const source of sources) {
67
+ const existing = grouped.get(source.skillName) ?? [];
68
+ existing.push(source);
69
+ grouped.set(source.skillName, existing);
70
+ }
71
+ return grouped;
72
+ }
73
+
74
+ export function manifestSourceViewsBySkillName(
75
+ sources: readonly AregManifestSkillSourceInspection[],
76
+ ): ReadonlyMap<string, readonly AregManifestSkillSourceView[]> {
77
+ return new Map(
78
+ [...groupBySkillName(sources)].map(([skillName, skillSources]) => [
79
+ skillName,
80
+ skillSources.map((source) => toManifestSkillSourceView(source)),
81
+ ]),
82
+ );
83
+ }
84
+
85
+ export function isSkillKindLookupPath(relativePath: string): boolean {
86
+ return relativePath.startsWith("skills/") || relativePath.startsWith(".agents/skills/");
87
+ }
88
+
89
+ export function manifestSkillKindNames(
90
+ sources: readonly AregManifestSkillSourceInspection[],
91
+ ): readonly string[] {
92
+ return sources
93
+ .filter(
94
+ (source) =>
95
+ source.skillDir.type === "directory" &&
96
+ source.skillMd.type === "file" &&
97
+ isSkillKindLookupPath(source.targetSkillRelativePath),
98
+ )
99
+ .map((source) => source.skillName);
100
+ }
101
+
102
+ export type ManifestSkillSourceStatus = "ok" | "target-missing" | "md-missing";
103
+
104
+ export function classifyManifestSkillSource(
105
+ source: AregManifestSkillSourceInspection,
106
+ ): ManifestSkillSourceStatus {
107
+ if (source.skillDir.type === "missing") return "target-missing";
108
+ if (source.skillMd.type === "missing") return "md-missing";
109
+ return "ok";
110
+ }
111
+
112
+ export type AregManifestInspectionError =
113
+ | { type: "manifest"; manifestPath: string; message: string }
114
+ | { type: "harness"; harness: HarnessId; message: string };
115
+
116
+ export interface AregManifestSkillSourcesInspection {
117
+ sources: readonly AregManifestSkillSourceInspection[];
118
+ errors: readonly AregManifestInspectionError[];
119
+ }
@@ -1,7 +1,7 @@
1
1
  import { formatErrorMessage, isRecord } from "@nseng-ai/foundation/primitives";
2
2
  import { err, 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
  import { rejectTextState, validateOptionalDirectoryState } from "./file-state.ts";
6
6
 
7
7
  export interface PiSettingsData {
@@ -12,10 +12,7 @@ export interface PiSettingsData {
12
12
 
13
13
  export type ParsePiSettingsResult = Result<PiSettingsData>;
14
14
 
15
- export function parsePiSettings(
16
- piDir: AregPathState,
17
- settings: AregTextFileState,
18
- ): ParsePiSettingsResult {
15
+ export function parsePiSettings(piDir: PathState, settings: TextFileState): ParsePiSettingsResult {
19
16
  const piDirectory = validateOptionalDirectoryState({
20
17
  pathLabel: ".pi",
21
18
  state: piDir,
@@ -1,62 +1,54 @@
1
+ import type { PathState, TextFileState } from "@nseng-ai/harness-artifacts/api";
2
+
1
3
  import type { AregCliContext } from "../context.ts";
2
4
  import type {
3
5
  AregCheckPairingDirectory,
4
6
  AregCheckSkillInspection,
5
- AregInstructionFilesInspection,
6
- AregPathState,
7
7
  AregProjectBaseInspection,
8
8
  AregReplacementInspection,
9
9
  AregSkillKindSkillInspection,
10
10
  AregSkillNameInventory,
11
- AregTextFileState,
12
11
  } from "../gateways.ts";
12
+ import {
13
+ manifestSkillKindNames,
14
+ type AregManifestSkillSourcesInspection,
15
+ } from "./manifest-sources.ts";
13
16
  import { uniqueSortedStrings } from "../sort.ts";
14
- import { parseInspectedLockfile } from "./lockfile.ts";
17
+ import { parseInspectedLockfile } from "@nseng-ai/harness-artifacts/api";
15
18
 
16
19
  export interface AregProjectInspectionFacts extends AregProjectBaseInspection {
17
- piDir: AregPathState;
18
- piSettings: AregTextFileState;
20
+ piDir: PathState;
21
+ piSettings: TextFileState;
19
22
  replacement: AregReplacementInspection;
20
23
  skillInventory: AregSkillNameInventory;
24
+ manifestSkillSources: AregManifestSkillSourcesInspection;
21
25
  }
22
26
 
23
27
  export interface AregCheckProjectInspection {
24
28
  projectDir: string;
25
- projectPathState: AregPathState;
26
- lockfile: AregTextFileState;
29
+ projectPathState: PathState;
30
+ lockfile: TextFileState;
27
31
  skillsDirectoryNames: readonly string[];
28
32
  agentsSkillNames: readonly string[];
29
33
  excludedSkillNames: readonly string[];
30
- piDir: AregPathState;
31
- piSettings: AregTextFileState;
34
+ piDir: PathState;
35
+ piSettings: TextFileState;
32
36
  replacement: AregReplacementInspection;
37
+ manifestSkillSources: AregManifestSkillSourcesInspection;
33
38
  skills: readonly AregCheckSkillInspection[];
34
39
  pairingDirectories: readonly AregCheckPairingDirectory[];
35
40
  }
36
41
 
37
42
  export interface AregSkillKindProjectInspection {
38
43
  projectDir: string;
39
- projectPathState: AregPathState;
40
- piDir: AregPathState;
41
- piSettings: AregTextFileState;
44
+ projectPathState: PathState;
45
+ piDir: PathState;
46
+ piSettings: TextFileState;
42
47
  replacement: AregReplacementInspection;
48
+ manifestSkillSources: AregManifestSkillSourcesInspection;
43
49
  skills: readonly AregSkillKindSkillInspection[];
44
50
  }
45
51
 
46
- export type AregInitProjectInspection = AregProjectBaseInspection & AregInstructionFilesInspection;
47
-
48
- export async function inspectInitProject(
49
- ctx: AregCliContext,
50
- projectPath: string,
51
- ): Promise<AregInitProjectInspection> {
52
- const base = await ctx.project.inspectProjectBase({ cwd: ctx.cwd, projectPath, env: ctx.env });
53
- const instructionFiles = await ctx.project.inspectInstructionFiles({
54
- projectDir: base.projectDir,
55
- env: ctx.env,
56
- });
57
- return { ...base, ...instructionFiles };
58
- }
59
-
60
52
  export async function collectProjectInspectionFacts(
61
53
  ctx: AregCliContext,
62
54
  projectPath: string,
@@ -70,12 +62,17 @@ export async function collectProjectInspectionFacts(
70
62
  projectDir: base.projectDir,
71
63
  env: ctx.env,
72
64
  });
65
+ const manifestSkillSources = await ctx.project.inspectManifestSkillSources({
66
+ projectDir: base.projectDir,
67
+ env: ctx.env,
68
+ });
73
69
  return {
74
70
  ...base,
75
71
  piDir: piArtifacts.piDir,
76
72
  piSettings: piArtifacts.piSettings,
77
73
  replacement: piArtifacts.replacement,
78
74
  skillInventory,
75
+ manifestSkillSources,
79
76
  };
80
77
  }
81
78
 
@@ -92,11 +89,13 @@ export async function inspectCheckProject(
92
89
  const lockfileSkillNames = lockfileResult.ok
93
90
  ? lockfileResult.value.skills.map((skill) => skill.name)
94
91
  : [];
92
+ const manifestSkillNames = facts.manifestSkillSources.sources.map((source) => source.skillName);
95
93
  const skillNames = uniqueSortedStrings([
96
94
  ...lockfileSkillNames,
97
95
  ...facts.skillInventory.skillsDirectoryNames,
98
96
  ...facts.skillInventory.agentsSkillNames,
99
97
  ...facts.skillInventory.claudeSkillNames,
98
+ ...manifestSkillNames,
100
99
  ]);
101
100
  return {
102
101
  projectDir: facts.projectDir,
@@ -108,6 +107,7 @@ export async function inspectCheckProject(
108
107
  piDir: facts.piDir,
109
108
  piSettings: facts.piSettings,
110
109
  replacement: facts.replacement,
110
+ manifestSkillSources: facts.manifestSkillSources,
111
111
  skills: await collectCheckSkillInspections(ctx, facts.projectDir, skillNames),
112
112
  pairingDirectories: await ctx.project.inspectPairingDirectories({
113
113
  projectDir: facts.projectDir,
@@ -127,10 +127,14 @@ export async function inspectSkillKindProject(
127
127
  piDir: facts.piDir,
128
128
  piSettings: facts.piSettings,
129
129
  replacement: facts.replacement,
130
+ manifestSkillSources: facts.manifestSkillSources,
130
131
  skills: await collectSkillKindInspections(
131
132
  ctx,
132
133
  facts.projectDir,
133
- facts.skillInventory.skillKindNames,
134
+ uniqueSortedStrings([
135
+ ...facts.skillInventory.skillKindNames,
136
+ ...manifestSkillKindNames(facts.manifestSkillSources.sources),
137
+ ]),
134
138
  ),
135
139
  };
136
140
  }
@@ -3,7 +3,6 @@ import type {
3
3
  AregErrorInfo,
4
4
  AregProjectGateway,
5
5
  AregProjectManagedTargetRequest,
6
- AregProjectMutationPolicy,
7
6
  AregProjectMutationResult,
8
7
  } from "../gateways.ts";
9
8
 
@@ -29,34 +28,23 @@ interface ProjectDeleteSymlinkPlan {
29
28
  description: string;
30
29
  }
31
30
 
32
- interface BaseApplyProjectMutationPlanRequest {
31
+ interface ApplyProjectMutationPlanRequest {
33
32
  ctx: AregCliContext;
34
33
  projectDir: string;
35
34
  writes: readonly ProjectTextWritePlan[];
35
+ deletes: readonly ProjectDeletePlan[];
36
+ deleteSymlinks: readonly ProjectDeleteSymlinkPlan[];
37
+ removeEmptyDirs: readonly ProjectRemoveEmptyDirPlan[];
36
38
  execute?: boolean;
37
39
  }
38
40
 
39
- type ApplyProjectMutationPlanRequest =
40
- | (BaseApplyProjectMutationPlanRequest & {
41
- policy: Exclude<AregProjectMutationPolicy, "skill-kind">;
42
- })
43
- | (BaseApplyProjectMutationPlanRequest & {
44
- policy: "skill-kind";
45
- deletes: readonly ProjectDeletePlan[];
46
- deleteSymlinks: readonly ProjectDeleteSymlinkPlan[];
47
- removeEmptyDirs: readonly ProjectRemoveEmptyDirPlan[];
48
- });
49
-
50
41
  export const PROJECT_FILE_MUTATION_OPERATION_TYPES = [
51
42
  "write",
52
43
  "delete",
53
44
  "delete-symlink",
54
45
  "remove-empty-dir",
55
46
  ] as const;
56
- export const PROJECT_MUTATION_OPERATION_TYPES = [
57
- ...PROJECT_FILE_MUTATION_OPERATION_TYPES,
58
- "external",
59
- ] as const;
47
+ export const PROJECT_MUTATION_OPERATION_TYPES = [...PROJECT_FILE_MUTATION_OPERATION_TYPES] as const;
60
48
  export const PROJECT_MUTATION_OPERATION_STATUSES = [
61
49
  "applied",
62
50
  "failed",
@@ -211,7 +199,6 @@ function flattenProjectMutationPlan(
211
199
  plan,
212
200
  }),
213
201
  );
214
- if (request.policy !== "skill-kind") return writes;
215
202
  const deletes = request.deletes.map(
216
203
  (plan): DeleteOperation => ({
217
204
  type: "delete",
@@ -248,7 +235,6 @@ const PROJECT_MUTATION_OPERATION_HANDLERS = {
248
235
  content: operation.plan.content,
249
236
  description: operation.plan.description,
250
237
  createParent: operation.plan.createParent,
251
- policy: request.policy,
252
238
  env: request.ctx.env,
253
239
  });
254
240
  },
@@ -259,7 +245,6 @@ const PROJECT_MUTATION_OPERATION_HANDLERS = {
259
245
  content: operation.plan.content,
260
246
  description: operation.plan.description,
261
247
  createParent: operation.plan.createParent,
262
- policy: request.policy,
263
248
  env: request.ctx.env,
264
249
  });
265
250
  },
@@ -339,7 +324,6 @@ function managedTargetRequest(
339
324
  projectDir: request.projectDir,
340
325
  relativePath: operation.plan.relativePath,
341
326
  description: operation.plan.description,
342
- policy: "skill-kind",
343
327
  env: request.ctx.env,
344
328
  };
345
329
  }
@@ -1,9 +1,9 @@
1
1
  import type { AregCliContext } from "../context.ts";
2
- import type { AregPathState } from "../gateways.ts";
2
+ import type { PathState } from "@nseng-ai/harness-artifacts/api";
3
3
 
4
4
  export interface ProjectPathInspection {
5
5
  projectDir: string;
6
- projectPathState: AregPathState;
6
+ projectPathState: PathState;
7
7
  }
8
8
 
9
9
  export type ResolvedProjectGitRoot<TInspection extends ProjectPathInspection> =
@@ -7,13 +7,18 @@ import {
7
7
  skillLookupFileRelativePath,
8
8
  skillLookupRootRank,
9
9
  type SkillLookupRoot,
10
- } from "../skill-lookup.ts";
10
+ } from "@nseng-ai/foundation/skill-lookup";
11
11
  import { z } from "zod";
12
12
 
13
13
  import type { AregCliContext } from "../context.ts";
14
14
  import type { AregSkillFindSkillInspection } from "../gateways.ts";
15
+ import {
16
+ aregManifestSkillSourceViewSchema,
17
+ manifestSourceViewsBySkillName,
18
+ type AregManifestSkillSourceView,
19
+ } from "./manifest-sources.ts";
15
20
  import { toProjectPath } from "../gateways/project-fs.ts";
16
- import { parseSkillFrontmatterBlock } from "./frontmatter.ts";
21
+ import { parseSkillFrontmatterBlock } from "@nseng-ai/harness-artifacts/api";
17
22
  import { inspectResolvedProjectGitRoot } from "./project-resolution.ts";
18
23
 
19
24
  const skillFindRootSchema = z.enum(SKILL_LOOKUP_ROOTS);
@@ -25,6 +30,8 @@ const skillFindWarningSchema = z.object({
25
30
  path: z.string(),
26
31
  });
27
32
 
33
+ const skillFindManifestSourceSchema = aregManifestSkillSourceViewSchema;
34
+
28
35
  const skillFindMatchSchema = z.object({
29
36
  name: z.string(),
30
37
  root: skillFindRootSchema,
@@ -37,6 +44,7 @@ const skillFindMatchSchema = z.object({
37
44
  frontmatterName: z.string().optional(),
38
45
  description: z.string().optional(),
39
46
  shouldDisableModelInvocation: z.boolean().optional(),
47
+ manifestSources: z.array(skillFindManifestSourceSchema).optional(),
40
48
  warnings: z.array(skillFindWarningSchema).optional(),
41
49
  });
42
50
 
@@ -103,13 +111,18 @@ export async function runSkillFind(
103
111
  if (resolved.type === "error") return failure("project-inspection-failed", resolved.message);
104
112
  const projectDir = resolved.projectDir;
105
113
  const inspection = await ctx.project.inspectSkillFindRoots({ projectDir, env: ctx.env });
114
+ const manifestSources = await ctx.project.inspectManifestSkillSources({
115
+ projectDir,
116
+ env: ctx.env,
117
+ });
118
+ const manifestSourceViews = manifestSourceViewsBySkillName(manifestSources.sources);
106
119
  const searchedRoots = buildSkillFindSearchedRoots(projectDir, request.skill);
107
120
  const exactSkills = inspection.skills
108
121
  .filter((skill) => skill.name === request.skill)
109
122
  .toSorted(compareSkillFindInspection);
110
123
  if (exactSkills.length > 0) {
111
124
  const matches = exactSkills.map((skill, index) =>
112
- toSkillFindMatch(projectDir, skill, index === 0),
125
+ toSkillFindMatch(projectDir, skill, index === 0, manifestSourceViews.get(skill.name)),
113
126
  );
114
127
  const preferred = matches[0];
115
128
  if (preferred === undefined)
@@ -155,6 +168,7 @@ function toSkillFindMatch(
155
168
  projectDir: string,
156
169
  skill: AregSkillFindSkillInspection,
157
170
  isPreferred: boolean,
171
+ manifestSources: readonly AregManifestSkillSourceView[] | undefined,
158
172
  ): SkillFindMatch {
159
173
  const skillFileRelativePath = skillLookupFileRelativePath(skill.root, skill.name);
160
174
  const skillFilePath = toProjectPath(projectDir, skillFileRelativePath);
@@ -169,6 +183,7 @@ function toSkillFindMatch(
169
183
  basePath: toProjectPath(projectDir, skill.baseRelativePath),
170
184
  skillFilePath,
171
185
  ...frontmatter.fields,
186
+ ...(manifestSources === undefined ? {} : { manifestSources: [...manifestSources] }),
172
187
  ...(frontmatter.warnings.length === 0 ? {} : { warnings: frontmatter.warnings }),
173
188
  };
174
189
  }
@@ -280,6 +295,11 @@ function renderSkillFindSuccess(result: SkillFindSuccessResult): string {
280
295
  for (const match of result.matches) {
281
296
  const marker = hasDuplicates ? (match.isPreferred ? "* " : " ") : "";
282
297
  lines.push(` ${marker}${match.skillFileRelativePath}`);
298
+ for (const source of match.manifestSources ?? []) {
299
+ lines.push(
300
+ ` manifest: ${source.harness} ${source.manifestKey} ${source.packageName}@${source.version}`,
301
+ );
302
+ }
283
303
  }
284
304
  return lines.join("\n");
285
305
  }
@@ -13,7 +13,7 @@ import {
13
13
  claudeSkillMirrorRelativePath,
14
14
  expectedAgentsSkillSymlinkTarget,
15
15
  expectedClaudeSkillSymlinkTarget,
16
- } from "./skill-mirror-conventions.ts";
16
+ } from "@nseng-ai/harness-artifacts/api";
17
17
  import { parsePiSettings, type PiSettingsData } from "./pi-settings.ts";
18
18
  import {
19
19
  PROJECT_FILE_MUTATION_OPERATION_TYPES,
@@ -416,7 +416,7 @@ export function deletionPrompt(plan: SkillKindApplyPlan): string {
416
416
  .filter(isDeletionOperation)
417
417
  .map((operation) => `- ${operation.relativePath}`)
418
418
  .join("\n");
419
- return `Apply ${plan.kind} to ${plan.skill} will delete managed artifacts:\n${paths}\nContinue?`;
419
+ return `Apply ${plan.kind} to ${plan.skill} will delete harness overlays:\n${paths}\nContinue?`;
420
420
  }
421
421
 
422
422
  function isDeletionOperation(operation: PlannedApplyOperation): boolean {
@@ -1,6 +1,6 @@
1
1
  import type { Result } from "@nseng-ai/foundation/result";
2
2
 
3
- import { transformSkillFrontmatter } from "./frontmatter.ts";
3
+ import { transformSkillFrontmatter } from "@nseng-ai/harness-artifacts/api";
4
4
  import {
5
5
  DISABLE_MODEL_INVOCATION_KEY,
6
6
  KIND_PROPERTIES,
@@ -1,8 +1,15 @@
1
1
  import { err, type Result } from "@nseng-ai/foundation/result";
2
2
 
3
3
  import type { AregSkillKindSkillInspection } from "../gateways.ts";
4
- import { sortStrings } from "../sort.ts";
5
- import { parseSkillFrontmatterBlock, type SkillFrontmatterData } from "./frontmatter.ts";
4
+ import { sortStringsLocaleAware } from "../sort.ts";
5
+ import {
6
+ manifestSourceViewsBySkillName,
7
+ type AregManifestSkillSourceView,
8
+ } from "./manifest-sources.ts";
9
+ import {
10
+ parseSkillFrontmatterBlock,
11
+ type SkillFrontmatterData,
12
+ } from "@nseng-ai/harness-artifacts/api";
6
13
  import {
7
14
  formatReplacementLabel,
8
15
  replacementAdvice,
@@ -90,6 +97,8 @@ export interface SkillKindReplacementInfo {
90
97
  advice?: string;
91
98
  }
92
99
 
100
+ export type SkillKindManifestSourceInfo = AregManifestSkillSourceView;
101
+
93
102
  export interface SkillKindRecord {
94
103
  skill: string;
95
104
  kind: InferredSkillInvocationKind;
@@ -98,6 +107,7 @@ export interface SkillKindRecord {
98
107
  piExtension: PiExtensionStatus;
99
108
  artifacts: SkillKindArtifactFacts;
100
109
  replacement: SkillKindReplacementInfo;
110
+ manifestSources: readonly SkillKindManifestSourceInfo[];
101
111
  notes: readonly string[];
102
112
  }
103
113
 
@@ -164,6 +174,7 @@ export function inferSkillKindRecord(options: {
164
174
  piExtension: piExtensionStatus(kind, artifacts, options.replacement),
165
175
  artifacts,
166
176
  replacement,
177
+ manifestSources: [],
167
178
  notes: buildNotes({
168
179
  skillName: options.skillName,
169
180
  kind,
@@ -188,6 +199,9 @@ export function buildSkillKindRecords(
188
199
  const piSettings = parsePiSettings(inspection.piDir, inspection.piSettings);
189
200
  if (!piSettings.ok) return piSettings;
190
201
  const records: SkillKindRecord[] = [];
202
+ const manifestSourceViews = manifestSourceViewsBySkillName(
203
+ inspection.manifestSkillSources.sources,
204
+ );
191
205
  for (const skill of sortSkills(inspection.skills)) {
192
206
  const readiness = validateInspectableSkill(skill);
193
207
  if (!readiness.ok) return readiness;
@@ -202,17 +216,19 @@ export function buildSkillKindRecords(
202
216
  );
203
217
  if (!frontmatter.ok) return frontmatter;
204
218
  const replacement = verifyPiReplacement(skill.name, inspection.replacement);
205
- records.push(
206
- inferSkillKindRecord({
207
- skillName: skill.name,
208
- frontmatter: frontmatter.value,
209
- hasCodexSidecar: skill.openaiPolicy.type === "file",
210
- isPiExcluded: piSettings.value.exclusions.includes(`-skills/${skill.name}`),
211
- hasAgentsMirror: skill.agentsPath.type !== "missing",
212
- hasClaudeMirror: skill.claudePath.type !== "missing",
213
- replacement,
214
- }),
215
- );
219
+ const record = inferSkillKindRecord({
220
+ skillName: skill.name,
221
+ frontmatter: frontmatter.value,
222
+ hasCodexSidecar: skill.openaiPolicy.type === "file",
223
+ isPiExcluded: piSettings.value.exclusions.includes(`-skills/${skill.name}`),
224
+ hasAgentsMirror: skill.agentsPath.type !== "missing",
225
+ hasClaudeMirror: skill.claudePath.type !== "missing",
226
+ replacement,
227
+ });
228
+ records.push({
229
+ ...record,
230
+ manifestSources: manifestSourceViews.get(skill.name) ?? [],
231
+ });
216
232
  }
217
233
  return { ok: true, value: records };
218
234
  }
@@ -385,7 +401,7 @@ function sortSkills(
385
401
  skills: readonly AregSkillKindSkillInspection[],
386
402
  ): readonly AregSkillKindSkillInspection[] {
387
403
  const byName = new Map(skills.map((skill) => [skill.name, skill]));
388
- return sortStrings([...byName.keys()])
404
+ return sortStringsLocaleAware([...byName.keys()])
389
405
  .map((name) => byName.get(name))
390
406
  .filter((skill): skill is AregSkillKindSkillInspection => skill !== undefined);
391
407
  }