archgen-skill 0.0.3 → 0.0.4

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 (33) hide show
  1. package/README.md +10 -0
  2. package/bin/archgen.mjs +23 -0
  3. package/lib/block.js +94 -9
  4. package/lib/config.js +96 -0
  5. package/lib/doctor.js +123 -26
  6. package/lib/init.js +3 -2
  7. package/lib/install.js +105 -14
  8. package/lib/migrate.js +88 -0
  9. package/lib/migrations/001-stamp-provenance.mjs +77 -0
  10. package/lib/migrations/index.mjs +170 -0
  11. package/lib/restore.js +239 -0
  12. package/lib/store.js +27 -12
  13. package/lib/uninstall-project.js +6 -3
  14. package/lib/version-stamp.js +47 -0
  15. package/package.json +37 -9
  16. package/vendor/skills/archgen/SKILL.md +242 -74
  17. package/vendor/skills/archgen/archgen.config.json +16 -0
  18. package/vendor/skills/archgen/assets/skill-registry.json +4 -3
  19. package/vendor/skills/archgen/references/artifact-templates.md +251 -7
  20. package/vendor/skills/archgen/references/interview.md +180 -16
  21. package/vendor/skills/archgen/references/mcp-and-skills.md +88 -17
  22. package/vendor/skills/archgen/references/orchestration.md +102 -39
  23. package/vendor/skills/archgen/references/platforms.md +1 -14
  24. package/vendor/skills/archgen/scripts/doc-index.mjs +457 -0
  25. package/vendor/skills/archgen/scripts/impact.mjs +14 -1
  26. package/vendor/skills/archgen/scripts/lib/graph.mjs +82 -3
  27. package/vendor/skills/archgen/scripts/lib/scope.mjs +65 -0
  28. package/vendor/skills/archgen/scripts/lib/yaml.mjs +19 -3
  29. package/vendor/skills/archgen/scripts/plan-graph.mjs +329 -0
  30. package/vendor/skills/archgen/scripts/set-status.mjs +26 -6
  31. package/vendor/skills/archgen/scripts/update-agents.mjs +4 -2
  32. package/vendor/skills/archgen/scripts/validate.mjs +18 -1
  33. package/vendor/skills/archgen/scripts/verify-plan.mjs +8 -3
package/lib/restore.js ADDED
@@ -0,0 +1,239 @@
1
+ // restore.js — recovery tooling for archgen backups.
2
+ //
3
+ // Backups live in two vaults:
4
+ // project: <projectRoot>/.archgen/.backup/<ts>/<relpath…> (written by init)
5
+ // global: <skills-dir>/.archgen-backups/<ts>/archgen (written by install --copy)
6
+ //
7
+ // restoreBackups() discovers both, lists snapshots deterministically, and
8
+ // restores a chosen snapshot by copying each entry back to its original path.
9
+ // Safety invariant: the CURRENT state of every about-to-be-overwritten path is
10
+ // moved into a fresh safety snapshot FIRST — a restore never destroys anything
11
+ // without a prior backup of it. Restores themselves land atomically
12
+ // (temp-sibling dir, then rename).
13
+
14
+ import { cpSync, existsSync, lstatSync, mkdirSync, readdirSync, renameSync } from 'node:fs';
15
+ import { randomBytes } from 'node:crypto';
16
+ import { homedir } from 'node:os';
17
+ import { dirname, join, relative, resolve } from 'node:path';
18
+ import { BACKUP_ROOT_REL, moveToBackupInto } from './store.js';
19
+ import { GLOBAL_BACKUP_REL, globalTargets } from './install.js';
20
+
21
+ function toPosix(p) {
22
+ return p.split('\\').join('/');
23
+ }
24
+
25
+ function byName(a, b) {
26
+ return a < b ? -1 : a > b ? 1 : 0;
27
+ }
28
+
29
+ function lstatSafe(p) {
30
+ try { return lstatSync(p); } catch { return null; }
31
+ }
32
+
33
+ function summarize(dir) {
34
+ let files = 0;
35
+ let dirs = 0;
36
+ (function walk(d) {
37
+ for (const e of readdirSync(d, { withFileTypes: true })) {
38
+ if (e.isDirectory()) { dirs++; walk(join(d, e.name)); } else { files++; }
39
+ }
40
+ })(dir);
41
+ const topLevel = readdirSync(dir).sort(byName);
42
+ return { files, dirs, topLevel };
43
+ }
44
+
45
+ function snapshotsUnder(origin, base, vaultAbs) {
46
+ if (!existsSync(vaultAbs)) return [];
47
+ return readdirSync(vaultAbs, { withFileTypes: true })
48
+ .filter((e) => e.isDirectory())
49
+ .map((e) => e.name)
50
+ .sort(byName)
51
+ .reverse()
52
+ .map((ts) => {
53
+ const snapshotDir = join(vaultAbs, ts);
54
+ let summary = null;
55
+ try { summary = summarize(snapshotDir); } catch { summary = { files: 0, dirs: 0, topLevel: [] }; }
56
+ return { id: ts, ts, origin, base, snapshotDir, summary };
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Discover backup snapshots in both vaults.
62
+ * @param {{projectRoot?: string|null, home?: string, includeGlobal?: boolean}} opts
63
+ * @returns {Array<{id: string, ts: string, origin: 'project'|'global', base: string,
64
+ * snapshotDir: string, summary: {files: number, dirs: number, topLevel: string[]}}>}
65
+ * Sorted newest-first, project before global (deterministic order).
66
+ */
67
+ export function discoverSnapshots(opts = {}) {
68
+ const home = opts.home ?? homedir();
69
+ const out = [];
70
+ if (opts.projectRoot) {
71
+ out.push(...snapshotsUnder('project', resolve(opts.projectRoot), join(resolve(opts.projectRoot), ...BACKUP_ROOT_REL.split('/'))));
72
+ }
73
+ if (opts.includeGlobal !== false) {
74
+ for (const tdir of globalTargets(home)) {
75
+ out.push(...snapshotsUnder('global', tdir, join(tdir, GLOBAL_BACKUP_REL)));
76
+ }
77
+ }
78
+ return out.sort((a, b) => (a.ts === b.ts ? byName(a.origin + a.base, b.origin + b.base) : a.ts < b.ts ? 1 : -1));
79
+ }
80
+
81
+ /**
82
+ * List backups under a project root and the global harness dirs.
83
+ * @param {string} projectRoot project root to scan (may be '' when unused)
84
+ * @param {{home?: string, includeGlobal?: boolean}} options
85
+ */
86
+ export function listBackups(projectRoot, options = {}) {
87
+ return discoverSnapshots({ projectRoot: projectRoot || null, home: options.home, includeGlobal: options.includeGlobal });
88
+ }
89
+
90
+ function uniqueSafetyTs(base, backupRootRel) {
91
+ for (;;) {
92
+ const ts = new Date().toISOString().replace(/:/g, '-');
93
+ if (!existsSync(join(base, ...backupRootRel.split('/'), ts))) return ts;
94
+ }
95
+ }
96
+
97
+ /** Atomic copy-back: temp sibling dir, then rename into place. */
98
+ function layDown(srcAbs, destAbs) {
99
+ mkdirSync(dirname(destAbs), { recursive: true });
100
+ const tmp = join(dirname(destAbs), '.archgen-restore-' + randomBytes(6).toString('hex'));
101
+ cpSync(srcAbs, tmp, { recursive: true });
102
+ renameSync(tmp, destAbs);
103
+ }
104
+
105
+ /**
106
+ * Restore backups discovered across the project and global vaults.
107
+ *
108
+ * @param {string} projectRoot project root to scan (used for listing; may be ''
109
+ * when only global snapshots are wanted)
110
+ * @param {{list?: boolean, snapshot?: string, target?: string, home?: string,
111
+ * includeGlobal?: boolean}} options
112
+ * - `list` (default true): dry-run discovery, returns `snapshots`.
113
+ * - `snapshot`: timestamp id to restore (takes precedence over `list`).
114
+ * Ambiguous ids (same ts in several vaults) must be narrowed via
115
+ * `includeGlobal:false` or an exact single match.
116
+ * - `target`: restore INTO this directory instead of the recorded origin
117
+ * (entries keep their layout relative to it).
118
+ * @returns {{action: 'list'|'restored', snapshots?: Array<object>,
119
+ * restored?: Array<{from: string, to: string}>,
120
+ * safetyBackups?: Array<{rel: string, abs: string}>, warnings: string[]}}
121
+ */
122
+ export function restoreBackups(projectRoot, options = {}) {
123
+ const warnings = [];
124
+ const all = discoverSnapshots({
125
+ projectRoot,
126
+ home: options.home,
127
+ includeGlobal: options.includeGlobal !== false,
128
+ });
129
+
130
+ if (!options.snapshot) {
131
+ return { action: 'list', snapshots: all, warnings };
132
+ }
133
+
134
+ const matches = all.filter((s) => s.ts === options.snapshot || s.id === options.snapshot);
135
+ if (matches.length === 0) {
136
+ throw new Error(`no backup snapshot '${options.snapshot}' found (run with list mode to see available ids)`);
137
+ }
138
+ if (matches.length > 1) {
139
+ throw new Error(`ambiguous snapshot '${options.snapshot}' exists in ${matches.length} vaults; narrow the scope`);
140
+ }
141
+ const snap = matches[0];
142
+
143
+ const destBase = options.target ? resolve(options.target) : snap.base;
144
+ const entries = readdirSync(snap.snapshotDir, { withFileTypes: true })
145
+ .map((e) => e.name)
146
+ .sort(byName)
147
+ .map((name) => ({ name, srcAbs: join(snap.snapshotDir, name), destAbs: join(destBase, name) }));
148
+
149
+ // Pass 1 — safety: move EVERY existing destination aside before laying down
150
+ // anything, so a mid-restore failure still leaves current state fully backed up.
151
+ const safetyBackups = [];
152
+ const safetyRootRel = snap.origin === 'global' ? GLOBAL_BACKUP_REL : BACKUP_ROOT_REL;
153
+ const safetyBase = snap.origin === 'global' ? snap.base : destBase;
154
+ const safetyTs = uniqueSafetyTs(safetyBase, safetyRootRel);
155
+ for (const en of entries) {
156
+ if (!lstatSafe(en.destAbs)) continue;
157
+ const rel = snap.origin === 'global' ? en.name : toPosix(relative(destBase, en.destAbs));
158
+ const loc = moveToBackupInto(safetyBase, rel, safetyRootRel, safetyTs);
159
+ const abs = join(safetyBase, ...loc.split('/'));
160
+ safetyBackups.push({ rel: loc, abs });
161
+ }
162
+
163
+ // Pass 2 — copy back atomically.
164
+ const restored = [];
165
+ for (const en of entries) {
166
+ try {
167
+ layDown(en.srcAbs, en.destAbs);
168
+ restored.push({ from: en.srcAbs, to: en.destAbs });
169
+ } catch (e) {
170
+ warnings.push(`could not restore ${en.name}: ${e.message}`);
171
+ }
172
+ }
173
+
174
+ return { action: 'restored', snapshot: snap, restored, safetyBackups, warnings };
175
+ }
176
+
177
+ function parseFlags(argv) {
178
+ const opts = { list: true, snapshot: null, project: null, global: false, yes: false };
179
+ for (let i = 0; i < argv.length; i++) {
180
+ const a = argv[i];
181
+ if (a === '--list') opts.list = true;
182
+ else if (a === '--snapshot') {
183
+ const v = argv[++i];
184
+ if (!v || v.startsWith('--')) return { error: '--snapshot requires a timestamp argument' };
185
+ opts.snapshot = v;
186
+ } else if (a === '--project') {
187
+ const v = argv[i + 1] && !argv[i + 1].startsWith('--') ? argv[++i] : process.cwd();
188
+ opts.project = v;
189
+ } else if (a === '--global') opts.global = true;
190
+ else if (a === '--yes') opts.yes = true; // accepted for CLI ergonomics; restore is interactive-free
191
+ else return { error: `unknown flag: ${a}` };
192
+ }
193
+ return opts;
194
+ }
195
+
196
+ /**
197
+ * CLI entry point (`archgen-skill restore …`). Interactive-free and
198
+ * deterministic; never prompts. Exit codes: 0 success/listing, 1 on errors.
199
+ * @param {string[]} argv flags after the command name
200
+ * @returns {Promise<number>} exit code
201
+ */
202
+ export async function restoreMain(argv) {
203
+ const parsed = parseFlags(argv);
204
+ if (parsed.error) {
205
+ console.error('archgen restore: ' + parsed.error);
206
+ return 1;
207
+ }
208
+ const { list, snapshot, project, global: wantGlobal, yes } = parsed;
209
+ void yes;
210
+
211
+ const scope = {
212
+ projectRoot: wantGlobal ? null : (project ?? process.cwd()),
213
+ includeGlobal: !project || wantGlobal,
214
+ };
215
+
216
+ try {
217
+ if (!snapshot) {
218
+ const snapshots = listBackups(scope.projectRoot ?? '', { includeGlobal: scope.includeGlobal });
219
+ console.log(`archgen restore — ${snapshots.length} backup snapshot(s) found`);
220
+ for (const s of snapshots) {
221
+ const names = s.summary.topLevel.length ? s.summary.topLevel.join(', ') : '(empty)';
222
+ console.log(` ${s.ts} [${s.origin}] ${s.base} (${s.summary.files} file(s), ${s.summary.dirs} dir(s): ${names})`);
223
+ }
224
+ console.log('Dry-run listing only. Pass --snapshot <ts> to restore.');
225
+ return 0;
226
+ }
227
+
228
+ const r = restoreBackups(scope.projectRoot ?? '', { snapshot, includeGlobal: scope.includeGlobal });
229
+ console.log(`archgen restore — restoring ${snapshot} [${r.snapshot.origin}]`);
230
+ for (const sb of r.safetyBackups) console.log(` ~ safety backup of current state: ${sb.abs}`);
231
+ for (const en of r.restored) console.log(` + restored ${en.to}`);
232
+ for (const w of r.warnings) console.error(' ! ' + w);
233
+ console.log(`Done: ${r.restored.length} path(s) restored, ${r.safetyBackups.length} safety backup(s).`);
234
+ return r.warnings.length > 0 ? 1 : 0;
235
+ } catch (e) {
236
+ console.error('archgen restore: ' + (e instanceof Error ? e.message : String(e)));
237
+ return 1;
238
+ }
239
+ }
package/lib/store.js CHANGED
@@ -27,12 +27,17 @@ import {
27
27
  writeFileSync,
28
28
  } from 'node:fs';
29
29
  import { dirname, join } from 'node:path';
30
+ import { loadConfig } from './config.js';
30
31
 
31
32
  export const STORE_REL = '.agents/skills/archgen';
32
33
  export const CLAUDE_LINK_REL = '.claude/skills/archgen';
33
- export const MANIFEST_REL = '.archgen/.install-manifest.json';
34
- export const BACKUP_ROOT_REL = '.archgen/.backup';
35
- export const VERSION_FILE = '.archgen-version';
34
+
35
+ // Filenames routed through the canonical config (STORE_REL/CLAUDE_LINK_REL
36
+ // stay literal: they are path semantics, not config).
37
+ const CONFIG_FILES = loadConfig().files;
38
+ export const MANIFEST_REL = '.archgen/' + CONFIG_FILES.projectManifest;
39
+ export const BACKUP_ROOT_REL = '.archgen/' + CONFIG_FILES.backupDir;
40
+ export const VERSION_FILE = CONFIG_FILES.stamp;
36
41
 
37
42
  const GUARDED_RELPATHS = new Set(['.claude', '.agents', '.claude/skills', '.agents/skills']);
38
43
 
@@ -48,17 +53,20 @@ function byName(a, b) {
48
53
  /**
49
54
  * Stable-order directory fingerprint: sha256 over sorted relpaths plus file
50
55
  * contents (symlinks hashed via their targets). Deterministic across machines.
56
+ * The version stamp is ALWAYS ignored on both sides of a comparison: it is
57
+ * CLI metadata written at destination time (global symlink installs stamp the
58
+ * shared real copy), never skill content, so it must not read as divergence.
51
59
  * @param {string} dir absolute directory to hash
52
- * @param {{ignore?: string[]}} opts relpaths to skip (e.g. our version stamp)
60
+ * @param {{ignore?: string[]}} opts EXTRA relpaths to skip
53
61
  */
54
62
  export function hashDir(dir, opts = {}) {
55
- const ignore = opts.ignore ?? [];
63
+ const ignore = new Set([VERSION_FILE, ...(opts.ignore ?? [])]);
56
64
  const hash = createHash('sha256');
57
65
  (function walk(abs, rel) {
58
66
  const entries = readdirSync(abs, { withFileTypes: true }).sort((a, b) => byName(a.name, b.name));
59
67
  for (const ent of entries) {
60
68
  const r = rel ? rel + '/' + ent.name : ent.name;
61
- if (ignore.includes(r)) continue;
69
+ if (ignore.has(r)) continue;
62
70
  const p = join(abs, ent.name);
63
71
  if (ent.isSymbolicLink()) hash.update('l ' + r + ' -> ' + readlinkSync(p) + '\n');
64
72
  else if (ent.isDirectory()) { hash.update('d ' + r + '\n'); walk(p, r); }
@@ -99,13 +107,15 @@ export function appendEntry(root, kind, relPath) {
99
107
  }
100
108
 
101
109
  /**
102
- * Move an existing project-relative path into `.archgen/.backup/<ts>/<rel>`.
103
- * Preserves the relative layout so backups restore unambiguously.
104
- * @returns {string} backup location relative to the project root
110
+ * Move an existing path into `<root>/<backupRootRel>/<ts>/<relPath>`, preserving
111
+ * the relative layout so backups restore unambiguously. Generalized core shared
112
+ * by the project vault (.archgen/.backup) and the global vault
113
+ * (<skills-dir>/.archgen-backups); `ts` can be injected so one operation lands
114
+ * every entry under a single timestamp.
115
+ * @returns {string} backup location relative to `root`
105
116
  */
106
- export function moveToBackup(root, relPath) {
107
- const ts = new Date().toISOString().replace(/:/g, '-');
108
- const destRel = BACKUP_ROOT_REL + '/' + ts + '/' + relPath;
117
+ export function moveToBackupInto(root, relPath, backupRootRel, ts = new Date().toISOString().replace(/:/g, '-')) {
118
+ const destRel = backupRootRel + '/' + ts + '/' + relPath;
109
119
  const destAbs = join(root, ...destRel.split('/'));
110
120
  mkdirSync(dirname(destAbs), { recursive: true });
111
121
  const srcAbs = join(root, ...relPath.split('/'));
@@ -118,6 +128,11 @@ export function moveToBackup(root, relPath) {
118
128
  return destRel;
119
129
  }
120
130
 
131
+ /** Project-root flavor: move `relPath` under `.archgen/.backup/<ts>/<relPath>`. */
132
+ export function moveToBackup(root, relPath) {
133
+ return moveToBackupInto(root, relPath, BACKUP_ROOT_REL);
134
+ }
135
+
121
136
  export function rmIfExists(absPath) {
122
137
  let st = null;
123
138
  try { st = lstatSync(absPath); } catch { /* absent */ }
@@ -69,9 +69,12 @@ export function uninstallProject(projectDir, packageRoot) {
69
69
  let storeKept = false;
70
70
  if (lstatSafe(storeAbs)) {
71
71
  const st = lstatSafe(storeAbs);
72
- const mine = st.isDirectory() && !st.isSymbolicLink()
73
- ? hashDir(storeAbs, { ignore: [VERSION_FILE] })
74
- : null;
72
+ let mine = null;
73
+ if (st.isDirectory() && !st.isSymbolicLink()) {
74
+ // An unreadable/diverging-mid-walk tree must degrade to "kept", never
75
+ // crash the uninstall.
76
+ try { mine = hashDir(storeAbs, { ignore: [VERSION_FILE] }); } catch { mine = null; }
77
+ }
75
78
  let vendor = null;
76
79
  try { vendor = hashDir(resolveSkillSource(packageRoot)); } catch { vendor = null; }
77
80
  if (vendor !== null && mine === vendor) {
@@ -0,0 +1,47 @@
1
+ // version-stamp.js — the `.archgen-version` stamp contract.
2
+ //
3
+ // WHY: the VS Code extension reads `<skillRoot>/.archgen-version` to tell an
4
+ // outdated skill copy from a current one and warns non-blockingly (missing or
5
+ // unparseable content means "unknown (legacy)" — a pre-stamp install). The
6
+ // CLI is the only writer: every operation that lays down skill files (init,
7
+ // global install in symlink AND --copy mode, update via its init/doctor
8
+ // refresh) rewrites the stamp with the CLI's own package version.
9
+ //
10
+ // Format: EXACTLY `MAJOR.MINOR.PATCH\n`, utf8, no BOM. Readers elsewhere trim
11
+ // before validating, so CRLF/whitespace is tolerated on read — this writer
12
+ // never produces it. Absent file and invalid content both read as null.
13
+
14
+ import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
+ import { dirname, join } from 'node:path';
16
+ import { VERSION_FILE } from './store.js';
17
+
18
+ const SEMVER_RE = /^\d+\.\d+\.\d+$/;
19
+
20
+ /**
21
+ * Read the stamp from a skill root (the directory containing SKILL.md).
22
+ * @returns {string|null} the trimmed `MAJOR.MINOR.PATCH` string, or null when
23
+ * the stamp is absent, unreadable, or not plain semver ("unknown").
24
+ */
25
+ export function readStamp(skillRoot) {
26
+ if (!skillRoot) return null;
27
+ let raw;
28
+ try {
29
+ raw = readFileSync(join(skillRoot, VERSION_FILE), 'utf8');
30
+ } catch {
31
+ return null; // absent (legacy pre-stamp install) or unreadable
32
+ }
33
+ const body = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
34
+ const v = body.trim();
35
+ return SEMVER_RE.test(v) ? v : null;
36
+ }
37
+
38
+ /**
39
+ * Write/refresh the stamp inside a skill root. Idempotent: overwriting an
40
+ * existing stamp with the same version yields byte-identical content.
41
+ * Creates parent directories as needed.
42
+ */
43
+ export function writeStamp(skillRoot, version) {
44
+ const p = join(skillRoot, VERSION_FILE);
45
+ mkdirSync(dirname(p), { recursive: true });
46
+ writeFileSync(p, version + '\n', 'utf8');
47
+ }
package/package.json CHANGED
@@ -1,20 +1,48 @@
1
1
  {
2
2
  "name": "archgen-skill",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Architecture generation & autonomous task execution for coding agents - conversational requirements in, dependency-ordered tasks out, executed wave-by-wave with verifier gates. Works with Claude Code, OpenCode, Cursor, Codex, Gemini CLI and any agentskills.io harness.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
- "bin": { "archgen-skill": "bin/archgen.mjs" },
8
- "files": ["bin/", "lib/", "vendor/", "README.md"],
9
- "engines": { "node": ">=18" },
10
- "keywords": ["ai", "agents", "claude", "codex", "cursor", "opencode", "gemini", "architecture", "skills", "orchestration", "task-graph", "planning"],
11
- "repository": { "type": "git", "url": "git+https://github.com/akashmaurya99/archgen.git" },
7
+ "bin": {
8
+ "archgen-skill": "bin/archgen.mjs"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "lib/",
13
+ "vendor/",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "keywords": [
20
+ "ai",
21
+ "agents",
22
+ "claude",
23
+ "codex",
24
+ "cursor",
25
+ "opencode",
26
+ "gemini",
27
+ "architecture",
28
+ "skills",
29
+ "orchestration",
30
+ "task-graph",
31
+ "planning"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/akashmaurya99/archgen.git"
36
+ },
12
37
  "homepage": "https://github.com/akashmaurya99/archgen#readme",
13
- "bugs": { "url": "https://github.com/akashmaurya99/archgen/issues" },
38
+ "bugs": {
39
+ "url": "https://github.com/akashmaurya99/archgen/issues"
40
+ },
14
41
  "//": "vendor/skills/archgen is synced from ../skills/archgen by scripts/sync-vendor.mjs (run automatically via prepublishOnly). The extension ships separately as a VS Code vsix.",
15
42
  "scripts": {
16
- "sync": "node scripts/sync-vendor.mjs",
17
- "prepublishOnly": "node scripts/sync-vendor.mjs",
43
+ "sync:config": "node scripts/sync-config.mjs",
44
+ "sync": "node scripts/sync-config.mjs && node scripts/sync-vendor.mjs",
45
+ "prepublishOnly": "node scripts/sync-config.mjs && node scripts/sync-vendor.mjs",
18
46
  "test": "node --test test/*.test.mjs"
19
47
  }
20
48
  }