yam-flow 2.3.0 → 2.3.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/src/bin/yam.ts CHANGED
@@ -33,6 +33,12 @@ import {
33
33
  verifyUeyeAssetManifest,
34
34
  verifyUeyeRevisionHistory
35
35
  } from '../lib/ueye-artifacts.js';
36
+ import {
37
+ INSTALL_LOCK_NAME,
38
+ INSTALL_RECEIPT_NAME,
39
+ inspectSkillInstallation,
40
+ installSkillSetTransactional
41
+ } from '../lib/skill-installation.js';
36
42
 
37
43
  type AnyRecord = Record<string, any>;
38
44
 
@@ -199,75 +205,73 @@ async function rmrf(target) {
199
205
  await fsp.rm(target, { recursive: true, force: true });
200
206
  }
201
207
 
202
- async function copyDir(source, target) {
203
- await fsp.mkdir(target, { recursive: true });
204
- const entries = await fsp.readdir(source, { withFileTypes: true });
205
- for (const entry of entries) {
206
- const from = path.join(source, entry.name);
207
- const to = path.join(target, entry.name);
208
- if (entry.isDirectory()) {
209
- await copyDir(from, to);
210
- } else if (entry.isFile()) {
211
- await fsp.copyFile(from, to);
212
- }
213
- }
214
- }
215
-
216
- async function installSkill(skill) {
217
- const source = path.join(ROOT, 'skills', skill);
218
- const target = path.join(DEST, skill);
219
- const references = path.join(ROOT, 'references');
220
-
221
- if (!await exists(path.join(source, 'SKILL.md'))) {
222
- throw new Error(`missing skill source: ${source}`);
223
- }
224
-
225
- await rmrf(target);
226
- await fsp.mkdir(target, { recursive: true });
227
- await fsp.copyFile(path.join(source, 'SKILL.md'), path.join(target, 'SKILL.md'));
228
- await copyDir(references, path.join(target, 'references'));
229
- }
230
-
231
208
  async function install() {
232
- await fsp.mkdir(DEST, { recursive: true });
233
- for (const skill of SKILLS) {
234
- await installSkill(skill);
235
- }
236
- for (const legacySkill of LEGACY_SKILLS) {
237
- await rmrf(path.join(DEST, legacySkill));
238
- }
239
- for (const retiredSkill of RETIRED_SKILLS) {
240
- await rmrf(path.join(DEST, retiredSkill));
241
- }
242
- if (CODEX_MIRROR !== DEST && fs.existsSync(CODEX_MIRROR)) {
243
- for (const skill of [...SKILLS, ...LEGACY_SKILLS, ...RETIRED_SKILLS]) {
244
- await rmrf(path.join(CODEX_MIRROR, skill));
245
- }
246
- }
209
+ const result = await installSkillSetTransactional({
210
+ sourceRoot: ROOT,
211
+ destination: DEST,
212
+ codexMirror: CODEX_MIRROR,
213
+ packageName: String(PACKAGE_JSON.name || 'yam-flow'),
214
+ version: VERSION,
215
+ skills: SKILLS,
216
+ legacySkills: LEGACY_SKILLS,
217
+ retiredSkills: RETIRED_SKILLS
218
+ });
247
219
  console.log(`yam installed to ${DEST}`);
220
+ console.log(`install receipt: ${result.receiptPath}`);
221
+ console.log(`integrity: ${result.installedFiles} files, sha256:${result.sourceDigest}`);
222
+ for (const warning of result.cleanupWarnings) console.warn(`warning: ${warning}`);
248
223
  console.log('Restart Codex to reload skills.');
249
224
  }
250
225
 
251
226
  async function uninstall() {
227
+ const lockPath = path.join(DEST, INSTALL_LOCK_NAME);
228
+ if (await exists(lockPath)) {
229
+ throw new Error(`cannot uninstall while an install lock exists: ${lockPath}`);
230
+ }
252
231
  for (const skill of [...SKILLS, ...LEGACY_SKILLS, ...RETIRED_SKILLS]) {
253
232
  await rmrf(path.join(DEST, skill));
254
233
  if (CODEX_MIRROR !== DEST) {
255
234
  await rmrf(path.join(CODEX_MIRROR, skill));
256
235
  }
257
236
  }
237
+ await rmrf(path.join(DEST, INSTALL_RECEIPT_NAME));
258
238
  console.log(`yam removed from ${DEST}`);
259
239
  if (CODEX_MIRROR !== DEST) console.log(`yam mirror entries removed from ${CODEX_MIRROR}`);
260
240
  console.log('Restart Codex to unload skills.');
261
241
  }
262
242
 
263
243
  async function status({ quiet = false } = {}) {
264
- let missing = 0;
265
- for (const skill of SKILLS) {
266
- const ok = await exists(path.join(DEST, skill, 'SKILL.md')) && await exists(path.join(DEST, skill, 'references'));
267
- if (!quiet) console.log(`${ok ? 'ok ' : 'missing'} ${skill}`);
268
- if (!ok) missing += 1;
244
+ const report = await inspectSkillInstallation({
245
+ sourceRoot: ROOT,
246
+ destination: DEST,
247
+ packageName: String(PACKAGE_JSON.name || 'yam-flow'),
248
+ version: VERSION,
249
+ skills: SKILLS
250
+ });
251
+ if (!quiet) {
252
+ for (const skill of report.skills) {
253
+ const label = skill.status === 'ok' ? 'ok ' : skill.status === 'missing' ? 'missing' : 'drift ';
254
+ console.log(`${label} ${skill.skill}`);
255
+ }
256
+ if (report.receiptStatus === 'ok' && report.receipt) {
257
+ console.log(`ok install receipt ${report.receipt.package.name}@${report.receipt.package.version}`);
258
+ console.log(` sha256:${report.receipt.integrity.source_digest}`);
259
+ } else {
260
+ console.log(`${report.receiptStatus === 'missing' ? 'missing' : 'drift '} install receipt`);
261
+ }
262
+ if (report.issues.length) {
263
+ console.log('');
264
+ console.log('Install integrity issues:');
265
+ for (const issue of report.issues.slice(0, 12)) console.log(`- ${issue}`);
266
+ if (report.issues.length > 12) console.log(`- ... ${report.issues.length - 12} more issue(s)`);
267
+ if (report.recoveryArtifacts.length) {
268
+ console.log('Confirm no install is running, then inspect and preserve the transaction backup before retrying.');
269
+ } else {
270
+ console.log('Run `yam install` to restore the package-bundled skill set.');
271
+ }
272
+ }
269
273
  }
270
- return missing;
274
+ return report.ok ? 0 : Math.max(1, report.issues.length);
271
275
  }
272
276
 
273
277
  async function list() {
@@ -316,6 +320,7 @@ async function verify({ quiet = false } = {}) {
316
320
 
317
321
  if (!manifest) return failOrReport('yam verify', issues, quiet);
318
322
  if (manifest.name !== 'yam') issues.push('manifest name must be yam');
323
+ if (manifest.version !== VERSION) issues.push(`manifest version must match package version: ${manifest.version || 'missing'} != ${VERSION}`);
319
324
  if (manifest.defaultHooks !== false) issues.push('manifest defaultHooks must be false');
320
325
  if (!Array.isArray(manifest.routes) || manifest.routes.length !== SKILLS.length) {
321
326
  issues.push(`manifest routes must contain ${SKILLS.length} routes`);