@aiwg/cli 2026.9.6 → 2026.9.9
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/dist/src/artifacts/index-builder.js +43 -1
- package/dist/src/artifacts/query-engine.js +7 -0
- package/dist/src/cli/handlers/help.js +7 -1
- package/dist/src/cli/handlers/installation.js +106 -2
- package/dist/src/cli/handlers/mc.js +100 -37
- package/dist/src/cli/handlers/ralph.js +14 -4
- package/dist/src/cli/handlers/refresh.js +359 -31
- package/dist/src/cli/handlers/repo-access.js +155 -4
- package/dist/src/cli/handlers/runtime-info.js +3 -0
- package/dist/src/cli/handlers/serve.js +21 -3
- package/dist/src/cli/handlers/setup.js +5 -5
- package/dist/src/cli/handlers/steward.js +30 -1
- package/dist/src/cli/handlers/use.js +123 -12
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +40 -14
- package/dist/src/cli/handlers/workspace-context.js +8 -0
- package/dist/src/cli/services/deployment-verification.js +156 -7
- package/dist/src/cli/watch-service.js +47 -4
- package/dist/src/config/aiwg-config.js +95 -3
- package/dist/src/config/cli.js +16 -1
- package/dist/src/config/gitignore.js +5 -0
- package/dist/src/config/project-artifacts-health.mjs +15 -2
- package/dist/src/cost/fleet-report.js +19 -5
- package/dist/src/extensions/claude-hooks-installer.js +22 -6
- package/dist/src/extensions/project-local-doctor.js +40 -2
- package/dist/src/extensions/project-quickref.js +4 -0
- package/dist/src/installation/manager.mjs +38 -3
- package/dist/src/lint/runner.js +138 -0
- package/dist/src/mcp/helpers.mjs +56 -22
- package/dist/src/mcp/registry.js +32 -22
- package/dist/src/mcp/registry.mjs +31 -26
- package/dist/src/mcp/toml-editor.mjs +117 -0
- package/dist/src/mcp/tools/orchestration.mjs +7 -7
- package/dist/src/mcp/tools/subsystems.mjs +7 -7
- package/dist/src/memory/context-pack.js +5 -1
- package/dist/src/plugin/skill-command-translator.js +70 -1
- package/dist/src/serve/a2a-terminal-observer.js +19 -1
- package/dist/src/serve/mission-hitl.js +91 -0
- package/dist/src/sessions/import-lease.js +5 -1
- package/dist/src/smiths/context-pipeline/workspace-context.js +132 -6
- package/dist/src/testing/fixtures/test-data-factory.js +3 -3
- package/dist/src/writing/pattern-library.js +29 -6
- package/package.json +2 -1
- package/tools/agents/deploy-agents.mjs +91 -5
- package/tools/agents/providers/base.mjs +162 -6
|
@@ -199,7 +199,7 @@ export function addManagedMarker(content, version, source, style = 'markdown') {
|
|
|
199
199
|
/**
|
|
200
200
|
* Compute SHA-256 hash of content (hex string).
|
|
201
201
|
*/
|
|
202
|
-
function contentHash(content) {
|
|
202
|
+
export function contentHash(content) {
|
|
203
203
|
return createHash('sha256').update(content).digest('hex');
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -240,9 +240,12 @@ export function updateSidecarManifest(dir, deployedEntries, opts) {
|
|
|
240
240
|
const existing = readSidecarManifest(dir) || { managed: {} };
|
|
241
241
|
|
|
242
242
|
for (const entry of deployedEntries) {
|
|
243
|
-
const { filename, hash, frameworkSlug } = entry;
|
|
243
|
+
const { filename, hash, frameworkSlug, kind } = entry;
|
|
244
244
|
const sidecarEntry = { hash: `sha256:${hash}`, source, version };
|
|
245
245
|
if (frameworkSlug) sidecarEntry.frameworkSlug = frameworkSlug;
|
|
246
|
+
// `kind` marks artifacts whose lifecycle is governed elsewhere — currently
|
|
247
|
+
// only `skill-command` wrappers, which follow their source skill (#2507).
|
|
248
|
+
if (kind) sidecarEntry.kind = kind;
|
|
246
249
|
existing.managed[filename] = sidecarEntry;
|
|
247
250
|
}
|
|
248
251
|
|
|
@@ -473,6 +476,41 @@ export function injectPlatformInContent(content, targetPlatform) {
|
|
|
473
476
|
* copies destined for such providers must drop the field entirely. An
|
|
474
477
|
* absent field is the documented "compatible with all platforms" default.
|
|
475
478
|
*/
|
|
479
|
+
/**
|
|
480
|
+
* Remove the `triggers:` field from a deployed rule's frontmatter.
|
|
481
|
+
*
|
|
482
|
+
* Rules declare trigger phrases so `aiwg discover` can reach them by the
|
|
483
|
+
* question an agent asks rather than by their policy name (#2544). That is
|
|
484
|
+
* index-time metadata: no provider matches a *rule* by trigger, and the agent
|
|
485
|
+
* reading the deployed rule gains nothing from the list. Shipping it spends
|
|
486
|
+
* startup context on noise, which is exactly the budget #2540 is defending —
|
|
487
|
+
* ~2KB across the 16 rules covered today, and ~16KB if every rule adopts.
|
|
488
|
+
*
|
|
489
|
+
* Skills are untouched: several providers do match skills by trigger.
|
|
490
|
+
*/
|
|
491
|
+
export function stripTriggersFromContent(content) {
|
|
492
|
+
const fmMatch = content.match(/^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))([\s\S]*)$/);
|
|
493
|
+
if (!fmMatch) return content;
|
|
494
|
+
|
|
495
|
+
const [, open, fm, close, body] = fmMatch;
|
|
496
|
+
// Block list form:
|
|
497
|
+
// triggers:
|
|
498
|
+
// - "am I allowed to do this"
|
|
499
|
+
let updated = fm.replace(
|
|
500
|
+
/^triggers:[ \t]*\r?\n(?:[ \t]+-[ \t]+\S[^\r\n]*(?:\r?\n|$))*/m,
|
|
501
|
+
'',
|
|
502
|
+
);
|
|
503
|
+
// Inline form: triggers: ["a", "b"]
|
|
504
|
+
if (updated === fm) updated = fm.replace(/^triggers:[^\r\n]*(?:\r?\n|$)/m, '');
|
|
505
|
+
|
|
506
|
+
if (updated === fm) return content;
|
|
507
|
+
// An otherwise-empty frontmatter block is dropped rather than left as `---\n---`.
|
|
508
|
+
if (updated.trim().length === 0) return body.replace(/^\r?\n/, '');
|
|
509
|
+
// Removing a block mid-frontmatter can leave a trailing blank line before the
|
|
510
|
+
// closing fence; the deployed file should not carry it.
|
|
511
|
+
return open + updated.replace(/\s+$/, '') + close + body;
|
|
512
|
+
}
|
|
513
|
+
|
|
476
514
|
export function stripPlatformsFromContent(content) {
|
|
477
515
|
const fmMatch = content.match(/^(---\r?\n)([\s\S]*?)(\r?\n---(?:\r?\n|$))([\s\S]*)$/);
|
|
478
516
|
if (!fmMatch) return content;
|
|
@@ -704,6 +742,13 @@ export function deployFiles(files, destDir, opts, transformFn) {
|
|
|
704
742
|
const srcContent = fs.readFileSync(f, 'utf8');
|
|
705
743
|
let transformedContent = transformFn ? transformFn(f, srcContent, opts) : srcContent;
|
|
706
744
|
|
|
745
|
+
// Rule triggers are index metadata, not something the reading agent needs.
|
|
746
|
+
// Keyed on the source path so every provider's rule deploy gets it without
|
|
747
|
+
// eight call sites opting in, and so skill triggers are never touched (#2544).
|
|
748
|
+
if (/(?:^|[\\/])rules[\\/][^\\/]+$/.test(f)) {
|
|
749
|
+
transformedContent = stripTriggersFromContent(transformedContent);
|
|
750
|
+
}
|
|
751
|
+
|
|
707
752
|
// Inject target platform into agent .md files that use platforms: [all]
|
|
708
753
|
if (injectPlatform && provider && /platforms:\s*\[all\]/.test(transformedContent)) {
|
|
709
754
|
const platformName = PROVIDER_TO_PLATFORM[provider] || provider;
|
|
@@ -1342,6 +1387,39 @@ export function resolveAiwgRoot(srcRoot) {
|
|
|
1342
1387
|
return null;
|
|
1343
1388
|
}
|
|
1344
1389
|
|
|
1390
|
+
/**
|
|
1391
|
+
* Names of every skill AIWG ships, kernel and standard alike (#2511).
|
|
1392
|
+
*
|
|
1393
|
+
* `computeAllKernelNames` filters to kernel skills, but skill-command wrappers
|
|
1394
|
+
* are generated from both tiers, so retiring an orphaned wrapper needs the full
|
|
1395
|
+
* set. Anchored to the AIWG root rather than `srcRoot` for the same reason
|
|
1396
|
+
* `computeAllArtifactBasenames` is: a bundle-scoped deploy must not produce an
|
|
1397
|
+
* empty desired set and retire everything.
|
|
1398
|
+
*
|
|
1399
|
+
* @param {string} srcRoot AIWG repo / install root (or a subdir of it)
|
|
1400
|
+
* @returns {Set<string>|null} skill directory names, or null when no AIWG tree
|
|
1401
|
+
* is found — callers MUST then skip pruning.
|
|
1402
|
+
*/
|
|
1403
|
+
export function computeAllSkillNames(srcRoot) {
|
|
1404
|
+
const aiwgRoot = resolveAiwgRoot(srcRoot);
|
|
1405
|
+
if (!aiwgRoot) return null;
|
|
1406
|
+
|
|
1407
|
+
const names = new Set();
|
|
1408
|
+
for (const group of ['frameworks', 'addons']) {
|
|
1409
|
+
const root = path.join(aiwgRoot, 'agentic', 'code', group);
|
|
1410
|
+
if (!fs.existsSync(root)) continue;
|
|
1411
|
+
for (const component of fs.readdirSync(root, { withFileTypes: true })) {
|
|
1412
|
+
if (!component.isDirectory()) continue;
|
|
1413
|
+
const skillsDir = path.join(root, component.name, 'skills');
|
|
1414
|
+
if (!fs.existsSync(skillsDir)) continue;
|
|
1415
|
+
for (const skill of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
1416
|
+
if (skill.isDirectory()) names.add(skill.name);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return names;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1345
1423
|
/**
|
|
1346
1424
|
* Holistic post-deploy prune of stale AIWG-managed flat artifacts
|
|
1347
1425
|
* (agents / commands / rules). The flat-file analogue of
|
|
@@ -1371,6 +1449,15 @@ export function resolveAiwgRoot(srcRoot) {
|
|
|
1371
1449
|
export function pruneStaleAiwgFiles(destDir, desiredStems, opts = {}) {
|
|
1372
1450
|
const { dryRun = false, verbose = false } = opts;
|
|
1373
1451
|
const artifactExtensions = opts.artifactExtensions || ['.md', '.mdc'];
|
|
1452
|
+
// When supplied, skill-command wrappers are retired against the set of skills
|
|
1453
|
+
// that still exist rather than exempted outright (#2511). `null`/absent keeps
|
|
1454
|
+
// the blanket exemption, so callers without a skill inventory cannot retire a
|
|
1455
|
+
// wrapper by accident.
|
|
1456
|
+
const skillCommandStems = opts.skillCommandStems instanceof Set
|
|
1457
|
+
? opts.skillCommandStems
|
|
1458
|
+
: Array.isArray(opts.skillCommandStems)
|
|
1459
|
+
? new Set(opts.skillCommandStems)
|
|
1460
|
+
: null;
|
|
1374
1461
|
const removed = [];
|
|
1375
1462
|
if (!destDir || !fs.existsSync(destDir)) return removed;
|
|
1376
1463
|
|
|
@@ -1397,6 +1484,15 @@ export function pruneStaleAiwgFiles(destDir, desiredStems, opts = {}) {
|
|
|
1397
1484
|
|
|
1398
1485
|
if (desired.has(artifactStem(name))) continue;
|
|
1399
1486
|
|
|
1487
|
+
// Skill-command wrappers are named after skills, not command sources, so
|
|
1488
|
+
// they are absent from the command desired set by construction — pruning
|
|
1489
|
+
// them against it would delete wrappers the same deploy just wrote (#2507).
|
|
1490
|
+
// They are retired against the skill inventory instead, when one is given
|
|
1491
|
+
// (#2511); without one they stay exempt.
|
|
1492
|
+
if (managed[name]?.kind === 'skill-command') {
|
|
1493
|
+
if (!skillCommandStems || skillCommandStems.has(artifactStem(name))) continue;
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1400
1496
|
// Ownership gate — never delete a file AIWG didn't deploy.
|
|
1401
1497
|
let owned = Object.prototype.hasOwnProperty.call(managed, name);
|
|
1402
1498
|
if (!owned) {
|
|
@@ -2878,8 +2974,47 @@ export function cleanupOldRuleFiles(rulesDir, opts = {}) {
|
|
|
2878
2974
|
* @param {boolean} opts.skipCommandsMigration - User opted out; warn about duplicates instead
|
|
2879
2975
|
* @returns {boolean} true if any AIWG command file was removed (or would be in dry-run)
|
|
2880
2976
|
*/
|
|
2977
|
+
/**
|
|
2978
|
+
* Commands directories already warned about this process. The stale-command
|
|
2979
|
+
* condition belongs to the directory, not to each deployed framework/addon, so
|
|
2980
|
+
* `aiwg use all` must not repeat it once per unit (#2541).
|
|
2981
|
+
*/
|
|
2982
|
+
const warnedCommandsDirs = new Set();
|
|
2983
|
+
|
|
2984
|
+
/**
|
|
2985
|
+
* AIWG-managed command filenames in a directory — sidecar entries or files
|
|
2986
|
+
* carrying the managed marker. Operator-authored commands and current
|
|
2987
|
+
* skill-command wrappers are excluded.
|
|
2988
|
+
*/
|
|
2989
|
+
function listManagedCommandFiles(commandsDir) {
|
|
2990
|
+
let entries;
|
|
2991
|
+
try {
|
|
2992
|
+
entries = fs.readdirSync(commandsDir, { withFileTypes: true });
|
|
2993
|
+
} catch {
|
|
2994
|
+
return [];
|
|
2995
|
+
}
|
|
2996
|
+
const sidecar = readSidecarManifest(commandsDir) || { managed: {} };
|
|
2997
|
+
const managed = sidecar.managed || {};
|
|
2998
|
+
const names = [];
|
|
2999
|
+
for (const entry of entries) {
|
|
3000
|
+
if (!entry.isFile()) continue;
|
|
3001
|
+
if (!entry.name.toLowerCase().endsWith('.md')) continue;
|
|
3002
|
+
if (managed[entry.name]?.kind === 'skill-command') continue;
|
|
3003
|
+
let owned = Object.prototype.hasOwnProperty.call(managed, entry.name);
|
|
3004
|
+
if (!owned) {
|
|
3005
|
+
try {
|
|
3006
|
+
owned = MANAGED_MARKER_RE.test(fs.readFileSync(path.join(commandsDir, entry.name), 'utf8'));
|
|
3007
|
+
} catch {
|
|
3008
|
+
owned = false;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if (owned) names.push(entry.name);
|
|
3012
|
+
}
|
|
3013
|
+
return names;
|
|
3014
|
+
}
|
|
3015
|
+
|
|
2881
3016
|
export function migrateCommandsDirectory(commandsDir, opts = {}) {
|
|
2882
|
-
const { dryRun = false, skipCommandsMigration = false, verbose = false } = opts;
|
|
3017
|
+
const { dryRun = false, skipCommandsMigration = false, verbose = false, warnOnSkip = true } = opts;
|
|
2883
3018
|
|
|
2884
3019
|
if (!fs.existsSync(commandsDir)) return false;
|
|
2885
3020
|
|
|
@@ -2887,11 +3022,27 @@ export function migrateCommandsDirectory(commandsDir, opts = {}) {
|
|
|
2887
3022
|
if (entries.length === 0) return false;
|
|
2888
3023
|
|
|
2889
3024
|
if (skipCommandsMigration) {
|
|
3025
|
+
// Structural opt-outs (project-local addon bundles) skip the migration because
|
|
3026
|
+
// it does not apply to them, not because the operator declined it. Warning
|
|
3027
|
+
// there is noise, and it fired once per bundle (#2541).
|
|
3028
|
+
if (!warnOnSkip) return false;
|
|
3029
|
+
|
|
2890
3030
|
const rel = path.relative(process.cwd(), commandsDir);
|
|
3031
|
+
// The condition is a property of the directory, not of each deployed unit;
|
|
3032
|
+
// emit it once per run no matter how many units pass through.
|
|
3033
|
+
if (warnedCommandsDirs.has(commandsDir)) return false;
|
|
3034
|
+
warnedCommandsDirs.add(commandsDir);
|
|
3035
|
+
|
|
3036
|
+
const stale = listManagedCommandFiles(commandsDir);
|
|
3037
|
+
if (stale.length === 0) return false;
|
|
3038
|
+
|
|
2891
3039
|
console.warn(`\nWarning: commands migration skipped for ${rel}`);
|
|
2892
|
-
console.warn(' Duplicate entries may appear in the command palette because old
|
|
2893
|
-
console.warn(' files overlap with newly deployed skills
|
|
2894
|
-
console.warn(`
|
|
3040
|
+
console.warn(' Duplicate entries may appear in the command palette because these old');
|
|
3041
|
+
console.warn(' AIWG command files overlap with newly deployed skills:');
|
|
3042
|
+
for (const name of stale) console.warn(` ${path.join(rel, name)}`);
|
|
3043
|
+
console.warn(' Resolve automatically by re-running without --skip-commands-migration,');
|
|
3044
|
+
console.warn(' or remove them directly:');
|
|
3045
|
+
console.warn(` rm ${stale.map((name) => path.join(rel, name)).join(' ')}`);
|
|
2895
3046
|
return false;
|
|
2896
3047
|
}
|
|
2897
3048
|
|
|
@@ -2907,6 +3058,11 @@ export function migrateCommandsDirectory(commandsDir, opts = {}) {
|
|
|
2907
3058
|
const lower = entry.name.toLowerCase();
|
|
2908
3059
|
if (!lower.endsWith('.md')) continue; // only command markdown files
|
|
2909
3060
|
const filePath = path.join(commandsDir, entry.name);
|
|
3061
|
+
// Skill-command wrappers ARE the current skill surface, not legacy command
|
|
3062
|
+
// files superseded by it. Migrating them away deletes what the same deploy
|
|
3063
|
+
// just wrote — and on a kernel-only run, which does not re-translate, they
|
|
3064
|
+
// are never restored (#2507).
|
|
3065
|
+
if (managed[entry.name]?.kind === 'skill-command') continue;
|
|
2910
3066
|
let owned = Object.prototype.hasOwnProperty.call(managed, entry.name);
|
|
2911
3067
|
if (!owned) {
|
|
2912
3068
|
try {
|