@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
|
|
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
|
-
|
|
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
|
|
175
|
-
if (!prefix)
|
|
176
|
-
continue;
|
|
156
|
+
const hashes = artifactHashesForProvider(entry, provider);
|
|
177
157
|
for (const [sourceRel, expectedHash] of Object.entries(hashes)) {
|
|
178
|
-
|
|
179
|
-
const
|
|
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
|
-
|
|
190
|
-
|
|
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.
|
|
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.
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
68
68
|
"chalk": "^4.1.2",
|
|
69
69
|
"chokidar": "^4.0.3",
|
|
70
70
|
"commander": "^12.1.0",
|