@aiwg/cli 2026.8.2 → 2026.8.3

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.
@@ -1062,6 +1062,7 @@ export const installPluginHandler = {
1062
1062
  const runner = createScriptRunner(frameworkRoot);
1063
1063
  return runner.run("tools/plugin/plugin-installer-cli.mjs", ctx.args, {
1064
1064
  cwd: ctx.cwd,
1065
+ env: { AIWG_ROOT: frameworkRoot },
1065
1066
  });
1066
1067
  },
1067
1068
  };
@@ -31,7 +31,7 @@ import { PROJECT_LOCAL_TYPE_TO_DIR } from '../../extensions/project-local-paths.
31
31
  import { buildUpstreamRegistry } from '../../extensions/upstream-registry.js';
32
32
  import { resolveShadows, formatShadowReport, } from '../../extensions/shadow-resolver.js';
33
33
  import { appendProjectLocalActivity, emitDiscoverEventsDeduped, } from '../../extensions/project-local-activity.js';
34
- import { hashBundleArtifacts } from '../../extensions/project-local-remove.js';
34
+ import { hashBundleArtifacts, hashDeployedBundleArtifacts, } from '../../extensions/project-local-remove.js';
35
35
  import { installAiwgHooks } from '../../extensions/claude-hooks-installer.js';
36
36
  import { detectScope, mirrorToUserScope, rejectOpenClawProjectScope, USER_SCOPE_PATHS, } from '../scope-resolver.js';
37
37
  import { maybeWarnProjectIsolation } from '../project-isolation/index.js';
@@ -1185,6 +1185,7 @@ async function deployProjectLocalBundles(opts) {
1185
1185
  // #1037 — record per-artifact source hashes so `aiwg remove` can
1186
1186
  // detect pristine vs mutated vs replaced deployed files.
1187
1187
  const artifactHashes = await hashBundleArtifacts(bundle.artifactPath);
1188
+ const deployedArtifactHashes = await hashDeployedBundleArtifacts(projectDir, provider, artifactHashes);
1188
1189
  const updated = updateInstalled(config, bundle.id, provider, result.counts, {
1189
1190
  version: bundle.manifest.version,
1190
1191
  source: 'project-local',
@@ -1193,6 +1194,7 @@ async function deployProjectLocalBundles(opts) {
1193
1194
  localType: bundle.type,
1194
1195
  manifestVersion: bundle.manifest.manifestVersion,
1195
1196
  artifactHashes,
1197
+ deployedArtifactHashes,
1196
1198
  });
1197
1199
  await writeAiwgConfig(projectDir, updated);
1198
1200
  }
@@ -784,6 +784,10 @@ export function updateInstalled(config, name, provider, counts, opts) {
784
784
  existing.manifestVersion = opts.manifestVersion;
785
785
  if (opts.artifactHashes)
786
786
  existing.artifactHashes = opts.artifactHashes;
787
+ if (opts.deployedArtifactHashes) {
788
+ existing.deployedArtifactHashes ??= {};
789
+ existing.deployedArtifactHashes[provider] = opts.deployedArtifactHashes;
790
+ }
787
791
  }
788
792
  else {
789
793
  // Clear stale project-local fields if a previously project-local entry is
@@ -792,6 +796,7 @@ export function updateInstalled(config, name, provider, counts, opts) {
792
796
  delete existing.localType;
793
797
  delete existing.manifestVersion;
794
798
  delete existing.artifactHashes;
799
+ delete existing.deployedArtifactHashes;
795
800
  }
796
801
  config.installed[name] = existing;
797
802
  return config;
@@ -12,8 +12,7 @@
12
12
  * @design @.aiwg/architecture/design-doctor-log-promote.md
13
13
  * @implements #1037
14
14
  */
15
- import { join, resolve } from 'path';
16
- import { homedir } from 'os';
15
+ import { join } from 'path';
17
16
  import { discoverProjectLocalBundles } from './project-local-discovery.js';
18
17
  import { buildUpstreamRegistry } from './upstream-registry.js';
19
18
  import { resolveShadows } from './shadow-resolver.js';
@@ -22,6 +21,7 @@ import { sha256OfFileRawAndNormalized } from './managed-marker.js';
22
21
  import { projectAiwgPath } from '../config/project-artifacts.js';
23
22
  import { projectRelativePathIfInside } from './project-local-paths.js';
24
23
  import { auditProjectQuickref } from './project-quickref.js';
24
+ import { artifactHashesForProvider, candidateDeployedPaths } from './project-local-remove.js';
25
25
  /**
26
26
  * Hash a deployed file in raw and managed-marker-normalized forms. Returns
27
27
  * null on read errors (e.g., file missing — caller treats as
@@ -48,23 +48,6 @@ const TYPE_DIR = {
48
48
  plugin: 'plugins',
49
49
  provider: 'providers',
50
50
  };
51
- // Per PUW-026 (#1127): home-deploying providers get absolute prefixes so
52
- // `resolve(projectDir, prefix)` correctly produces the home-rooted path
53
- // (resolve treats absolute paths as authoritative). Previously these were
54
- // `null`, which silently skipped lifecycle operations against home-deployed
55
- // project-local bundles.
56
- const PROVIDER_PREFIX = {
57
- claude: '.claude',
58
- cursor: '.cursor',
59
- factory: '.factory',
60
- opencode: '.opencode',
61
- windsurf: '.windsurf',
62
- warp: '.warp',
63
- codex: '.codex',
64
- copilot: '.github',
65
- openclaw: resolve(homedir(), '.openclaw'),
66
- hermes: resolve(homedir(), '.hermes'),
67
- };
68
51
  export async function buildProjectLocalDoctorSection(opts) {
69
52
  const { projectDir, frameworkRoot, config, quiet = false } = opts;
70
53
  const discovery = await discoverProjectLocalBundles(projectDir);
@@ -165,18 +148,19 @@ export async function buildProjectLocalDoctorSection(opts) {
165
148
  const entry = config.installed[bundle.id];
166
149
  if (!entry || entry.source !== 'project-local')
167
150
  continue;
168
- const hashes = entry.artifactHashes;
169
- if (!hashes) {
151
+ if (!entry.artifactHashes && !entry.deployedArtifactHashes) {
170
152
  unhashedSeen = true;
171
153
  continue;
172
154
  }
173
155
  for (const provider of Object.keys(entry.deployedTo)) {
174
- const prefix = PROVIDER_PREFIX[provider];
175
- if (!prefix)
176
- continue;
156
+ const hashes = artifactHashesForProvider(entry, provider);
177
157
  for (const [sourceRel, expectedHash] of Object.entries(hashes)) {
178
- const deployedAbs = resolve(projectDir, `${prefix}/${sourceRel}`);
179
- const actualHash = await hashDeployed(deployedAbs);
158
+ let actualHash = null;
159
+ for (const deployedAbs of candidateDeployedPaths(projectDir, provider, sourceRel)) {
160
+ actualHash = await hashDeployed(deployedAbs);
161
+ if (actualHash)
162
+ break;
163
+ }
180
164
  if (actualHash === null) {
181
165
  // Missing — not drift, deploy is just absent
182
166
  continue;
@@ -93,6 +93,34 @@ export async function hashBundleArtifacts(bundleAbsPath) {
93
93
  }
94
94
  return out;
95
95
  }
96
+ /**
97
+ * Hash the provider-transformed files produced by a successful deployment.
98
+ * The source hash map supplies the canonical inventory/key shape; values are
99
+ * read from the first canonical provider path that exists.
100
+ */
101
+ export async function hashDeployedBundleArtifacts(projectDir, provider, sourceHashes) {
102
+ const deployed = {};
103
+ for (const [sourceRel, sourceHash] of Object.entries(sourceHashes)) {
104
+ // Keep the complete inventory if a provider path unexpectedly cannot be
105
+ // resolved. Removal then fails safely instead of clearing the registry
106
+ // while leaving an untracked provider artifact behind.
107
+ deployed[sourceRel] = sourceHash;
108
+ for (const candidate of candidateDeployedPaths(projectDir, provider, sourceRel)) {
109
+ try {
110
+ deployed[sourceRel] = await sha256Hex(candidate);
111
+ break;
112
+ }
113
+ catch {
114
+ // Try translated/provider-alternate paths before leaving it absent.
115
+ }
116
+ }
117
+ }
118
+ return deployed;
119
+ }
120
+ /** Resolve current provider hashes while preserving old registry entries. */
121
+ export function artifactHashesForProvider(entry, provider) {
122
+ return entry.deployedArtifactHashes?.[provider] ?? entry.artifactHashes ?? {};
123
+ }
96
124
  /** Resolve a recorded source-relative artifact through the canonical provider
97
125
  * definition rather than assuming every artifact lives directly under the
98
126
  * provider prefix (#1869). Returned paths are absolute for both project- and
@@ -111,6 +139,11 @@ export function candidateDeployedPaths(projectDir, provider, sourceRel) {
111
139
  const tail = sourceRel.slice(separator + 1);
112
140
  const absoluteRoot = isAbsolute(root) ? root : resolve(projectDir, root);
113
141
  const candidates = [join(absoluteRoot, tail)];
142
+ // Codex discovers project skills from the shared native `.agents/skills`
143
+ // root while retaining `.codex` as its compatibility deployment surface.
144
+ if (provider === 'codex' && artifactType === 'skills') {
145
+ candidates.unshift(join(resolve(projectDir, '.agents', 'skills'), tail));
146
+ }
114
147
  // Provider adapters may translate source extensions.
115
148
  if (provider === 'cursor' && artifactType === 'rules' && tail.endsWith('.md')) {
116
149
  candidates.push(join(absoluteRoot, `${tail.slice(0, -3)}.mdc`));
@@ -186,12 +219,10 @@ function resolveOwnership(config, selfBundleId, provider, sourceRel) {
186
219
  continue;
187
220
  if (entry.source !== 'project-local')
188
221
  continue;
189
- if (!entry.artifactHashes)
190
- continue;
191
- if (sourceRel in entry.artifactHashes) {
222
+ const hashes = artifactHashesForProvider(entry, provider);
223
+ if (sourceRel in hashes) {
192
224
  // Same source-rel path claimed by another project-local bundle —
193
225
  // the deployed file (if present) is theirs, not ours.
194
- void provider;
195
226
  return name;
196
227
  }
197
228
  }
@@ -219,12 +250,12 @@ export async function removeProjectLocalBundle(config, projectDir, bundleId, opt
219
250
  const { force = false, provider: onlyProvider, dryRun = false, keepRegistry = false } = opts;
220
251
  const confirmMutation = opts.confirmMutation ?? (async () => false);
221
252
  const installedEntry = entry;
222
- const artifactHashes = installedEntry.artifactHashes ?? {};
223
253
  const providers = Object.keys(installedEntry.deployedTo).filter(p => !onlyProvider || p === onlyProvider);
224
254
  const outcomes = [];
225
255
  const revertedProviders = [];
226
256
  const partialProviders = [];
227
257
  for (const provider of providers) {
258
+ const artifactHashes = artifactHashesForProvider(installedEntry, provider);
228
259
  let providerHadSkip = false;
229
260
  for (const sourceRel of Object.keys(artifactHashes)) {
230
261
  const owner = resolveOwnership(config, bundleId, provider, sourceRel);
@@ -334,6 +365,12 @@ export async function removeProjectLocalBundle(config, projectDir, bundleId, opt
334
365
  // Mutate registry for fully-reverted providers
335
366
  if (!dryRun && !keepRegistry && !providerHadSkip) {
336
367
  delete installedEntry.deployedTo[provider];
368
+ if (installedEntry.deployedArtifactHashes) {
369
+ delete installedEntry.deployedArtifactHashes[provider];
370
+ if (Object.keys(installedEntry.deployedArtifactHashes).length === 0) {
371
+ delete installedEntry.deployedArtifactHashes;
372
+ }
373
+ }
337
374
  }
338
375
  }
339
376
  // Top-level remove activity entry
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.8.2",
3
+ "version": "2026.8.3",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@fortemi/core": "2026.7.15",
67
- "@modelcontextprotocol/sdk": "^1.29.0",
67
+ "@modelcontextprotocol/sdk": "^1.30.0",
68
68
  "chalk": "^4.1.2",
69
69
  "chokidar": "^4.0.3",
70
70
  "commander": "^12.1.0",