@ghl-ai/aw 0.1.34-beta.16 → 0.1.34-beta.17

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/apply.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // apply.mjs — Apply sync plan (file operations). Zero dependencies.
2
2
 
3
- import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync } from 'node:fs';
3
+ import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, unlinkSync, rmSync, readdirSync } from 'node:fs';
4
4
  import { dirname } from 'node:path';
5
5
 
6
6
  function writeConflictMarkers(filePath, localContent, registryContent) {
@@ -51,6 +51,24 @@ export function applyActions(actions, { teamNS } = {}) {
51
51
  }
52
52
 
53
53
  case 'ORPHAN':
54
+ // File was removed from registry — delete local copy
55
+ if (existsSync(act.targetPath)) {
56
+ try {
57
+ unlinkSync(act.targetPath);
58
+ // Clean up empty parent dirs, stop at .aw_registry/ boundary
59
+ let dir = dirname(act.targetPath);
60
+ while (!dir.endsWith('.aw_registry') && !dir.endsWith('.aw_registry/')) {
61
+ if (readdirSync(dir).length === 0) {
62
+ rmSync(dir);
63
+ dir = dirname(dir);
64
+ } else {
65
+ break;
66
+ }
67
+ }
68
+ } catch { /* best effort */ }
69
+ }
70
+ break;
71
+
54
72
  case 'UNCHANGED':
55
73
  break;
56
74
  }
package/commands/pull.mjs CHANGED
@@ -320,7 +320,7 @@ export async function pullAsync(args) {
320
320
  config.addPattern(workspaceDir, pattern);
321
321
  }
322
322
 
323
- const { actions: rawActions } = computePlan(registryDirs, workspaceDir, [pattern], { skipOrphans: true });
323
+ const { actions: rawActions } = computePlan(registryDirs, workspaceDir, [pattern], { skipOrphans: false });
324
324
  const actions = filterActions(rawActions, pattern);
325
325
  const conflictCount = applyActions(actions, { teamNS: teamNSOverride || renameNamespace || undefined });
326
326
  updateManifest(workspaceDir, actions, cfg.namespace, { fromTemplate: !!renameNamespace });
@@ -418,6 +418,7 @@ function printSummary(actions, verbose, conflictCount) {
418
418
  if (counts.ADD > 0) parts.push(chalk.green(`${counts.ADD} new`));
419
419
  if (counts.UPDATE > 0) parts.push(chalk.cyan(`${counts.UPDATE} updated`));
420
420
  if (counts.CONFLICT > 0) parts.push(chalk.red(`${counts.CONFLICT} conflict`));
421
+ if (counts.ORPHAN > 0) parts.push(chalk.yellow(`${counts.ORPHAN} removed`));
421
422
  const detail = parts.length > 0 ? ` (${parts.join(', ')})` : '';
422
423
 
423
424
  fmt.logSuccess(`${typeActions.length} ${type} pulled${detail}`);
package/fmt.mjs CHANGED
@@ -81,7 +81,7 @@ export function actionLabel(action) {
81
81
  case 'UPDATE': return chalk.bgCyan.black(' UPD ');
82
82
  case 'SKIP': return chalk.bgYellow.black(' SKP ');
83
83
  case 'CONFLICT': return chalk.bgRed.white(' CON ');
84
- case 'ORPHAN': return chalk.bgYellow.black(' ORP ');
84
+ case 'ORPHAN': return chalk.bgRed.white(' DEL ');
85
85
  case 'UNCHANGED': return chalk.dim(' --- ');
86
86
  default: return chalk.dim(` ${action} `);
87
87
  }
@@ -94,6 +94,6 @@ export function countSummary(counts) {
94
94
  if (counts.UNCHANGED > 0) parts.push(chalk.dim(`${counts.UNCHANGED} unchanged`));
95
95
  if (counts.SKIP > 0) parts.push(chalk.yellow(`${counts.SKIP} skipped`));
96
96
  if (counts.CONFLICT > 0) parts.push(chalk.red(`${counts.CONFLICT} conflict`));
97
- if (counts.ORPHAN > 0) parts.push(chalk.yellow(`${counts.ORPHAN} orphan`));
97
+ if (counts.ORPHAN > 0) parts.push(chalk.red(`${counts.ORPHAN} removed`));
98
98
  return parts.join(chalk.dim(' · '));
99
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghl-ai/aw",
3
- "version": "0.1.34-beta.16",
3
+ "version": "0.1.34-beta.17",
4
4
  "description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
5
5
  "type": "module",
6
6
  "bin": {
package/plan.mjs CHANGED
@@ -100,8 +100,14 @@ export function computePlan(registryDirs, workspaceDir, includePatterns = [], {
100
100
 
101
101
  // Orphans: in manifest but not in plan
102
102
  if (manifest.files && !skipOrphans) {
103
+ // Build a set of all targetFilenames in the plan for exact matching
104
+ const planTargets = new Set(actions.map(a => a.targetFilename));
105
+
103
106
  for (const [manifestKey, manifestEntry] of Object.entries(manifest.files)) {
104
- // manifestKey is now namespace/type/slug.md or namespace/type/slug/relPath
107
+ // Already handled by the plan — not an orphan
108
+ if (planTargets.has(manifestKey)) continue;
109
+
110
+ // manifestKey is namespace/type/slug.md or namespace/type/slug/relPath
105
111
  const parts = manifestKey.split('/');
106
112
  // Find the type segment
107
113
  let typeIdx = -1;
@@ -115,22 +121,20 @@ export function computePlan(registryDirs, workspaceDir, includePatterns = [], {
115
121
  const namespace = parts.slice(0, typeIdx).join('/');
116
122
  const type = parts[typeIdx];
117
123
  const slug = parts[typeIdx + 1]?.replace(/\.md$/, '');
118
- const key = `${namespace}/${type}/${slug}`;
119
- if (!plan.has(key)) {
120
- actions.push({
121
- slug,
122
- source: manifestEntry.source || 'unknown',
123
- type,
124
- action: 'ORPHAN',
125
- sourcePath: '',
126
- targetPath: join(workspaceDir, manifestKey),
127
- targetFilename: manifestKey,
128
- isDirectory: false,
129
- registryHash: '',
130
- namespacePath: parts.slice(0, typeIdx).join('/'),
131
- registryPath: '',
132
- });
133
- }
124
+
125
+ actions.push({
126
+ slug,
127
+ source: manifestEntry.source || 'unknown',
128
+ type,
129
+ action: 'ORPHAN',
130
+ sourcePath: '',
131
+ targetPath: join(workspaceDir, manifestKey),
132
+ targetFilename: manifestKey,
133
+ isDirectory: false,
134
+ registryHash: '',
135
+ namespacePath: namespace,
136
+ registryPath: '',
137
+ });
134
138
  }
135
139
  }
136
140