@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.
- package/package.json +7 -6
- package/src/cli.ts +0 -33
- package/src/context.ts +3 -29
- package/src/fake-gateways.ts +108 -380
- package/src/gateways/errors.ts +2 -2
- package/src/gateways/fs-utils.ts +2 -2
- package/src/gateways/project-fs.ts +29 -41
- package/src/gateways/project-gateway.ts +98 -31
- package/src/gateways.ts +34 -150
- package/src/index.ts +2 -28
- package/src/operations/check.ts +33 -20
- package/src/operations/doctor-skills.ts +71 -7
- package/src/operations/file-state.ts +4 -4
- package/src/operations/manifest-source-findings.ts +45 -0
- package/src/operations/manifest-sources.ts +119 -0
- package/src/operations/pi-settings.ts +2 -5
- package/src/operations/project-inspection.ts +32 -28
- package/src/operations/project-mutations.ts +5 -21
- package/src/operations/project-resolution.ts +2 -2
- package/src/operations/skill-find.ts +23 -3
- package/src/operations/skill-kind-apply-plan.ts +2 -2
- package/src/operations/skill-kind-frontmatter.ts +1 -1
- package/src/operations/skill-kind-inference.ts +30 -14
- package/src/operations/skill-kind.ts +34 -12
- package/src/sort.ts +3 -3
- package/src/gateways/command-constants.ts +0 -1
- package/src/gateways/github-gateway.ts +0 -111
- package/src/gateways/host-gateway.ts +0 -35
- package/src/gateways/mutation-policy.ts +0 -94
- package/src/gateways/npx-skills-gateway.ts +0 -53
- package/src/gateways/prompt-gateway.ts +0 -21
- package/src/gateways/skillx-workspace-gateway.ts +0 -220
- package/src/operations/frontmatter.ts +0 -151
- package/src/operations/init.ts +0 -548
- package/src/operations/lockfile.ts +0 -107
- package/src/operations/managed-markdown-block.ts +0 -47
- package/src/operations/project-agents.ts +0 -115
- package/src/operations/skill-mirror-conventions.ts +0 -126
- package/src/operations/skillx.ts +0 -305
- package/src/operations/toml-section.ts +0 -53
- package/src/operations/update-skills.ts +0 -325
- package/src/real-gateways.ts +0 -6
- package/src/skill-lookup.ts +0 -254
package/src/gateways/errors.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AregErrorInfo } from "../gateways.ts";
|
|
2
2
|
|
|
3
|
-
export function errorInfo(code: string, message: string
|
|
4
|
-
return
|
|
3
|
+
export function errorInfo(code: string, message: string): AregErrorInfo {
|
|
4
|
+
return { code, message };
|
|
5
5
|
}
|
package/src/gateways/fs-utils.ts
CHANGED
|
@@ -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 {
|
|
5
|
+
import type { PathState } from "@nseng-ai/harness-artifacts/api";
|
|
6
6
|
|
|
7
|
-
export async function inspectPath(candidate: string): Promise<
|
|
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
|
|
6
|
+
import type { AregErrorInfo } from "../gateways.ts";
|
|
7
7
|
import {
|
|
8
8
|
classifySkillMirrorSymlinkState,
|
|
9
9
|
expectedMirrorTarget,
|
|
10
|
-
|
|
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<
|
|
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
|
-
"
|
|
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("
|
|
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
|
-
"
|
|
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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
118
|
-
|
|
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
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 "
|
|
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 {
|
|
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:
|
|
75
|
+
private readonly git: AregGitGateway;
|
|
65
76
|
|
|
66
|
-
constructor(options: { git?:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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 =
|
|
854
|
+
const subdirs = sortStringsLocaleAware(
|
|
788
855
|
entries
|
|
789
856
|
.filter((entry) => entry.isDirectory())
|
|
790
857
|
.map((entry) => entry.name)
|