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.
- package/README.md +10 -0
- package/bin/archgen.mjs +23 -0
- package/lib/block.js +94 -9
- package/lib/config.js +96 -0
- package/lib/doctor.js +123 -26
- package/lib/init.js +3 -2
- package/lib/install.js +105 -14
- package/lib/migrate.js +88 -0
- package/lib/migrations/001-stamp-provenance.mjs +77 -0
- package/lib/migrations/index.mjs +170 -0
- package/lib/restore.js +239 -0
- package/lib/store.js +27 -12
- package/lib/uninstall-project.js +6 -3
- package/lib/version-stamp.js +47 -0
- package/package.json +37 -9
- package/vendor/skills/archgen/SKILL.md +242 -74
- package/vendor/skills/archgen/archgen.config.json +16 -0
- package/vendor/skills/archgen/assets/skill-registry.json +4 -3
- package/vendor/skills/archgen/references/artifact-templates.md +251 -7
- package/vendor/skills/archgen/references/interview.md +180 -16
- package/vendor/skills/archgen/references/mcp-and-skills.md +88 -17
- package/vendor/skills/archgen/references/orchestration.md +102 -39
- package/vendor/skills/archgen/references/platforms.md +1 -14
- package/vendor/skills/archgen/scripts/doc-index.mjs +457 -0
- package/vendor/skills/archgen/scripts/impact.mjs +14 -1
- package/vendor/skills/archgen/scripts/lib/graph.mjs +82 -3
- package/vendor/skills/archgen/scripts/lib/scope.mjs +65 -0
- package/vendor/skills/archgen/scripts/lib/yaml.mjs +19 -3
- package/vendor/skills/archgen/scripts/plan-graph.mjs +329 -0
- package/vendor/skills/archgen/scripts/set-status.mjs +26 -6
- package/vendor/skills/archgen/scripts/update-agents.mjs +4 -2
- package/vendor/skills/archgen/scripts/validate.mjs +18 -1
- 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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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.
|
|
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
|
|
103
|
-
*
|
|
104
|
-
*
|
|
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
|
|
107
|
-
const
|
|
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 */ }
|
package/lib/uninstall-project.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
"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": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
11
|
-
|
|
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": {
|
|
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-
|
|
17
|
-
"
|
|
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
|
}
|