cadet-agent 0.15.1 → 0.15.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/package.json CHANGED
@@ -1,35 +1,35 @@
1
- {
2
- "name": "cadet-agent",
3
- "version": "0.15.1",
4
- "description": "Cross-IDE agent framework for Unity/C# game-development — one-command install",
5
- "type": "module",
6
- "bin": {
7
- "cadet-agent": "bin/cli.mjs"
8
- },
9
- "scripts": {
10
- "test": "node --test test/*.test.mjs"
11
- },
12
- "files": [
13
- "bin/",
14
- "src/"
15
- ],
16
- "keywords": [
17
- "cadet",
18
- "cadet-agent",
19
- "unity",
20
- "game-development",
21
- "ai-agent",
22
- "copilot",
23
- "cursor",
24
- "claude-code"
25
- ],
26
- "license": "CC-BY-4.0",
27
- "repository": {
28
- "type": "git",
29
- "url": "git+https://github.com/naishtech/cadet-agent.git"
30
- },
31
- "homepage": "https://github.com/naishtech/cadet-agent#readme",
32
- "engines": {
33
- "node": ">=18.0.0"
34
- }
35
- }
1
+ {
2
+ "name": "cadet-agent",
3
+ "version": "0.15.4",
4
+ "description": "Cross-IDE agent framework for Unity/C# game-development — one-command install",
5
+ "type": "module",
6
+ "bin": {
7
+ "cadet-agent": "bin/cli.mjs"
8
+ },
9
+ "scripts": {
10
+ "test": "node --test test/*.test.mjs"
11
+ },
12
+ "files": [
13
+ "bin/",
14
+ "src/"
15
+ ],
16
+ "keywords": [
17
+ "cadet",
18
+ "cadet-agent",
19
+ "unity",
20
+ "game-development",
21
+ "ai-agent",
22
+ "copilot",
23
+ "cursor",
24
+ "claude-code"
25
+ ],
26
+ "license": "CC-BY-4.0",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/naishtech/cadet-agent.git"
30
+ },
31
+ "homepage": "https://github.com/naishtech/cadet-agent#readme",
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ }
35
+ }
package/src/install.mjs CHANGED
@@ -2,6 +2,7 @@ import { createWriteStream, mkdirSync, readFileSync, unlinkSync, existsSync, rea
2
2
  import { join, dirname, relative } from 'node:path';
3
3
  import { inflateRawSync } from 'node:zlib';
4
4
  import { Readable } from 'node:stream';
5
+ import { runUpgrades } from './upgrades.mjs';
5
6
 
6
7
  // ── Constants ────────────────────────────────────────────────────────────────
7
8
 
@@ -274,13 +275,13 @@ function walkDir(dir, fn) {
274
275
 
275
276
  function deleteObsoleteManagedFiles(targetDir, managedPaths, zipFilenames) {
276
277
  const normalizedZip = new Set(
277
- zipFilenames.map(f => f.replace(/^\.?\/?/, '').replace(/\\/g, '/'))
278
+ zipFilenames.map(f => f.replace(/^\.\//, '').replace(/\\/g, '/'))
278
279
  );
279
280
  const deleted = [];
280
281
 
281
282
  for (const managed of managedPaths) {
282
- const mn = managed.replace(/^\.?\/?/, '').replace(/\\/g, '/');
283
- const managedPath = join(targetDir, managed.replace(/^\.?\/?/, ''));
283
+ const mn = managed.replace(/^\.\//, '').replace(/\\/g, '/');
284
+ const managedPath = join(targetDir, managed.replace(/^\.\//, ''));
284
285
  const stat = (() => { try { return statSync(managedPath); } catch { return null; } })();
285
286
  if (!stat) continue;
286
287
 
@@ -340,7 +341,61 @@ async function extractZipWithManifest(buf, targetDir, { preserved, managed }) {
340
341
  // Delete obsolete managed files no longer in the zip (renamed/removed managed paths)
341
342
  const deleted = deleteObsoleteManagedFiles(targetDir, managed, zipFilenames);
342
343
 
343
- return { updated, preserved: preserved_list, added, deleted };
344
+ return { updated, preserved: preserved_list, added, deleted, zipFilenames };
345
+ }
346
+
347
+ // ── Removed-managed-path cleanup ─────────────────────────────────────────────
348
+
349
+ export function findManagedPathsInZip(buf) {
350
+ try {
351
+ const eocdOff = findEocd(buf);
352
+ const cdSize = read32(buf, eocdOff + 12);
353
+ const cdOff = read32(buf, eocdOff + 16);
354
+
355
+ for (const entry of centralDirectoryEntries(buf, cdOff, cdSize)) {
356
+ const fn = entry.filename.replace(/^\.\//, '').replace(/\\/g, '/');
357
+ if (fn === '.cadet/agent/core/FrameworkManifest.json') {
358
+ // Extract just this one entry to read managedPaths
359
+ const lfhFilenameLen = read16(buf, entry.localHeaderOff + 26);
360
+ const lfhExtraLen = read16(buf, entry.localHeaderOff + 28);
361
+ const dataStart = entry.localHeaderOff + 30 + lfhFilenameLen + lfhExtraLen;
362
+ const compressed = buf.subarray(dataStart, dataStart + entry.compressedSize);
363
+ let data;
364
+ if (entry.method === 0) data = compressed;
365
+ else if (entry.method === 8) data = inflateRawSync(compressed);
366
+ else break;
367
+ const manifest = JSON.parse(data.toString('utf-8'));
368
+ return manifest.managedPaths || [];
369
+ }
370
+ }
371
+ } catch {
372
+ // Not a valid zip or manifest not found
373
+ }
374
+ return [];
375
+ }
376
+
377
+ export function deleteRemovedManagedPaths(targetDir, oldManaged, newManaged) {
378
+ const newSet = new Set(newManaged.map(p => p.replace(/^\.\//, '').replace(/\\/g, '/')));
379
+ const deleted = [];
380
+
381
+ for (const old of oldManaged) {
382
+ const oldNorm = old.replace(/^\.\//, '').replace(/\\/g, '/');
383
+ if (newSet.has(oldNorm)) continue;
384
+
385
+ // This path was in the old manifest but is absent from the new one — remove it
386
+ const fullPath = join(targetDir, old.replace(/^\.\//, ''));
387
+ const st = (() => { try { return statSync(fullPath); } catch { return null; } })();
388
+ if (!st) continue;
389
+
390
+ if (st.isFile()) {
391
+ try { unlinkSync(fullPath); deleted.push(fullPath); } catch {}
392
+ } else if (st.isDirectory()) {
393
+ walkDir(fullPath, (filePath) => {
394
+ try { unlinkSync(filePath); deleted.push(filePath); } catch {}
395
+ });
396
+ }
397
+ }
398
+ return deleted;
344
399
  }
345
400
 
346
401
  // ── Public sync entry ───────────────────────────────────────────────────────
@@ -384,6 +439,23 @@ export async function sync(targetDir, opts = {}) {
384
439
  managed: existingManifest.managedPaths || [],
385
440
  });
386
441
 
442
+ // 4b. Find new managed paths from the zip and delete any old paths that were removed
443
+ const newManagedPaths = findManagedPathsInZip(zipBuf);
444
+ const removedDeleted = deleteRemovedManagedPaths(
445
+ targetDir,
446
+ existingManifest.managedPaths || [],
447
+ newManagedPaths
448
+ );
449
+ if (removedDeleted.length > 0) {
450
+ result.deleted.push(...removedDeleted);
451
+ }
452
+
453
+ // 4c. Run version-based upgrades (migrations for renamed/removed files)
454
+ const upgradeDeleted = runUpgrades(targetDir, oldVersionNorm, newVersion);
455
+ if (upgradeDeleted.length > 0) {
456
+ result.deleted.push(...upgradeDeleted);
457
+ }
458
+
387
459
  // 5. Report
388
460
  console.log('');
389
461
  console.log(`✅ Cadet-Agent synced: v${oldVersionNorm} → v${newVersion}`);
@@ -0,0 +1,77 @@
1
+ import { unlinkSync, statSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ // ── Path helpers ────────────────────────────────────────────────────────────
5
+
6
+ function walkDir(dir, fn) {
7
+ let entries;
8
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
9
+ for (const entry of entries) {
10
+ const fullPath = join(dir, entry.name);
11
+ if (entry.isDirectory()) {
12
+ walkDir(fullPath, fn);
13
+ } else {
14
+ fn(fullPath);
15
+ }
16
+ }
17
+ }
18
+
19
+ function deletePath(targetDir, relativePath) {
20
+ const norm = relativePath.replace(/^\.\//, '').replace(/\\/g, '/');
21
+ const fullPath = join(targetDir, norm);
22
+ let st;
23
+ try { st = statSync(fullPath); } catch { return []; }
24
+
25
+ const deleted = [];
26
+ if (st.isFile()) {
27
+ try { unlinkSync(fullPath); deleted.push(fullPath); } catch {}
28
+ } else if (st.isDirectory()) {
29
+ walkDir(fullPath, (filePath) => {
30
+ try { unlinkSync(filePath); deleted.push(filePath); } catch {}
31
+ });
32
+ }
33
+ return deleted;
34
+ }
35
+
36
+ // ── Upgrade registry ────────────────────────────────────────────────────────
37
+
38
+ // Key = FROM version. Each upgrade runs when upgrading FROM a version
39
+ // older than or equal to the key, TO a version newer than the key.
40
+ // Upgrade functions receive (targetDir) and return string[] of deleted paths.
41
+
42
+ const upgrades = {
43
+ '0.15.3': (targetDir) => [
44
+ ...deletePath(targetDir, '.cadet/orchestrator'),
45
+ ...deletePath(targetDir, '.github/prompts'),
46
+ ...deletePath(targetDir, 'AGENTS.md'),
47
+ ],
48
+ };
49
+
50
+ // ── Runner ──────────────────────────────────────────────────────────────────
51
+
52
+ function semverGt(a, b) {
53
+ const pa = a.split('.').map(Number);
54
+ const pb = b.split('.').map(Number);
55
+ for (let i = 0; i < 3; i++) {
56
+ if ((pa[i] || 0) > (pb[i] || 0)) return true;
57
+ if ((pa[i] || 0) < (pb[i] || 0)) return false;
58
+ }
59
+ return false;
60
+ }
61
+
62
+ export function runUpgrades(targetDir, fromVersion, toVersion) {
63
+ const deleted = [];
64
+
65
+ for (const [ver, fn] of Object.entries(upgrades)) {
66
+ if (semverGt(ver, fromVersion) && !semverGt(ver, toVersion)) {
67
+ try {
68
+ const result = fn(targetDir);
69
+ if (Array.isArray(result)) deleted.push(...result);
70
+ } catch {
71
+ // Upgrade failure shouldn't block the sync
72
+ }
73
+ }
74
+ }
75
+
76
+ return deleted;
77
+ }