@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
@@ -1,5 +1,5 @@
1
1
  import type { AregErrorInfo } from "../gateways.ts";
2
2
 
3
- export function errorInfo(code: string, message: string, displayCommand?: string): AregErrorInfo {
4
- return displayCommand === undefined ? { code, message } : { code, message, displayCommand };
3
+ export function errorInfo(code: string, message: string): AregErrorInfo {
4
+ return { code, message };
5
5
  }
@@ -2,9 +2,9 @@ import { lstat, readlink } from "node:fs/promises";
2
2
 
3
3
  import { errorCodeFromUnknown, isPathInside } from "@nseng-ai/foundation/primitives";
4
4
 
5
- import type { AregPathState } from "../gateways.ts";
5
+ import type { PathState } from "@nseng-ai/harness-artifacts/api";
6
6
 
7
- export async function inspectPath(candidate: string): Promise<AregPathState> {
7
+ export async function inspectPath(candidate: string): Promise<PathState> {
8
8
  try {
9
9
  const info = await lstat(candidate);
10
10
  if (info.isSymbolicLink()) return { type: "symlink", target: await readlink(candidate) };
@@ -3,14 +3,14 @@ import path from "node:path";
3
3
 
4
4
  import { formatErrorMessage } from "@nseng-ai/foundation/primitives";
5
5
 
6
- import type { AregErrorInfo, AregProjectMutationPolicy, AregTextFileState } from "../gateways.ts";
6
+ import type { AregErrorInfo } from "../gateways.ts";
7
7
  import {
8
8
  classifySkillMirrorSymlinkState,
9
9
  expectedMirrorTarget,
10
- } from "../operations/skill-mirror-conventions.ts";
10
+ type TextFileState,
11
+ } from "@nseng-ai/harness-artifacts/api";
11
12
  import { errorInfo } from "./errors.ts";
12
13
  import { inspectPath, isPathAtOrBelow } from "./fs-utils.ts";
13
- import { getAregProjectMutationPolicyDescriptor } from "./mutation-policy.ts";
14
14
 
15
15
  export { inspectPath, isNodeErrorCode } from "./fs-utils.ts";
16
16
 
@@ -19,14 +19,12 @@ export function toProjectPath(projectRoot: string, relativePath: string): string
19
19
  }
20
20
 
21
21
  interface ResolveAllowedWriteTargetOptions {
22
- policy: AregProjectMutationPolicy;
23
22
  projectRoot: string;
24
23
  relativePath: string;
25
24
  description: string;
26
25
  }
27
26
 
28
27
  interface ValidateTextWriteTargetOptions {
29
- policy: AregProjectMutationPolicy;
30
28
  target: string;
31
29
  projectRoot: string;
32
30
  description: string;
@@ -35,7 +33,7 @@ interface ValidateTextWriteTargetOptions {
35
33
 
36
34
  type WriteTargetValidationResult = { ok: true } | { ok: false; error: AregErrorInfo };
37
35
 
38
- export async function inspectTextFile(candidate: string): Promise<AregTextFileState> {
36
+ export async function inspectTextFile(candidate: string): Promise<TextFileState> {
39
37
  const pathState = await inspectPath(candidate);
40
38
  if (
41
39
  pathState.type === "missing" ||
@@ -60,14 +58,14 @@ export async function resolveExistingDirectory(
60
58
  return {
61
59
  type: "error",
62
60
  error: errorInfo(
63
- "init-symlink",
61
+ "project-root-symlink",
64
62
  `${description} at ${candidate} is a symlink; refusing to manage it.`,
65
63
  ),
66
64
  };
67
65
  if (state.type !== "directory")
68
66
  return {
69
67
  type: "error",
70
- error: errorInfo("init-not-directory", `${candidate} exists but is not a directory.`),
68
+ error: errorInfo("project-root-not-directory", `${candidate} exists but is not a directory.`),
71
69
  };
72
70
  try {
73
71
  return { type: "ok", value: await realpath(candidate) };
@@ -75,7 +73,7 @@ export async function resolveExistingDirectory(
75
73
  return {
76
74
  type: "error",
77
75
  error: errorInfo(
78
- "init-realpath-failed",
76
+ "realpath-failed",
79
77
  `Could not resolve ${description} at ${candidate}: ${formatErrorMessage(error)}`,
80
78
  ),
81
79
  };
@@ -85,37 +83,21 @@ export async function resolveExistingDirectory(
85
83
  export function resolveAllowedWriteTarget(
86
84
  options: ResolveAllowedWriteTargetOptions,
87
85
  ): { type: "ok"; value: string } | { type: "error"; error: AregErrorInfo } {
88
- const descriptor = getAregProjectMutationPolicyDescriptor(options.policy);
89
- if (
90
- descriptor.shouldCheckUnsupportedFirst &&
91
- !descriptor.isAllowedRelativePath(options.relativePath)
92
- ) {
93
- return {
94
- type: "error",
95
- error: errorInfo(
96
- descriptor.refusedTargetCode,
97
- descriptor.unsupportedMessage(options.relativePath, options.description),
98
- ),
99
- };
100
- }
101
86
  if (path.isAbsolute(options.relativePath) || options.relativePath.split("/").includes("..")) {
102
87
  return {
103
88
  type: "error",
104
89
  error: errorInfo(
105
- descriptor.refusedTargetCode,
106
- descriptor.unsafeMessage(options.relativePath, options.description),
90
+ "skill-kind-target-refused",
91
+ `Refusing to write ${options.description}: unsafe target ${options.relativePath}.`,
107
92
  ),
108
93
  };
109
94
  }
110
- if (
111
- !descriptor.shouldCheckUnsupportedFirst &&
112
- !descriptor.isAllowedRelativePath(options.relativePath)
113
- ) {
95
+ if (!isSkillKindWritablePath(options.relativePath)) {
114
96
  return {
115
97
  type: "error",
116
98
  error: errorInfo(
117
- descriptor.refusedTargetCode,
118
- descriptor.unsupportedMessage(options.relativePath, options.description),
99
+ "skill-kind-target-refused",
100
+ `Refusing to write ${options.description}: unsupported target ${options.relativePath}.`,
119
101
  ),
120
102
  };
121
103
  }
@@ -125,38 +107,44 @@ export function resolveAllowedWriteTarget(
125
107
  return {
126
108
  type: "error",
127
109
  error: errorInfo(
128
- descriptor.refusedTargetCode,
129
- descriptor.outsideMessage(options.relativePath, options.description),
110
+ "skill-kind-target-refused",
111
+ `Refusing to write ${options.description}: target ${options.relativePath} resolves outside the project.`,
130
112
  ),
131
113
  };
132
114
  }
133
115
  return { type: "ok", value: target };
134
116
  }
135
117
 
118
+ const SKILL_KIND_WRITABLE_PATH_RE =
119
+ /^(?:skills|\.agents\/skills)\/[^/]+\/(?:SKILL\.md|agents(?:\/openai\.yaml)?)$/u;
120
+
121
+ function isSkillKindWritablePath(relativePath: string): boolean {
122
+ return SKILL_KIND_WRITABLE_PATH_RE.test(relativePath) || relativePath === ".pi/settings.json";
123
+ }
124
+
136
125
  export async function validateWriteTarget(
137
126
  options: ValidateTextWriteTargetOptions,
138
127
  ): Promise<WriteTargetValidationResult> {
139
- const descriptor = getAregProjectMutationPolicyDescriptor(options.policy);
140
128
  const targetState = await inspectPath(options.target);
141
129
  if (targetState.type === "symlink")
142
130
  return {
143
131
  ok: false,
144
132
  error: errorInfo(
145
- descriptor.symlinkCode,
133
+ "skill-kind-symlink",
146
134
  `${options.description} at ${options.target} is a symlink; refusing to manage it.`,
147
135
  ),
148
136
  };
149
137
  if (targetState.type === "directory" || targetState.type === "other")
150
138
  return {
151
139
  ok: false,
152
- error: errorInfo(descriptor.notFileCode, `${options.target} exists but is not a file.`),
140
+ error: errorInfo("skill-kind-not-file", `${options.target} exists but is not a file.`),
153
141
  };
154
142
  if (targetState.type === "file")
155
143
  return await requirePathAtOrBelow(options.target, options.projectRoot, options.description);
156
144
  const parent = await nearestExistingParent(
157
145
  options.target,
158
146
  options.projectRoot,
159
- descriptor.parentMissingCode,
147
+ "skill-kind-parent-missing",
160
148
  );
161
149
  if (parent.type === "error") return { ok: false, error: parent.error };
162
150
  const parentState = await inspectPath(parent.value);
@@ -164,7 +152,7 @@ export async function validateWriteTarget(
164
152
  return {
165
153
  ok: false,
166
154
  error: errorInfo(
167
- descriptor.parentSymlinkCode,
155
+ "skill-kind-parent-symlink",
168
156
  `Parent directory at ${parent.value} is a symlink; refusing to manage it.`,
169
157
  ),
170
158
  };
@@ -172,7 +160,7 @@ export async function validateWriteTarget(
172
160
  return {
173
161
  ok: false,
174
162
  error: errorInfo(
175
- descriptor.parentNotDirectoryCode,
163
+ "skill-kind-parent-not-directory",
176
164
  `${parent.value} exists but is not a directory.`,
177
165
  ),
178
166
  };
@@ -186,7 +174,7 @@ export async function validateWriteTarget(
186
174
  return {
187
175
  ok: false,
188
176
  error: errorInfo(
189
- descriptor.parentMissingCode,
177
+ "skill-kind-parent-missing",
190
178
  `Parent directory at ${path.dirname(options.target)} does not exist.`,
191
179
  ),
192
180
  };
@@ -304,7 +292,7 @@ async function requirePathAtOrBelow(
304
292
  return {
305
293
  ok: false,
306
294
  error: errorInfo(
307
- "init-outside-project",
295
+ "outside-project",
308
296
  `${description} at ${candidate} resolves outside ${projectRoot}; refusing to manage it.`,
309
297
  ),
310
298
  };
@@ -312,7 +300,7 @@ async function requirePathAtOrBelow(
312
300
  return {
313
301
  ok: false,
314
302
  error: errorInfo(
315
- "init-realpath-failed",
303
+ "realpath-failed",
316
304
  `Could not resolve ${description} at ${candidate}: ${formatErrorMessage(error)}`,
317
305
  ),
318
306
  };
@@ -3,7 +3,6 @@ import { lstat, mkdir, readdir, realpath, rm, rmdir, unlink, writeFile } from "n
3
3
  import path from "node:path";
4
4
 
5
5
  import { RealGitGateway } from "@nseng-ai/capability-kit/git";
6
- import type { GitGateway } from "@nseng-ai/capability-kit/git";
7
6
  import { visibleCommandBackedReplacementSurfaces } from "@nseng-ai/command-backed-skill-registry";
8
7
  import { NodeCommandExecApi } from "@nseng-ai/foundation/exec";
9
8
  import { formatErrorMessage } from "@nseng-ai/foundation/primitives";
@@ -13,13 +12,22 @@ import {
13
12
  skillLookupDescriptorForSourceType,
14
13
  skillLookupFileRelativePath,
15
14
  type SkillLookupSourceType,
16
- } from "../skill-lookup.ts";
15
+ } from "@nseng-ai/foundation/skill-lookup";
16
+ import {
17
+ ALL_HARNESS_IDS,
18
+ INSTALL_MANIFEST_FILE_NAME,
19
+ readInstallManifestAtRoot,
20
+ resolveHarnessSkillRoot,
21
+ type InstallManifestEntryData,
22
+ type TextFileState,
23
+ } from "@nseng-ai/harness-artifacts/api";
17
24
 
18
25
  import { AREG_SKILL_KIND_ROOT_DESCRIPTORS, skillKindDescriptorForSourceType } from "../gateways.ts";
19
26
  import type {
20
27
  AregCheckPairingDirectory,
21
28
  AregCheckSkillInspection,
22
29
  AregErrorInfo,
30
+ AregGitGateway,
23
31
  AregPiSkillInventoryInspection,
24
32
  AregProjectFileDeleteRequest,
25
33
  AregProjectGateway,
@@ -34,11 +42,9 @@ import type {
34
42
  AregSkillKindResolveRequest,
35
43
  AregSkillKindResolveResult,
36
44
  AregSkillKindSkillInspection,
37
- AregTextFileState,
38
45
  } from "../gateways.ts";
39
- import { sortStrings } from "../sort.ts";
46
+ import { sortStringsLocaleAware } from "../sort.ts";
40
47
  import { errorInfo } from "./errors.ts";
41
- import { getAregProjectMutationPolicyDescriptor } from "./mutation-policy.ts";
42
48
  import {
43
49
  inspectPath,
44
50
  inspectTextFile,
@@ -51,6 +57,11 @@ import {
51
57
  validateSkillKindRemoveDirTarget,
52
58
  validateWriteTarget,
53
59
  } from "./project-fs.ts";
60
+ import type {
61
+ AregManifestInspectionError,
62
+ AregManifestSkillSourceInspection,
63
+ AregManifestSkillSourcesInspection,
64
+ } from "../operations/manifest-sources.ts";
54
65
  import { classifyResolvedSkillKindInspection } from "./skill-kind-classification.ts";
55
66
 
56
67
  const PI_GENERIC_REPLACEMENT_ADAPTER_RELATIVE_PATH = ".pi/extensions/backing-skill-commands.ts";
@@ -61,9 +72,9 @@ const PI_GENERIC_REPLACEMENT_PACKAGE_MODULE_RELATIVE_PATH =
61
72
  const AREG_VISIBLE_REPLACEMENT_SURFACES = visibleCommandBackedReplacementSurfaces();
62
73
 
63
74
  export class RealAregProjectGateway implements AregProjectGateway {
64
- private readonly git: GitGateway;
75
+ private readonly git: AregGitGateway;
65
76
 
66
- constructor(options: { git?: GitGateway } = {}) {
77
+ constructor(options: { git?: AregGitGateway } = {}) {
67
78
  this.git = options.git ?? new RealGitGateway(new NodeCommandExecApi());
68
79
  }
69
80
 
@@ -78,17 +89,6 @@ export class RealAregProjectGateway implements AregProjectGateway {
78
89
  };
79
90
  }
80
91
 
81
- async inspectInstructionFiles(request: { projectDir: string; env: NodeJS.ProcessEnv }) {
82
- return {
83
- agentsMd: await inspectTextFile(path.join(request.projectDir, "AGENTS.md")),
84
- claudeMd: await inspectTextFile(path.join(request.projectDir, "CLAUDE.md")),
85
- claudeDir: await inspectPath(path.join(request.projectDir, ".claude")),
86
- claudeSettings: await inspectTextFile(
87
- path.join(request.projectDir, ".claude", "settings.local.json"),
88
- ),
89
- };
90
- }
91
-
92
92
  async inspectPiArtifacts(request: { projectDir: string; env: NodeJS.ProcessEnv }) {
93
93
  return {
94
94
  piDir: await inspectPath(path.join(request.projectDir, ".pi")),
@@ -117,6 +117,13 @@ export class RealAregProjectGateway implements AregProjectGateway {
117
117
  };
118
118
  }
119
119
 
120
+ async inspectManifestSkillSources(request: {
121
+ projectDir: string;
122
+ env: NodeJS.ProcessEnv;
123
+ }): Promise<AregManifestSkillSourcesInspection> {
124
+ return inspectManifestSkillSources(request.projectDir, request.env);
125
+ }
126
+
120
127
  async inspectSkillFindRoots(request: {
121
128
  projectDir: string;
122
129
  env: NodeJS.ProcessEnv;
@@ -192,7 +199,6 @@ export class RealAregProjectGateway implements AregProjectGateway {
192
199
  async writeTextFile(request: AregProjectTextWriteRequest): Promise<AregProjectMutationResult> {
193
200
  const target = await resolveWriteTextFileTarget(request);
194
201
  if (target.type === "error") return { ok: false, error: target.error };
195
- const policyDescriptor = getAregProjectMutationPolicyDescriptor(request.policy);
196
202
  if (request.createParent) {
197
203
  try {
198
204
  await mkdir(path.dirname(target.value), { recursive: true });
@@ -200,13 +206,12 @@ export class RealAregProjectGateway implements AregProjectGateway {
200
206
  return {
201
207
  ok: false,
202
208
  error: errorInfo(
203
- policyDescriptor.parentCreateFailedCode,
209
+ "skill-kind-parent-create-failed",
204
210
  `Failed to create ${path.dirname(target.value)}: ${formatErrorMessage(error)}`,
205
211
  ),
206
212
  };
207
213
  }
208
214
  const revalidation = await validateWriteTarget({
209
- policy: request.policy,
210
215
  target: target.value,
211
216
  projectRoot: target.projectRoot,
212
217
  shouldCreateParent: request.createParent,
@@ -221,7 +226,7 @@ export class RealAregProjectGateway implements AregProjectGateway {
221
226
  return {
222
227
  ok: false,
223
228
  error: errorInfo(
224
- policyDescriptor.writeFailedCode,
229
+ "skill-kind-write-failed",
225
230
  `Failed to write ${request.description} at ${target.value}: ${formatErrorMessage(error)}`,
226
231
  ),
227
232
  };
@@ -294,7 +299,6 @@ async function resolveWriteTextFileTarget(
294
299
  const target = await resolveProjectMutationTarget(request);
295
300
  if (target.type === "error") return target;
296
301
  const validation = await validateWriteTarget({
297
- policy: request.policy,
298
302
  target: target.value,
299
303
  projectRoot: target.projectRoot,
300
304
  shouldCreateParent: request.createParent,
@@ -361,7 +365,6 @@ async function resolveRemoveEmptyDirTarget(
361
365
 
362
366
  async function resolveProjectMutationTarget(request: {
363
367
  projectDir: string;
364
- policy: AregProjectTextWriteRequest["policy"];
365
368
  relativePath: string;
366
369
  description: string;
367
370
  }): Promise<
@@ -370,7 +373,6 @@ async function resolveProjectMutationTarget(request: {
370
373
  const projectRoot = await resolveExistingDirectory(request.projectDir, "project root");
371
374
  if (projectRoot.type === "error") return { type: "error", error: projectRoot.error };
372
375
  const target = resolveAllowedWriteTarget({
373
- policy: request.policy,
374
376
  projectRoot: projectRoot.value,
375
377
  relativePath: request.relativePath,
376
378
  description: request.description,
@@ -444,7 +446,7 @@ async function listSkillKindNames(projectDir: string): Promise<string[]> {
444
446
  }
445
447
 
446
448
  function collectSortedUniqueNames(namesByRoot: readonly (readonly string[])[]): string[] {
447
- return sortStrings([...new Set(namesByRoot.flat())]);
449
+ return sortStringsLocaleAware([...new Set(namesByRoot.flat())]);
448
450
  }
449
451
 
450
452
  async function listFirstPartySkillKindNames(projectDir: string): Promise<string[]> {
@@ -484,6 +486,71 @@ async function listVendoredSkillKindNames(projectDir: string): Promise<string[]>
484
486
  ).map((entry) => entry.name);
485
487
  }
486
488
 
489
+ async function inspectManifestSkillSources(
490
+ projectDir: string,
491
+ env: NodeJS.ProcessEnv,
492
+ ): Promise<AregManifestSkillSourcesInspection> {
493
+ const sources: AregManifestSkillSourceInspection[] = [];
494
+ const errors: AregManifestInspectionError[] = [];
495
+ const context = { projectRoot: projectDir, homeDir: env.HOME ?? "", env };
496
+ for (const harness of ALL_HARNESS_IDS) {
497
+ const root = resolveHarnessSkillRoot({ harness, scope: "project", context });
498
+ if (!root.ok) {
499
+ errors.push({ type: "harness", harness, message: root.error.message });
500
+ continue;
501
+ }
502
+ const manifestPath = path.join(root.value.rootPath, INSTALL_MANIFEST_FILE_NAME);
503
+ const manifest = await readInstallManifestAtRoot({ targetRoot: root.value.rootPath });
504
+ if (!manifest.ok) {
505
+ errors.push({ type: "manifest", manifestPath, message: manifest.error.message });
506
+ continue;
507
+ }
508
+ for (const [manifestKey, entry] of Object.entries(manifest.value.artifacts)) {
509
+ if (entry.kind !== "skill") continue;
510
+ sources.push(
511
+ await inspectManifestSkillSource({ projectDir, manifestPath, manifestKey, entry }),
512
+ );
513
+ }
514
+ }
515
+ return { sources: sortManifestSkillSources(sources), errors };
516
+ }
517
+
518
+ interface InspectManifestSkillSourceOptions {
519
+ projectDir: string;
520
+ manifestPath: string;
521
+ manifestKey: string;
522
+ entry: InstallManifestEntryData;
523
+ }
524
+
525
+ async function inspectManifestSkillSource(
526
+ options: InspectManifestSkillSourceOptions,
527
+ ): Promise<AregManifestSkillSourceInspection> {
528
+ const { projectDir, manifestPath, manifestKey, entry } = options;
529
+ return {
530
+ skillName: entry.provisionName,
531
+ harness: entry.harness,
532
+ scope: entry.scope,
533
+ manifestPath,
534
+ manifestKey,
535
+ provenance: { ...entry.source },
536
+ targetRootRelativePath: path.relative(projectDir, entry.targetRoot),
537
+ targetSkillRelativePath: path.relative(projectDir, entry.targetArtifactPath),
538
+ skillDir: await inspectPath(entry.targetArtifactPath),
539
+ skillMd: await inspectTextFile(path.join(entry.targetArtifactPath, "SKILL.md")),
540
+ };
541
+ }
542
+
543
+ function sortManifestSkillSources(
544
+ sources: readonly AregManifestSkillSourceInspection[],
545
+ ): AregManifestSkillSourceInspection[] {
546
+ return [...sources].sort(
547
+ (left, right) =>
548
+ left.skillName.localeCompare(right.skillName) ||
549
+ left.harness.localeCompare(right.harness) ||
550
+ left.manifestKey.localeCompare(right.manifestKey),
551
+ );
552
+ }
553
+
487
554
  async function inspectSkillFindRoots(projectDir: string): Promise<AregSkillFindSkillInspection[]> {
488
555
  const skills: AregSkillFindSkillInspection[] = [];
489
556
  for (const root of SKILL_LOOKUP_ROOT_DESCRIPTORS) {
@@ -517,7 +584,7 @@ interface ScanSkillRootOptions {
517
584
  interface ScannedSkillRootEntry {
518
585
  name: string;
519
586
  dirent: Dirent;
520
- skillMd: AregTextFileState;
587
+ skillMd: TextFileState;
521
588
  }
522
589
 
523
590
  async function scanSkillRootEntries(
@@ -727,7 +794,7 @@ async function listChildNames(directory: string): Promise<string[]> {
727
794
  const info = await lstat(directory);
728
795
  if (!info.isDirectory()) return [];
729
796
  const entries = await readdir(directory);
730
- return sortStrings(entries.filter((entry) => entry !== ".DS_Store"));
797
+ return sortStringsLocaleAware(entries.filter((entry) => entry !== ".DS_Store"));
731
798
  } catch (error) {
732
799
  if (isNodeErrorCode(error, "ENOENT")) return [];
733
800
  return [];
@@ -736,7 +803,7 @@ async function listChildNames(directory: string): Promise<string[]> {
736
803
 
737
804
  async function readLocallyExcludedSkillNames(options: {
738
805
  projectDir: string;
739
- git: GitGateway;
806
+ git: AregGitGateway;
740
807
  }): Promise<string[]> {
741
808
  const gitPath = await options.git.gitPath({
742
809
  cwd: options.projectDir,
@@ -754,7 +821,7 @@ async function readLocallyExcludedSkillNames(options: {
754
821
  if (line.startsWith(prefix)) names.add(line.slice(prefix.length));
755
822
  }
756
823
  }
757
- return sortStrings([...names]);
824
+ return sortStringsLocaleAware([...names]);
758
825
  }
759
826
 
760
827
  async function inspectPairingDirectories(projectDir: string): Promise<AregCheckPairingDirectory[]> {
@@ -784,7 +851,7 @@ async function inspectPairingDirectories(projectDir: string): Promise<AregCheckP
784
851
  ...(claude.type === "file" ? { claudeText: claude.text } : {}),
785
852
  });
786
853
  }
787
- const subdirs = sortStrings(
854
+ const subdirs = sortStringsLocaleAware(
788
855
  entries
789
856
  .filter((entry) => entry.isDirectory())
790
857
  .map((entry) => entry.name)