@vegastack/skills 0.1.0

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/index.js +512 -0
  4. package/package.json +35 -0
  5. package/skill/vegastack-arch-guardian/SKILL.md +96 -0
  6. package/skill/vegastack-arch-guardian/agents/openai.yaml +4 -0
  7. package/skill/vegastack-arch-guardian/assets/adr-template.md +40 -0
  8. package/skill/vegastack-arch-guardian/assets/answers-example.json +20 -0
  9. package/skill/vegastack-arch-guardian/assets/architecture-profile.json +25 -0
  10. package/skill/vegastack-arch-guardian/assets/architecture-profile.schema.json +213 -0
  11. package/skill/vegastack-arch-guardian/assets/deployment-review-template.md +24 -0
  12. package/skill/vegastack-arch-guardian/assets/service-design-template.md +33 -0
  13. package/skill/vegastack-arch-guardian/assets/threat-model-template.md +34 -0
  14. package/skill/vegastack-arch-guardian/references/architecture/agent-product.md +22 -0
  15. package/skill/vegastack-arch-guardian/references/architecture/ai-cost.md +22 -0
  16. package/skill/vegastack-arch-guardian/references/architecture/ai-data-boundaries.md +19 -0
  17. package/skill/vegastack-arch-guardian/references/architecture/ai-evals.md +26 -0
  18. package/skill/vegastack-arch-guardian/references/architecture/connectors-sandbox.md +39 -0
  19. package/skill/vegastack-arch-guardian/references/architecture/data-memory.md +25 -0
  20. package/skill/vegastack-arch-guardian/references/architecture/delivery-operations.md +34 -0
  21. package/skill/vegastack-arch-guardian/references/architecture/durable-execution.md +43 -0
  22. package/skill/vegastack-arch-guardian/references/architecture/flutter.md +26 -0
  23. package/skill/vegastack-arch-guardian/references/architecture/foundation.md +33 -0
  24. package/skill/vegastack-arch-guardian/references/architecture/hosting-reliability.md +37 -0
  25. package/skill/vegastack-arch-guardian/references/architecture/identity-tenancy.md +37 -0
  26. package/skill/vegastack-arch-guardian/references/architecture/model-lifecycle.md +18 -0
  27. package/skill/vegastack-arch-guardian/references/architecture/models-observability.md +23 -0
  28. package/skill/vegastack-arch-guardian/references/architecture/realtime-channels.md +16 -0
  29. package/skill/vegastack-arch-guardian/references/architecture/security-privacy.md +23 -0
  30. package/skill/vegastack-arch-guardian/references/architecture/topology-monorepo.md +47 -0
  31. package/skill/vegastack-arch-guardian/references/architecture/web.md +29 -0
  32. package/skill/vegastack-arch-guardian/references/control-catalog.json +55 -0
  33. package/skill/vegastack-arch-guardian/references/foundation-compatibility.json +44 -0
  34. package/skill/vegastack-arch-guardian/references/golden-patterns.md +43 -0
  35. package/skill/vegastack-arch-guardian/references/profile-governance.md +54 -0
  36. package/skill/vegastack-arch-guardian/references/rule-model.json +36 -0
  37. package/skill/vegastack-arch-guardian/references/workflows.md +45 -0
  38. package/skill/vegastack-arch-guardian/refresh/REFRESH.md +40 -0
  39. package/skill/vegastack-arch-guardian/refresh/sources.json +1159 -0
  40. package/skill/vegastack-arch-guardian/scripts/architecture-check.mjs +323 -0
  41. package/skill/vegastack-arch-guardian/scripts/lib.mjs +57 -0
  42. package/skill/vegastack-arch-guardian/scripts/profile-tool.mjs +223 -0
  43. package/skill/vegastack-arch-guardian/scripts/refresh-evidence.mjs +325 -0
  44. package/skill/vegastack-arch-guardian/scripts/schema-validate.mjs +63 -0
  45. package/skill/vegastack-arch-guardian/scripts/validate-profile.mjs +241 -0
  46. package/skill/vegastack-arch-guardian/scripts/verify-corpus.mjs +148 -0
  47. package/skill-integrity.json +48 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VegaStack
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @vegastack/skills
2
+
3
+ Installer for VegaStack Agent Skills. Installs the **vegastack-arch-guardian** skill — an architecture advisor with stable normative rules and deterministic checks — into Claude Code (`.claude/skills`) and Codex (`.agents/skills`).
4
+
5
+ ```sh
6
+ npx @vegastack/skills add vegastack-arch-guardian
7
+ ```
8
+
9
+ ## Commands
10
+
11
+ | Command | What it does |
12
+ |---|---|
13
+ | `add <skill>` | Install (or upgrade) the skill into the selected agent directories |
14
+ | `verify <skill>` | Check an installed copy against the bundled checksum manifest |
15
+ | `doctor` | Diagnose an install: integrity, profile presence, installed-vs-latest version |
16
+ | `remove <skill>` | Uninstall the skill from the selected agent directories |
17
+
18
+ ## Flags
19
+
20
+ | Flag | Meaning |
21
+ |---|---|
22
+ | `--project` / `--global` | Install into the current project (default) or the user's home directory |
23
+ | `--agent codex\|claude\|both` | Target agent runtime(s) |
24
+ | `--dir PATH` | Operate on a different project directory |
25
+ | `--dry-run` | Show what would change without writing |
26
+ | `--force` | Overwrite a modified installed copy |
27
+ | `--non-interactive` | Skip prompts and use defaults: `--agent both`, project-local (for automation) |
28
+
29
+ ## Integrity model
30
+
31
+ The package ships a checksum manifest that is verified at install and by `verify` — it proves the installed bytes match what was packed, not who published it. Publisher identity is attested separately by npm provenance, generated by the trusted-publishing release pipeline. Verify it with `npm audit signatures` or on the package's npm page.
32
+
33
+ ## Network and telemetry
34
+
35
+ Zero telemetry. The only network call in the tool is `doctor`'s single version check against registry.npmjs.org (installed-vs-latest); `add`, `verify`, and `remove` are fully offline.
36
+
37
+ ## Requirements
38
+
39
+ - Node >= 20.11
40
+ - macOS or Linux. Windows is not yet supported (path handling; tracked in the repo issues).
41
+
42
+ ## Docs
43
+
44
+ Skill content, freshness model, and policies: [github.com/vegastack/vegastack-skills](https://github.com/vegastack/vegastack-skills)
45
+
46
+ MIT license.
package/dist/index.js ADDED
@@ -0,0 +1,512 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { constants as fsConstants } from "node:fs";
6
+ import { access, cp, lstat, mkdir, open, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
7
+ import { homedir } from "node:os";
8
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { createInterface } from "node:readline/promises";
11
+ import { spawnSync } from "node:child_process";
12
+ var packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
13
+ var skillName = "vegastack-arch-guardian";
14
+ var surfaces = { codex: ".agents/skills", claude: ".claude/skills" };
15
+ var packageVersion = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8")).version;
16
+ function usage() {
17
+ return `Usage: vegastack-skills <add|verify|doctor|remove> [vegastack-arch-guardian] [options]
18
+
19
+ Options:
20
+ --agent codex|claude|both
21
+ --project | --global
22
+ --dir PATH
23
+ --dry-run
24
+ --force
25
+ --non-interactive
26
+ --version
27
+ `;
28
+ }
29
+ function parse(argv) {
30
+ const command = argv[0] && !argv[0].startsWith("-") ? argv.shift() : "help";
31
+ const options = { command, skill: skillName, dryRun: false, force: false, nonInteractive: false };
32
+ if (argv[0] && !argv[0].startsWith("-"))
33
+ options.skill = argv.shift();
34
+ while (argv.length) {
35
+ const flag = argv.shift();
36
+ if (flag === "--agent")
37
+ options.agent = argv.shift();
38
+ else if (flag === "--project")
39
+ options.mode = "project";
40
+ else if (flag === "--global")
41
+ options.mode = "global";
42
+ else if (flag === "--dir")
43
+ options.dir = argv.shift();
44
+ else if (flag === "--dry-run")
45
+ options.dryRun = true;
46
+ else if (flag === "--force")
47
+ options.force = true;
48
+ else if (flag === "--non-interactive" || flag === "--yes")
49
+ options.nonInteractive = true;
50
+ else if (flag === "--help" || flag === "-h")
51
+ options.command = "help";
52
+ else if (flag === "--version" || flag === "-v")
53
+ options.command = "version";
54
+ else
55
+ throw new Error(`Unknown option: ${flag}`);
56
+ }
57
+ if (!["add", "verify", "doctor", "remove", "version", "help"].includes(options.command))
58
+ throw new Error(`Unknown command: ${options.command}`);
59
+ if (options.skill !== skillName)
60
+ throw new Error(`Unknown skill: ${options.skill}`);
61
+ if (options.agent && !["codex", "claude", "both"].includes(options.agent))
62
+ throw new Error(`Invalid --agent: ${options.agent}`);
63
+ if (options.mode === "global" && options.dir)
64
+ throw new Error("--dir cannot be combined with --global");
65
+ return options;
66
+ }
67
+ async function prompt(options) {
68
+ if (options.agent && options.mode)
69
+ return { agent: options.agent, mode: options.mode };
70
+ if (options.nonInteractive || !process.stdin.isTTY) {
71
+ return { agent: options.agent ?? "both", mode: options.mode ?? "project" };
72
+ }
73
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
74
+ const agentAnswer = options.agent ?? await rl.question("Install for codex, claude, or both? [both] ");
75
+ const modeAnswer = options.mode ?? await rl.question("Install project-local or user-global? [project] ");
76
+ rl.close();
77
+ const agent = agentAnswer || "both";
78
+ const mode = modeAnswer === "global" ? "global" : "project";
79
+ if (!["codex", "claude", "both"].includes(agent))
80
+ throw new Error(`Invalid agent choice: ${agent}`);
81
+ return { agent, mode };
82
+ }
83
+ async function exists(path) {
84
+ try {
85
+ await lstat(path);
86
+ return true;
87
+ } catch (error) {
88
+ if (error.code === "ENOENT")
89
+ return false;
90
+ throw error;
91
+ }
92
+ }
93
+ async function assertNoSymlink(path, allowMissingTail = true) {
94
+ const absolute = resolve(path);
95
+ const parsedRoot = resolve(absolute, sep);
96
+ const parts = relative(parsedRoot, absolute).split(sep).filter(Boolean);
97
+ let current = parsedRoot;
98
+ for (const part of parts) {
99
+ current = join(current, part);
100
+ try {
101
+ const info = await lstat(current);
102
+ if (info.isSymbolicLink())
103
+ throw new Error(`Refusing symlink path component: ${current}`);
104
+ } catch (error) {
105
+ if (allowMissingTail && error.code === "ENOENT")
106
+ return;
107
+ throw error;
108
+ }
109
+ }
110
+ }
111
+ async function syncDirectory(path) {
112
+ const handle = await open(path, "r");
113
+ try {
114
+ await handle.sync();
115
+ } finally {
116
+ await handle.close();
117
+ }
118
+ }
119
+ async function durableJson(path, value) {
120
+ await assertNoSymlink(dirname(path));
121
+ await mkdir(dirname(path), { recursive: true });
122
+ await assertNoSymlink(dirname(path), false);
123
+ if (await exists(path))
124
+ await assertNoSymlink(path, false);
125
+ const temporary = `${path}.${randomUUID()}.tmp`;
126
+ const handle = await open(temporary, "wx");
127
+ try {
128
+ await handle.writeFile(`${JSON.stringify(value, null, 2)}
129
+ `);
130
+ await handle.sync();
131
+ } finally {
132
+ await handle.close();
133
+ }
134
+ await rename(temporary, path);
135
+ await syncDirectory(dirname(path));
136
+ }
137
+ async function recoverInstall(base) {
138
+ const journalPath = join(base, ".vegastack", ".skills-install-transaction.json");
139
+ if (!await exists(journalPath))
140
+ return;
141
+ await assertNoSymlink(journalPath, false);
142
+ const journal = JSON.parse(await readFile(journalPath, "utf8"));
143
+ if (journal.schemaVersion !== 1 || !["prepared", "committed"].includes(journal.status) || !Array.isArray(journal.operations))
144
+ throw new Error(`Invalid installer recovery journal: ${journalPath}`);
145
+ const seenAgents = new Set;
146
+ for (const operation of journal.operations) {
147
+ if (!["codex", "claude"].includes(operation.agent) || seenAgents.has(operation.agent))
148
+ throw new Error(`Untrusted installer recovery journal: invalid agent`);
149
+ seenAgents.add(operation.agent);
150
+ const expectedDestination = join(base, surfaces[operation.agent], skillName);
151
+ if (resolve(operation.destination) !== expectedDestination || typeof operation.existed !== "boolean")
152
+ throw new Error(`Untrusted installer recovery journal: destination outside installer roots`);
153
+ const expectedParent = dirname(expectedDestination);
154
+ const validateTemporary = (path, kind) => {
155
+ if (!path)
156
+ return kind === "backup" && !operation.existed;
157
+ return dirname(resolve(path)) === expectedParent && basename(path).startsWith(`.${skillName}.${kind}-`);
158
+ };
159
+ if (!validateTemporary(operation.stage, "stage") || !validateTemporary(operation.backup, "backup"))
160
+ throw new Error(`Untrusted installer recovery journal: invalid transaction path`);
161
+ }
162
+ for (const operation of [...journal.operations].reverse()) {
163
+ for (const path of [operation.destination, operation.stage, operation.backup].filter(Boolean))
164
+ await assertNoSymlink(path);
165
+ if (journal.status === "prepared") {
166
+ if (operation.backup && await exists(operation.backup)) {
167
+ if (await exists(operation.destination))
168
+ await rm(operation.destination, { recursive: true, force: true });
169
+ await rename(operation.backup, operation.destination);
170
+ } else if (!operation.existed && await exists(operation.destination))
171
+ await rm(operation.destination, { recursive: true, force: true });
172
+ } else if (operation.backup && await exists(operation.backup))
173
+ await rm(operation.backup, { recursive: true, force: true });
174
+ if (await exists(operation.stage))
175
+ await rm(operation.stage, { recursive: true, force: true });
176
+ }
177
+ await rm(journalPath, { force: true });
178
+ await syncDirectory(dirname(journalPath));
179
+ }
180
+ async function withInstallLock(base, callback) {
181
+ const directory = join(base, ".vegastack");
182
+ const lockPath = join(directory, ".skills-install.lock");
183
+ await assertNoSymlink(directory);
184
+ await mkdir(directory, { recursive: true });
185
+ await assertNoSymlink(directory, false);
186
+ let handle;
187
+ try {
188
+ handle = await open(lockPath, "wx");
189
+ } catch (error) {
190
+ if (error.code !== "EEXIST")
191
+ throw error;
192
+ await assertNoSymlink(lockPath, false);
193
+ let active = true;
194
+ try {
195
+ const owner = JSON.parse(await readFile(lockPath, "utf8"));
196
+ if (!Number.isInteger(owner.pid) || owner.pid <= 0)
197
+ active = false;
198
+ else
199
+ try {
200
+ process.kill(owner.pid, 0);
201
+ } catch {
202
+ active = false;
203
+ }
204
+ } catch {
205
+ active = false;
206
+ }
207
+ if (active)
208
+ throw new Error(`Another VegaStack skill installation is active: ${lockPath}`);
209
+ await rm(lockPath, { force: true });
210
+ return withInstallLock(base, callback);
211
+ }
212
+ try {
213
+ await handle.writeFile(`${JSON.stringify({ schemaVersion: 1, pid: process.pid, startedAt: new Date().toISOString() })}
214
+ `);
215
+ await handle.sync();
216
+ return await callback();
217
+ } finally {
218
+ await handle.close();
219
+ await rm(lockPath, { force: true });
220
+ await syncDirectory(directory);
221
+ }
222
+ }
223
+ async function listFiles(root) {
224
+ const output = [];
225
+ async function walk(directory) {
226
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
227
+ const path = join(directory, entry.name);
228
+ if (entry.isSymbolicLink())
229
+ throw new Error(`Refusing symlink in skill tree: ${path}`);
230
+ if (entry.isDirectory())
231
+ await walk(path);
232
+ else if (entry.isFile())
233
+ output.push(path);
234
+ }
235
+ }
236
+ await walk(root);
237
+ return output.sort();
238
+ }
239
+ var hash = (body) => createHash("sha256").update(body).digest("hex");
240
+ async function loadSource() {
241
+ const source = join(packageRoot, "skill", skillName);
242
+ const integrityPath = join(packageRoot, "skill-integrity.json");
243
+ const manifest = JSON.parse(await readFile(integrityPath, "utf8"));
244
+ if (manifest.schemaVersion !== 1 || manifest.skill !== skillName)
245
+ throw new Error("Invalid bundled skill manifest");
246
+ const skill = await readFile(join(source, "SKILL.md"), "utf8");
247
+ if (!skill.startsWith(`---
248
+ `) || !/^name: vegastack-arch-guardian$/m.test(skill) || !/^description: .+/m.test(skill)) {
249
+ throw new Error("Bundled skill fails Agent Skills frontmatter validation");
250
+ }
251
+ const observed = {};
252
+ for (const file of await listFiles(source))
253
+ observed[relative(source, file).split(sep).join("/")] = hash(await readFile(file));
254
+ if (JSON.stringify(observed) !== JSON.stringify(manifest.files))
255
+ throw new Error("Bundled skill checksum mismatch");
256
+ return { source, manifest };
257
+ }
258
+ function destinations(agent, mode, directory) {
259
+ const agents = agent === "both" ? ["codex", "claude"] : [agent];
260
+ const base = mode === "global" ? homedir() : resolve(directory ?? process.cwd());
261
+ return agents.map((item) => ({ agent: item, destination: join(base, surfaces[item], skillName) }));
262
+ }
263
+ async function compare(destination, manifest) {
264
+ if (!await exists(destination))
265
+ return { status: "missing", issues: ["not installed"] };
266
+ await assertNoSymlink(destination, false);
267
+ const issues = [];
268
+ const actualFiles = await listFiles(destination);
269
+ const actualKeys = new Set(actualFiles.map((file) => relative(destination, file).split(sep).join("/")).filter((key) => key !== ".vegastack-install.json"));
270
+ for (const [key, expected] of Object.entries(manifest.files)) {
271
+ if (!actualKeys.has(key))
272
+ issues.push(`missing ${key}`);
273
+ else if (hash(await readFile(join(destination, key))) !== expected)
274
+ issues.push(`changed ${key}`);
275
+ }
276
+ for (const key of actualKeys)
277
+ if (!(key in manifest.files))
278
+ issues.push(`unexpected ${key}`);
279
+ return { status: issues.length ? "drifted" : "verified", issues };
280
+ }
281
+ async function install(options) {
282
+ const choice = await prompt(options);
283
+ const base = choice.mode === "global" ? homedir() : resolve(options.dir ?? process.cwd());
284
+ if (!options.dryRun)
285
+ return withInstallLock(base, () => installLocked(options, choice, base));
286
+ return installLocked(options, choice, base, false);
287
+ }
288
+ async function installLocked(options, choice, base, recover = true) {
289
+ if (recover)
290
+ await recoverInstall(base);
291
+ const { source, manifest } = await loadSource();
292
+ const targets = destinations(choice.agent, choice.mode, options.dir);
293
+ const operations = [];
294
+ for (const target of targets) {
295
+ await assertNoSymlink(target.destination);
296
+ const parent = dirname(target.destination);
297
+ await assertNoSymlink(parent);
298
+ const existed = await exists(target.destination);
299
+ if (existed) {
300
+ const comparison = await compare(target.destination, manifest);
301
+ if (comparison.status === "verified" && !options.force) {
302
+ console.log(`unchanged ${target.agent}: ${target.destination}`);
303
+ continue;
304
+ }
305
+ if (!options.force)
306
+ throw new Error(`Refusing differing installation without --force: ${target.destination}`);
307
+ }
308
+ const suffix = randomUUID();
309
+ operations.push({ ...target, existed, stage: join(parent, `.${skillName}.stage-${suffix}`), backup: existed ? join(parent, `.${skillName}.backup-${suffix}`) : undefined });
310
+ }
311
+ if (options.dryRun) {
312
+ for (const operation of operations)
313
+ console.log(`would install ${operation.agent}: ${operation.destination}`);
314
+ return;
315
+ }
316
+ if (!operations.length)
317
+ return;
318
+ const journalPath = join(base, ".vegastack", ".skills-install-transaction.json");
319
+ const staged = [];
320
+ const applied = [];
321
+ try {
322
+ for (const operation of operations) {
323
+ await mkdir(dirname(operation.destination), { recursive: true });
324
+ await assertNoSymlink(dirname(operation.destination), false);
325
+ await cp(source, operation.stage, { recursive: true, dereference: false, errorOnExist: true });
326
+ await writeFile(join(operation.stage, ".vegastack-install.json"), `${JSON.stringify({ installer: "@vegastack/skills", version: packageVersion, manifest }, null, 2)}
327
+ `, { flag: "wx" });
328
+ const stagedCheck = await compare(operation.stage, manifest);
329
+ if (stagedCheck.status !== "verified")
330
+ throw new Error(`Staged copy failed verification: ${stagedCheck.issues.join(", ")}`);
331
+ staged.push(operation);
332
+ }
333
+ await durableJson(journalPath, { schemaVersion: 1, status: "prepared", operations });
334
+ for (const operation of operations) {
335
+ await assertNoSymlink(dirname(operation.destination), false);
336
+ if (await exists(operation.destination))
337
+ await assertNoSymlink(operation.destination, false);
338
+ if (operation.existed && operation.backup)
339
+ await rename(operation.destination, operation.backup);
340
+ try {
341
+ await rename(operation.stage, operation.destination);
342
+ } catch (error) {
343
+ if (operation.existed && operation.backup && await exists(operation.backup))
344
+ await rename(operation.backup, operation.destination);
345
+ throw error;
346
+ }
347
+ applied.push(operation);
348
+ }
349
+ await durableJson(journalPath, { schemaVersion: 1, status: "committed", operations });
350
+ } catch (error) {
351
+ if (await exists(journalPath))
352
+ await recoverInstall(base);
353
+ else
354
+ for (const operation of [...applied].reverse()) {
355
+ await rm(operation.destination, { recursive: true, force: true });
356
+ if (operation.backup && await exists(operation.backup))
357
+ await rename(operation.backup, operation.destination);
358
+ }
359
+ for (const operation of staged)
360
+ await rm(operation.stage, { recursive: true, force: true });
361
+ throw error;
362
+ }
363
+ for (const operation of applied)
364
+ if (operation.backup) {
365
+ try {
366
+ await rm(operation.backup, { recursive: true, force: true });
367
+ } catch (error) {
368
+ console.warn(`warning: installed successfully but could not remove backup ${operation.backup}: ${error.message}`);
369
+ }
370
+ }
371
+ await rm(journalPath, { force: true });
372
+ await syncDirectory(dirname(journalPath));
373
+ for (const operation of applied)
374
+ console.log(`installed ${operation.agent}: ${operation.destination}`);
375
+ }
376
+ async function verify(options) {
377
+ const choice = await prompt(options);
378
+ const { manifest } = await loadSource();
379
+ let failed = false;
380
+ for (const target of destinations(choice.agent, choice.mode, options.dir)) {
381
+ const result = await compare(target.destination, manifest);
382
+ console.log(`${result.status} ${target.agent}: ${target.destination}${result.issues.length ? ` (${result.issues.join(", ")})` : ""}`);
383
+ if (result.status !== "verified")
384
+ failed = true;
385
+ }
386
+ if (failed)
387
+ process.exitCode = 1;
388
+ }
389
+ async function latestPublishedVersion() {
390
+ try {
391
+ const response = await fetch("https://registry.npmjs.org/@vegastack%2fskills/latest", { signal: AbortSignal.timeout(3000) });
392
+ if (!response.ok)
393
+ return null;
394
+ const version = (await response.json()).version;
395
+ return typeof version === "string" && /^\d+\.\d+\.\d+/.test(version) ? version : null;
396
+ } catch {
397
+ return null;
398
+ }
399
+ }
400
+ async function removeSkill(options) {
401
+ const choice = await prompt(options);
402
+ let removed = 0;
403
+ for (const target of destinations(choice.agent, choice.mode, options.dir)) {
404
+ if (!await exists(target.destination)) {
405
+ console.log(`not installed ${target.agent}: ${target.destination}`);
406
+ continue;
407
+ }
408
+ await assertNoSymlink(target.destination, false);
409
+ if (!options.force) {
410
+ const { manifest } = await loadSource();
411
+ const comparison = await compare(target.destination, manifest);
412
+ if (comparison.status === "drifted")
413
+ throw new Error(`Installation differs from the bundled skill (possibly locally modified); re-run with --force to remove anyway: ${target.destination}`);
414
+ }
415
+ if (options.dryRun) {
416
+ console.log(`would remove ${target.agent}: ${target.destination}`);
417
+ continue;
418
+ }
419
+ await rm(target.destination, { recursive: true, force: true });
420
+ removed += 1;
421
+ console.log(`removed ${target.agent}: ${target.destination}`);
422
+ }
423
+ if (!removed && !options.dryRun)
424
+ process.exitCode = 1;
425
+ }
426
+ async function doctor(options) {
427
+ const base = options.mode === "global" ? homedir() : resolve(options.dir ?? process.cwd());
428
+ await access(base, fsConstants.R_OK | fsConstants.W_OK);
429
+ await assertNoSymlink(base, false);
430
+ let failed = false;
431
+ const profileCandidates = [join(base, ".vegastack", "architecture.json"), join(base, ".vegastack", "architecture.yaml")];
432
+ if (options.mode !== "global") {
433
+ let profile = null;
434
+ for (const candidate of profileCandidates)
435
+ if (await exists(candidate)) {
436
+ profile = candidate;
437
+ break;
438
+ }
439
+ if (profile) {
440
+ if (profile.endsWith(".yaml"))
441
+ console.log(`notice: ${profile} uses the legacy .yaml name for a JSON document; rename to architecture.json`);
442
+ try {
443
+ const parsed = JSON.parse(await readFile(profile, "utf8"));
444
+ if (parsed.schemaVersion !== 3 || parsed.profileStatus !== "confirmed" || !parsed.project?.name || !parsed.environments?.production)
445
+ throw new Error("required identity fields are absent or profile is not confirmed schema v3");
446
+ console.log(`ok architecture profile: ${profile}`);
447
+ } catch (error) {
448
+ console.log(`invalid architecture profile: ${profile} (${error.message})`);
449
+ failed = true;
450
+ }
451
+ } else {
452
+ console.log(`missing architecture profile: ${profileCandidates[0]}`);
453
+ failed = true;
454
+ }
455
+ }
456
+ console.log(`ok runtime: Node ${process.versions.node}`);
457
+ const latest = await latestPublishedVersion();
458
+ if (latest && latest !== packageVersion)
459
+ console.log(`update available: installed ${packageVersion}, latest ${latest} — run: npx @vegastack/skills@latest add ${skillName} --force`);
460
+ else if (latest)
461
+ console.log(`ok installer version: ${packageVersion} (latest)`);
462
+ else
463
+ console.log(`skipped installer version check (npmjs.org unreachable); installed ${packageVersion}`);
464
+ const { manifest } = await loadSource();
465
+ let installations = 0;
466
+ let checkScript = "";
467
+ for (const agent of ["codex", "claude"]) {
468
+ const destination = join(base, surfaces[agent], skillName);
469
+ if (!await exists(destination)) {
470
+ console.log(`missing ${agent} guardian installation`);
471
+ continue;
472
+ }
473
+ installations += 1;
474
+ const result = await compare(destination, manifest);
475
+ console.log(`${result.status === "verified" ? "ok" : "invalid"} ${agent} guardian installation${result.issues.length ? ` (${result.issues.join(", ")})` : ""}`);
476
+ if (result.status !== "verified")
477
+ failed = true;
478
+ if (!checkScript)
479
+ checkScript = join(destination, "scripts", "architecture-check.mjs");
480
+ }
481
+ if (!installations)
482
+ failed = true;
483
+ if (options.mode !== "global" && checkScript && !failed) {
484
+ const result = spawnSync(process.execPath, [checkScript, base, "--json"], { encoding: "utf8" });
485
+ if (result.status === 0)
486
+ console.log("ok architecture invariants");
487
+ else {
488
+ console.log(`invalid architecture invariants: ${result.stdout.trim() || result.stderr.trim()}`);
489
+ failed = true;
490
+ }
491
+ }
492
+ if (failed)
493
+ process.exitCode = 1;
494
+ }
495
+ async function main() {
496
+ const options = parse(process.argv.slice(2));
497
+ if (options.command === "help")
498
+ return console.log(usage());
499
+ if (options.command === "version")
500
+ return console.log(packageVersion);
501
+ if (options.command === "add")
502
+ return install(options);
503
+ if (options.command === "verify")
504
+ return verify(options);
505
+ if (options.command === "remove")
506
+ return removeSkill(options);
507
+ return doctor(options);
508
+ }
509
+ main().catch((error) => {
510
+ console.error(`error: ${error.message}`);
511
+ process.exitCode = 1;
512
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@vegastack/skills",
3
+ "version": "0.1.0",
4
+ "description": "Installer and verifier for VegaStack Agent Skills (Claude Code and Codex)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/vegastack/vegastack-skills.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "homepage": "https://github.com/vegastack/vegastack-skills#readme",
13
+ "bugs": "https://github.com/vegastack/vegastack-skills/issues",
14
+ "author": "VegaStack",
15
+ "keywords": [
16
+ "agent-skills",
17
+ "claude-code",
18
+ "codex",
19
+ "skill",
20
+ "architecture",
21
+ "vegastack"
22
+ ],
23
+ "bin": { "vegastack-skills": "dist/index.js" },
24
+ "files": ["dist", "skill", "skill-integrity.json", "README.md", "LICENSE"],
25
+ "engines": { "node": ">=20.11" },
26
+ "scripts": {
27
+ "sync": "bun scripts/sync-skill.mjs",
28
+ "build": "bun run sync && bun build src/index.ts --target=node --outfile dist/index.js && chmod +x dist/index.js",
29
+ "lint": "bun scripts/lint.mjs",
30
+ "typecheck": "bunx tsc -p tsconfig.json --noEmit",
31
+ "test": "bun test test",
32
+ "prepack": "bun run build",
33
+ "pack:local": "mkdir -p ../../work/packed && npm pack --pack-destination ../../work/packed"
34
+ }
35
+ }