@nseng-ai/areg 0.1.1
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 +29 -0
- package/src/cli.ts +109 -0
- package/src/context.ts +59 -0
- package/src/fake-gateways.ts +1009 -0
- package/src/gateways/command-constants.ts +1 -0
- package/src/gateways/errors.ts +5 -0
- package/src/gateways/fs-utils.ts +26 -0
- package/src/gateways/github-gateway.ts +111 -0
- package/src/gateways/host-gateway.ts +35 -0
- package/src/gateways/mutation-policy.ts +94 -0
- package/src/gateways/npx-skills-gateway.ts +53 -0
- package/src/gateways/project-fs.ts +320 -0
- package/src/gateways/project-gateway.ts +801 -0
- package/src/gateways/prompt-gateway.ts +21 -0
- package/src/gateways/skill-kind-classification.ts +39 -0
- package/src/gateways/skillx-workspace-gateway.ts +220 -0
- package/src/gateways.ts +361 -0
- package/src/index.ts +48 -0
- package/src/operations/check.ts +559 -0
- package/src/operations/doctor-skills-report.ts +109 -0
- package/src/operations/doctor-skills-severity.ts +9 -0
- package/src/operations/doctor-skills.ts +471 -0
- package/src/operations/file-state.ts +77 -0
- package/src/operations/frontmatter.ts +151 -0
- package/src/operations/init.ts +548 -0
- package/src/operations/lockfile.ts +107 -0
- package/src/operations/managed-markdown-block.ts +47 -0
- package/src/operations/pi-replacement.ts +33 -0
- package/src/operations/pi-settings.ts +60 -0
- package/src/operations/project-agents.ts +115 -0
- package/src/operations/project-inspection.ts +165 -0
- package/src/operations/project-mutations.ts +383 -0
- package/src/operations/project-resolution.ts +53 -0
- package/src/operations/skill-find.ts +295 -0
- package/src/operations/skill-kind-apply-plan.ts +531 -0
- package/src/operations/skill-kind-frontmatter.ts +55 -0
- package/src/operations/skill-kind-inference.ts +391 -0
- package/src/operations/skill-kind.ts +525 -0
- package/src/operations/skill-mirror-conventions.ts +126 -0
- package/src/operations/skillx.ts +305 -0
- package/src/operations/toml-section.ts +53 -0
- package/src/operations/update-skills.ts +325 -0
- package/src/real-gateways.ts +6 -0
- package/src/skill-lookup.ts +254 -0
- package/src/sort.ts +7 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { failure, negative, ok, type ClinkrExit } from "@nseng-ai/clinkr";
|
|
2
|
+
import {
|
|
3
|
+
SKILL_LOOKUP_ROOT_DESCRIPTORS,
|
|
4
|
+
SKILL_LOOKUP_ROOTS,
|
|
5
|
+
SKILL_LOOKUP_SOURCE_TYPES,
|
|
6
|
+
buildSkillLookupSearchedRoots,
|
|
7
|
+
skillLookupFileRelativePath,
|
|
8
|
+
skillLookupRootRank,
|
|
9
|
+
type SkillLookupRoot,
|
|
10
|
+
} from "../skill-lookup.ts";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
|
|
13
|
+
import type { AregCliContext } from "../context.ts";
|
|
14
|
+
import type { AregSkillFindSkillInspection } from "../gateways.ts";
|
|
15
|
+
import { toProjectPath } from "../gateways/project-fs.ts";
|
|
16
|
+
import { parseSkillFrontmatterBlock } from "./frontmatter.ts";
|
|
17
|
+
import { inspectResolvedProjectGitRoot } from "./project-resolution.ts";
|
|
18
|
+
|
|
19
|
+
const skillFindRootSchema = z.enum(SKILL_LOOKUP_ROOTS);
|
|
20
|
+
const skillFindSourceTypeSchema = z.enum(SKILL_LOOKUP_SOURCE_TYPES);
|
|
21
|
+
|
|
22
|
+
const skillFindWarningSchema = z.object({
|
|
23
|
+
code: z.string(),
|
|
24
|
+
message: z.string(),
|
|
25
|
+
path: z.string(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const skillFindMatchSchema = z.object({
|
|
29
|
+
name: z.string(),
|
|
30
|
+
root: skillFindRootSchema,
|
|
31
|
+
sourceType: skillFindSourceTypeSchema,
|
|
32
|
+
isPreferred: z.boolean(),
|
|
33
|
+
baseRelativePath: z.string(),
|
|
34
|
+
skillFileRelativePath: z.string(),
|
|
35
|
+
basePath: z.string(),
|
|
36
|
+
skillFilePath: z.string(),
|
|
37
|
+
frontmatterName: z.string().optional(),
|
|
38
|
+
description: z.string().optional(),
|
|
39
|
+
shouldDisableModelInvocation: z.boolean().optional(),
|
|
40
|
+
warnings: z.array(skillFindWarningSchema).optional(),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const skillFindSearchedRootSchema = z.object({
|
|
44
|
+
root: skillFindRootSchema,
|
|
45
|
+
sourceType: skillFindSourceTypeSchema,
|
|
46
|
+
searchedRelativePath: z.string(),
|
|
47
|
+
searchedPath: z.string(),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const skillFindCandidateSchema = z.object({
|
|
51
|
+
name: z.string(),
|
|
52
|
+
roots: z.array(skillFindRootSchema),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export const skillFindRequestSchema = z.object({
|
|
56
|
+
path: z
|
|
57
|
+
.string()
|
|
58
|
+
.default(".")
|
|
59
|
+
.describe("Project directory or subdirectory to inspect (default: current directory)."),
|
|
60
|
+
skill: z.string().describe("Exact managed skill name to find."),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export const skillFindSuccessResultSchema = z.object({
|
|
64
|
+
projectDir: z.string(),
|
|
65
|
+
query: z.string(),
|
|
66
|
+
preferred: skillFindMatchSchema,
|
|
67
|
+
matches: z.array(skillFindMatchSchema),
|
|
68
|
+
searchedRoots: z.array(skillFindSearchedRootSchema),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const skillFindMissResultSchema = z.object({
|
|
72
|
+
projectDir: z.string(),
|
|
73
|
+
query: z.string(),
|
|
74
|
+
matches: z.array(skillFindMatchSchema),
|
|
75
|
+
candidates: z.array(skillFindCandidateSchema),
|
|
76
|
+
candidateLimit: z.number(),
|
|
77
|
+
searchedRoots: z.array(skillFindSearchedRootSchema),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export const skillFindResultSchema = z.union([
|
|
81
|
+
skillFindSuccessResultSchema,
|
|
82
|
+
skillFindMissResultSchema,
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
export type SkillFindRequest = z.infer<typeof skillFindRequestSchema>;
|
|
86
|
+
export type SkillFindMatch = z.infer<typeof skillFindMatchSchema>;
|
|
87
|
+
export type SkillFindSearchedRoot = z.infer<typeof skillFindSearchedRootSchema>;
|
|
88
|
+
export type SkillFindSuccessResult = z.infer<typeof skillFindSuccessResultSchema>;
|
|
89
|
+
export type SkillFindMissResult = z.infer<typeof skillFindMissResultSchema>;
|
|
90
|
+
export type SkillFindResult = z.infer<typeof skillFindResultSchema>;
|
|
91
|
+
|
|
92
|
+
export async function runSkillFind(
|
|
93
|
+
ctx: AregCliContext,
|
|
94
|
+
request: SkillFindRequest,
|
|
95
|
+
): Promise<ClinkrExit<SkillFindResult>> {
|
|
96
|
+
const resolved = await inspectResolvedProjectGitRoot(ctx, request.path, (context, projectPath) =>
|
|
97
|
+
context.project.inspectProjectBase({
|
|
98
|
+
cwd: context.cwd,
|
|
99
|
+
projectPath,
|
|
100
|
+
env: context.env,
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
if (resolved.type === "error") return failure("project-inspection-failed", resolved.message);
|
|
104
|
+
const projectDir = resolved.projectDir;
|
|
105
|
+
const inspection = await ctx.project.inspectSkillFindRoots({ projectDir, env: ctx.env });
|
|
106
|
+
const searchedRoots = buildSkillFindSearchedRoots(projectDir, request.skill);
|
|
107
|
+
const exactSkills = inspection.skills
|
|
108
|
+
.filter((skill) => skill.name === request.skill)
|
|
109
|
+
.toSorted(compareSkillFindInspection);
|
|
110
|
+
if (exactSkills.length > 0) {
|
|
111
|
+
const matches = exactSkills.map((skill, index) =>
|
|
112
|
+
toSkillFindMatch(projectDir, skill, index === 0),
|
|
113
|
+
);
|
|
114
|
+
const preferred = matches[0];
|
|
115
|
+
if (preferred === undefined)
|
|
116
|
+
return failure(
|
|
117
|
+
"skill-find-invalid-result",
|
|
118
|
+
`Exact skill vanished while finding ${request.skill}.`,
|
|
119
|
+
);
|
|
120
|
+
return ok({
|
|
121
|
+
projectDir,
|
|
122
|
+
query: request.skill,
|
|
123
|
+
preferred,
|
|
124
|
+
matches,
|
|
125
|
+
searchedRoots,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const miss = {
|
|
129
|
+
projectDir,
|
|
130
|
+
query: request.skill,
|
|
131
|
+
matches: [],
|
|
132
|
+
candidates: buildSkillFindCandidates(inspection.skills, request.skill, 10),
|
|
133
|
+
candidateLimit: 10,
|
|
134
|
+
searchedRoots,
|
|
135
|
+
};
|
|
136
|
+
return negative(`Skill not found: ${request.skill}`, {
|
|
137
|
+
data: miss,
|
|
138
|
+
human: renderSkillFindMiss(miss),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function renderSkillFind(result: SkillFindResult): string {
|
|
143
|
+
if ("preferred" in result) return renderSkillFindSuccess(result);
|
|
144
|
+
return renderSkillFindMiss(result);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildSkillFindSearchedRoots(projectDir: string, query: string): SkillFindSearchedRoot[] {
|
|
148
|
+
return buildSkillLookupSearchedRoots(projectDir, query).map((root) => ({
|
|
149
|
+
...root,
|
|
150
|
+
searchedPath: toProjectPath(projectDir, root.searchedRelativePath),
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function toSkillFindMatch(
|
|
155
|
+
projectDir: string,
|
|
156
|
+
skill: AregSkillFindSkillInspection,
|
|
157
|
+
isPreferred: boolean,
|
|
158
|
+
): SkillFindMatch {
|
|
159
|
+
const skillFileRelativePath = skillLookupFileRelativePath(skill.root, skill.name);
|
|
160
|
+
const skillFilePath = toProjectPath(projectDir, skillFileRelativePath);
|
|
161
|
+
const frontmatter = parseSkillFindFrontmatter(skillFileRelativePath, skill.skillMd);
|
|
162
|
+
return {
|
|
163
|
+
name: skill.name,
|
|
164
|
+
root: skill.root,
|
|
165
|
+
sourceType: skill.sourceType,
|
|
166
|
+
isPreferred,
|
|
167
|
+
baseRelativePath: skill.baseRelativePath,
|
|
168
|
+
skillFileRelativePath,
|
|
169
|
+
basePath: toProjectPath(projectDir, skill.baseRelativePath),
|
|
170
|
+
skillFilePath,
|
|
171
|
+
...frontmatter.fields,
|
|
172
|
+
...(frontmatter.warnings.length === 0 ? {} : { warnings: frontmatter.warnings }),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function parseSkillFindFrontmatter(
|
|
177
|
+
skillFileRelativePath: string,
|
|
178
|
+
skillMd: AregSkillFindSkillInspection["skillMd"],
|
|
179
|
+
): {
|
|
180
|
+
fields: Pick<SkillFindMatch, "frontmatterName" | "description" | "shouldDisableModelInvocation">;
|
|
181
|
+
warnings: Array<{ code: string; message: string; path: string }>;
|
|
182
|
+
} {
|
|
183
|
+
if (skillMd.type !== "file") {
|
|
184
|
+
return {
|
|
185
|
+
fields: {},
|
|
186
|
+
warnings: [
|
|
187
|
+
{
|
|
188
|
+
code: "skill-file-unreadable",
|
|
189
|
+
message: `Expected readable SKILL.md, got ${skillMd.type}.`,
|
|
190
|
+
path: skillFileRelativePath,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const parsed = parseSkillFrontmatterBlock(skillMd.text);
|
|
196
|
+
if (!parsed.ok) {
|
|
197
|
+
return {
|
|
198
|
+
fields: {},
|
|
199
|
+
warnings: [
|
|
200
|
+
{
|
|
201
|
+
code: parsed.error.code.replaceAll("_", "-"),
|
|
202
|
+
message: parsed.error.message,
|
|
203
|
+
path: skillFileRelativePath,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const frontmatterName = nonEmpty(parsed.value.fields.name);
|
|
209
|
+
const description = nonEmpty(parsed.value.fields.description);
|
|
210
|
+
const shouldDisableModelInvocation = parseOptionalBoolean(
|
|
211
|
+
parsed.value.fields["disable-model-invocation"],
|
|
212
|
+
);
|
|
213
|
+
return {
|
|
214
|
+
fields: {
|
|
215
|
+
...(frontmatterName === undefined ? {} : { frontmatterName }),
|
|
216
|
+
...(description === undefined ? {} : { description }),
|
|
217
|
+
...(shouldDisableModelInvocation === undefined ? {} : { shouldDisableModelInvocation }),
|
|
218
|
+
},
|
|
219
|
+
warnings: [],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function parseOptionalBoolean(value: string | undefined): boolean | undefined {
|
|
224
|
+
if (value === "true") return true;
|
|
225
|
+
if (value === "false") return false;
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function nonEmpty(value: string | undefined): string | undefined {
|
|
230
|
+
if (value === undefined) return undefined;
|
|
231
|
+
return value.length === 0 ? undefined : value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function buildSkillFindCandidates(
|
|
235
|
+
skills: readonly AregSkillFindSkillInspection[],
|
|
236
|
+
query: string,
|
|
237
|
+
limit: number,
|
|
238
|
+
): Array<{ name: string; roots: SkillLookupRoot[] }> {
|
|
239
|
+
const rootByName = new Map<string, Set<SkillLookupRoot>>();
|
|
240
|
+
for (const skill of skills) {
|
|
241
|
+
const roots = rootByName.get(skill.name) ?? new Set<SkillLookupRoot>();
|
|
242
|
+
roots.add(skill.root);
|
|
243
|
+
rootByName.set(skill.name, roots);
|
|
244
|
+
}
|
|
245
|
+
const names = [...rootByName.keys()].toSorted();
|
|
246
|
+
const lowerQuery = query.toLocaleLowerCase();
|
|
247
|
+
const rankedNames = [
|
|
248
|
+
...names.filter((name) => name.toLocaleLowerCase().startsWith(lowerQuery)),
|
|
249
|
+
...names.filter(
|
|
250
|
+
(name) =>
|
|
251
|
+
!name.toLocaleLowerCase().startsWith(lowerQuery) &&
|
|
252
|
+
name.toLocaleLowerCase().includes(lowerQuery),
|
|
253
|
+
),
|
|
254
|
+
];
|
|
255
|
+
return rankedNames.slice(0, limit).map((name) => ({
|
|
256
|
+
name,
|
|
257
|
+
roots: orderedRoots(rootByName.get(name) ?? new Set()),
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function orderedRoots(roots: ReadonlySet<SkillLookupRoot>): SkillLookupRoot[] {
|
|
262
|
+
return SKILL_LOOKUP_ROOT_DESCRIPTORS.map((descriptor) => descriptor.root).filter((root) =>
|
|
263
|
+
roots.has(root),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function compareSkillFindInspection(
|
|
268
|
+
left: AregSkillFindSkillInspection,
|
|
269
|
+
right: AregSkillFindSkillInspection,
|
|
270
|
+
): number {
|
|
271
|
+
return (
|
|
272
|
+
skillLookupRootRank(left.root) - skillLookupRootRank(right.root) ||
|
|
273
|
+
left.name.localeCompare(right.name)
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function renderSkillFindSuccess(result: SkillFindSuccessResult): string {
|
|
278
|
+
const lines = [result.query];
|
|
279
|
+
const hasDuplicates = result.matches.length > 1;
|
|
280
|
+
for (const match of result.matches) {
|
|
281
|
+
const marker = hasDuplicates ? (match.isPreferred ? "* " : " ") : "";
|
|
282
|
+
lines.push(` ${marker}${match.skillFileRelativePath}`);
|
|
283
|
+
}
|
|
284
|
+
return lines.join("\n");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function renderSkillFindMiss(result: SkillFindMissResult): string {
|
|
288
|
+
const lines = [`Skill not found: ${result.query}`, "Searched:"];
|
|
289
|
+
for (const root of result.searchedRoots) lines.push(` ${root.searchedRelativePath}`);
|
|
290
|
+
if (result.candidates.length > 0) {
|
|
291
|
+
lines.push("", "Did you mean:");
|
|
292
|
+
for (const candidate of result.candidates) lines.push(` ${candidate.name}`);
|
|
293
|
+
}
|
|
294
|
+
return lines.join("\n");
|
|
295
|
+
}
|