@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.
Files changed (45) hide show
  1. package/package.json +29 -0
  2. package/src/cli.ts +109 -0
  3. package/src/context.ts +59 -0
  4. package/src/fake-gateways.ts +1009 -0
  5. package/src/gateways/command-constants.ts +1 -0
  6. package/src/gateways/errors.ts +5 -0
  7. package/src/gateways/fs-utils.ts +26 -0
  8. package/src/gateways/github-gateway.ts +111 -0
  9. package/src/gateways/host-gateway.ts +35 -0
  10. package/src/gateways/mutation-policy.ts +94 -0
  11. package/src/gateways/npx-skills-gateway.ts +53 -0
  12. package/src/gateways/project-fs.ts +320 -0
  13. package/src/gateways/project-gateway.ts +801 -0
  14. package/src/gateways/prompt-gateway.ts +21 -0
  15. package/src/gateways/skill-kind-classification.ts +39 -0
  16. package/src/gateways/skillx-workspace-gateway.ts +220 -0
  17. package/src/gateways.ts +361 -0
  18. package/src/index.ts +48 -0
  19. package/src/operations/check.ts +559 -0
  20. package/src/operations/doctor-skills-report.ts +109 -0
  21. package/src/operations/doctor-skills-severity.ts +9 -0
  22. package/src/operations/doctor-skills.ts +471 -0
  23. package/src/operations/file-state.ts +77 -0
  24. package/src/operations/frontmatter.ts +151 -0
  25. package/src/operations/init.ts +548 -0
  26. package/src/operations/lockfile.ts +107 -0
  27. package/src/operations/managed-markdown-block.ts +47 -0
  28. package/src/operations/pi-replacement.ts +33 -0
  29. package/src/operations/pi-settings.ts +60 -0
  30. package/src/operations/project-agents.ts +115 -0
  31. package/src/operations/project-inspection.ts +165 -0
  32. package/src/operations/project-mutations.ts +383 -0
  33. package/src/operations/project-resolution.ts +53 -0
  34. package/src/operations/skill-find.ts +295 -0
  35. package/src/operations/skill-kind-apply-plan.ts +531 -0
  36. package/src/operations/skill-kind-frontmatter.ts +55 -0
  37. package/src/operations/skill-kind-inference.ts +391 -0
  38. package/src/operations/skill-kind.ts +525 -0
  39. package/src/operations/skill-mirror-conventions.ts +126 -0
  40. package/src/operations/skillx.ts +305 -0
  41. package/src/operations/toml-section.ts +53 -0
  42. package/src/operations/update-skills.ts +325 -0
  43. package/src/real-gateways.ts +6 -0
  44. package/src/skill-lookup.ts +254 -0
  45. package/src/sort.ts +7 -0
@@ -0,0 +1,254 @@
1
+ import { lstat, realpath } from "node:fs/promises";
2
+ import { dirname, join, parse, resolve } from "node:path";
3
+
4
+ import { isPathInside } from "@nseng-ai/foundation/primitives";
5
+
6
+ export const SKILL_LOOKUP_ROOT_DESCRIPTORS = [
7
+ { root: "skills", sourceType: "repo" },
8
+ { root: ".agents/skills", sourceType: "vendored" },
9
+ { root: ".claude/skills", sourceType: "claude" },
10
+ ] as const satisfies ReadonlyArray<{ root: string; sourceType: string }>;
11
+
12
+ export type SkillLookupRootDescriptor = (typeof SKILL_LOOKUP_ROOT_DESCRIPTORS)[number];
13
+ export type SkillLookupRoot = SkillLookupRootDescriptor["root"];
14
+ export type SkillLookupSourceType = SkillLookupRootDescriptor["sourceType"];
15
+
16
+ export const SKILL_LOOKUP_ROOTS = SKILL_LOOKUP_ROOT_DESCRIPTORS.map(
17
+ (descriptor): SkillLookupRoot => descriptor.root,
18
+ ) as [SkillLookupRoot, ...SkillLookupRoot[]];
19
+ export const SKILL_LOOKUP_SOURCE_TYPES = SKILL_LOOKUP_ROOT_DESCRIPTORS.map(
20
+ (descriptor): SkillLookupSourceType => descriptor.sourceType,
21
+ ) as [SkillLookupSourceType, ...SkillLookupSourceType[]];
22
+
23
+ const SKILL_LOOKUP_DESCRIPTOR_BY_ROOT = new Map<SkillLookupRoot, SkillLookupRootDescriptor>(
24
+ SKILL_LOOKUP_ROOT_DESCRIPTORS.map((descriptor) => [descriptor.root, descriptor] as const),
25
+ );
26
+ const SKILL_LOOKUP_DESCRIPTOR_BY_SOURCE_TYPE = new Map<
27
+ SkillLookupSourceType,
28
+ SkillLookupRootDescriptor
29
+ >(SKILL_LOOKUP_ROOT_DESCRIPTORS.map((descriptor) => [descriptor.sourceType, descriptor] as const));
30
+ const SKILL_LOOKUP_ROOT_RANKS = new Map<SkillLookupRoot, number>(
31
+ SKILL_LOOKUP_ROOT_DESCRIPTORS.map((descriptor, index) => [descriptor.root, index] as const),
32
+ );
33
+
34
+ export interface SkillLookupPathStat {
35
+ isFile(): boolean;
36
+ isDirectory(): boolean;
37
+ isSymbolicLink(): boolean;
38
+ }
39
+
40
+ export interface SkillLookupIo {
41
+ statPath?: (path: string) => Promise<SkillLookupPathStat>;
42
+ }
43
+
44
+ export interface SkillLookupSearchedRoot {
45
+ root: SkillLookupRoot;
46
+ sourceType: SkillLookupSourceType;
47
+ searchedRelativePath: string;
48
+ searchedPath: string;
49
+ }
50
+
51
+ export interface FoundSkillLookup {
52
+ type: "found";
53
+ root: SkillLookupRoot;
54
+ sourceType: SkillLookupSourceType;
55
+ baseRelativePath: string;
56
+ skillFileRelativePath: string;
57
+ basePath: string;
58
+ skillFilePath: string;
59
+ }
60
+
61
+ export interface MissingSkillLookup {
62
+ type: "missing";
63
+ searchedRoots: readonly SkillLookupSearchedRoot[];
64
+ }
65
+
66
+ export interface FailedSkillLookup {
67
+ type: "error";
68
+ message: string;
69
+ path: string;
70
+ }
71
+
72
+ export type SkillLookupResult = FoundSkillLookup | MissingSkillLookup | FailedSkillLookup;
73
+
74
+ export interface ResolveExactSkillLookupOptions extends SkillLookupIo {
75
+ projectDir: string;
76
+ skillName: string;
77
+ }
78
+
79
+ export interface ResolveSkillLookupProjectRootOptions extends Pick<SkillLookupIo, "statPath"> {
80
+ cwd: string;
81
+ }
82
+
83
+ export function skillLookupDescriptorForRoot(root: SkillLookupRoot): SkillLookupRootDescriptor {
84
+ const descriptor = SKILL_LOOKUP_DESCRIPTOR_BY_ROOT.get(root);
85
+ if (descriptor === undefined) throw new Error(`Unknown skill lookup root: ${root}`);
86
+ return descriptor;
87
+ }
88
+
89
+ export function skillLookupDescriptorForSourceType(
90
+ sourceType: SkillLookupSourceType,
91
+ ): SkillLookupRootDescriptor {
92
+ const descriptor = SKILL_LOOKUP_DESCRIPTOR_BY_SOURCE_TYPE.get(sourceType);
93
+ if (descriptor === undefined) throw new Error(`Unknown skill lookup source type: ${sourceType}`);
94
+ return descriptor;
95
+ }
96
+
97
+ export function skillLookupRootRank(root: SkillLookupRoot): number {
98
+ return SKILL_LOOKUP_ROOT_RANKS.get(root) ?? SKILL_LOOKUP_ROOT_DESCRIPTORS.length;
99
+ }
100
+
101
+ export function skillLookupBaseRelativePath(root: SkillLookupRoot, skillName: string): string {
102
+ return `${root}/${skillName}`;
103
+ }
104
+
105
+ export function skillLookupFileRelativePath(root: SkillLookupRoot, skillName: string): string {
106
+ return `${skillLookupBaseRelativePath(root, skillName)}/SKILL.md`;
107
+ }
108
+
109
+ export function skillLookupIoOptions(options: SkillLookupIo): SkillLookupIo {
110
+ if (options.statPath === undefined) return {};
111
+ return { statPath: options.statPath };
112
+ }
113
+
114
+ export function buildSkillLookupSearchedRoots(
115
+ projectDir: string,
116
+ skillName: string,
117
+ ): SkillLookupSearchedRoot[] {
118
+ return SKILL_LOOKUP_ROOT_DESCRIPTORS.map((descriptor) => {
119
+ const searchedRelativePath = skillLookupFileRelativePath(descriptor.root, skillName);
120
+ return {
121
+ root: descriptor.root,
122
+ sourceType: descriptor.sourceType,
123
+ searchedRelativePath,
124
+ searchedPath: join(projectDir, descriptor.root, skillName, "SKILL.md"),
125
+ };
126
+ });
127
+ }
128
+
129
+ function defaultStatPath(path: string): Promise<SkillLookupPathStat> {
130
+ return lstat(path);
131
+ }
132
+
133
+ function defaultRealpathPath(path: string): Promise<string> {
134
+ return realpath(path);
135
+ }
136
+
137
+ function containmentErrorOrUndefined(options: {
138
+ base: string;
139
+ target: string;
140
+ skillFilePath: string;
141
+ projectDir: string;
142
+ }): FailedSkillLookup | undefined {
143
+ if (isPathInside(options.base, options.target)) return undefined;
144
+ return {
145
+ type: "error",
146
+ message: `Backing skill path ${options.skillFilePath} resolves outside repository root ${options.projectDir}.`,
147
+ path: options.skillFilePath,
148
+ };
149
+ }
150
+
151
+ export async function resolveSkillLookupProjectRoot(
152
+ options: ResolveSkillLookupProjectRootOptions,
153
+ ): Promise<string> {
154
+ const statPath = options.statPath ?? defaultStatPath;
155
+ let current = resolve(options.cwd);
156
+ const root = parse(current).root;
157
+
158
+ while (true) {
159
+ if (await hasGitMarker(current, statPath)) return current;
160
+ if (current === root) {
161
+ throw new Error(`Could not find a Git repository root from ${options.cwd}.`);
162
+ }
163
+ current = dirname(current);
164
+ }
165
+ }
166
+
167
+ export async function resolveExactSkillLookup(
168
+ options: ResolveExactSkillLookupOptions,
169
+ ): Promise<SkillLookupResult> {
170
+ const statPath = options.statPath ?? defaultStatPath;
171
+ const projectDir = resolve(options.projectDir);
172
+ const realProjectDir = await defaultRealpathPath(projectDir);
173
+ const searchedRoots = buildSkillLookupSearchedRoots(projectDir, options.skillName);
174
+
175
+ for (const descriptor of SKILL_LOOKUP_ROOT_DESCRIPTORS) {
176
+ const baseRelativePath = skillLookupBaseRelativePath(descriptor.root, options.skillName);
177
+ const skillFileRelativePath = skillLookupFileRelativePath(descriptor.root, options.skillName);
178
+ const basePath = join(projectDir, descriptor.root, options.skillName);
179
+ const skillFilePath = join(basePath, "SKILL.md");
180
+ const normalizedSkillFilePath = resolve(skillFilePath);
181
+ const normalizedContainmentError = containmentErrorOrUndefined({
182
+ base: projectDir,
183
+ target: normalizedSkillFilePath,
184
+ skillFilePath,
185
+ projectDir,
186
+ });
187
+ if (normalizedContainmentError !== undefined) return normalizedContainmentError;
188
+
189
+ const skillStat = await statPathOrUndefined(statPath, skillFilePath);
190
+ if (skillStat === undefined || (!skillStat.isFile() && !skillStat.isSymbolicLink())) {
191
+ continue;
192
+ }
193
+ if (skillStat.isSymbolicLink()) {
194
+ return {
195
+ type: "error",
196
+ message: `Refusing to read symlinked backing skill at ${skillFilePath}.`,
197
+ path: skillFilePath,
198
+ };
199
+ }
200
+
201
+ let realSkillFilePath: string;
202
+ try {
203
+ realSkillFilePath = await defaultRealpathPath(skillFilePath);
204
+ } catch (error) {
205
+ return {
206
+ type: "error",
207
+ message: `Could not resolve backing skill path ${skillFilePath}: ${formatUnknownError(error)}`,
208
+ path: skillFilePath,
209
+ };
210
+ }
211
+ const realContainmentError = containmentErrorOrUndefined({
212
+ base: realProjectDir,
213
+ target: realSkillFilePath,
214
+ skillFilePath,
215
+ projectDir,
216
+ });
217
+ if (realContainmentError !== undefined) return realContainmentError;
218
+
219
+ return {
220
+ type: "found",
221
+ root: descriptor.root,
222
+ sourceType: descriptor.sourceType,
223
+ baseRelativePath,
224
+ skillFileRelativePath,
225
+ basePath,
226
+ skillFilePath,
227
+ };
228
+ }
229
+
230
+ return { type: "missing", searchedRoots };
231
+ }
232
+
233
+ async function hasGitMarker(
234
+ directory: string,
235
+ statPath: (path: string) => Promise<SkillLookupPathStat>,
236
+ ): Promise<boolean> {
237
+ const marker = await statPathOrUndefined(statPath, join(directory, ".git"));
238
+ return marker !== undefined && (marker.isDirectory() || marker.isFile());
239
+ }
240
+
241
+ async function statPathOrUndefined(
242
+ statPath: (path: string) => Promise<SkillLookupPathStat>,
243
+ path: string,
244
+ ): Promise<SkillLookupPathStat | undefined> {
245
+ try {
246
+ return await statPath(path);
247
+ } catch {
248
+ return undefined;
249
+ }
250
+ }
251
+
252
+ function formatUnknownError(error: unknown): string {
253
+ return error instanceof Error ? error.message : String(error);
254
+ }
package/src/sort.ts ADDED
@@ -0,0 +1,7 @@
1
+ export function sortStrings(values: readonly string[]): string[] {
2
+ return [...values].sort();
3
+ }
4
+
5
+ export function uniqueSortedStrings(values: readonly string[]): string[] {
6
+ return sortStrings([...new Set(values)]);
7
+ }