@aiwg/cli 2026.10.4 → 2026.10.6
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/agentic/code/addons/composition-engine/lib/runtime.mjs +1401 -0
- package/agentic/code/addons/composition-engine/lib/validator.mjs +513 -0
- package/agentic/code/addons/composition-engine/schemas/flow-graph.schema.json +372 -0
- package/agentic/code/providers/model-capabilities.v1.json +74 -50
- package/bin/aiwg.mjs +3 -1
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +72 -9
- package/dist/src/artifacts/fortemi-core-query-adapter.js +2 -0
- package/dist/src/artifacts/fortemi-core-sync.js +26 -8
- package/dist/src/artifacts/graph-query.js +18 -8
- package/dist/src/artifacts/index-adapters.js +339 -0
- package/dist/src/artifacts/index-builder.js +146 -16
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +91 -26
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +130 -56
- package/dist/src/artifacts/index-status.js +20 -5
- package/dist/src/artifacts/index-worker.mjs +9 -7
- package/dist/src/artifacts/memory-maintenance.js +503 -0
- package/dist/src/artifacts/prebuilt-relations.js +3 -1
- package/dist/src/artifacts/query-engine.js +263 -108
- package/dist/src/artifacts/relations-builder.js +2 -2
- package/dist/src/artifacts/source-graph.js +2 -2
- package/dist/src/artifacts/types.js +9 -1
- package/dist/src/cli/handlers/decision.js +19 -1
- package/dist/src/cli/handlers/guided-setup.js +470 -0
- package/dist/src/cli/handlers/help.js +2 -2
- package/dist/src/cli/handlers/housekeeper.js +1110 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/init.js +41 -6
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/mc.js +509 -53
- package/dist/src/cli/handlers/models.js +32 -2
- package/dist/src/cli/handlers/ralph-launcher.js +76 -4
- package/dist/src/cli/handlers/rules.js +1 -1
- package/dist/src/cli/handlers/script-runner.js +73 -2
- package/dist/src/cli/handlers/serve.js +5 -3
- package/dist/src/cli/handlers/sessions.js +40 -6
- package/dist/src/cli/handlers/setup.js +428 -1
- package/dist/src/cli/handlers/skill-lint.js +1 -1
- package/dist/src/cli/handlers/subcommands.js +118 -8
- package/dist/src/cli/handlers/use.js +279 -93
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +8 -0
- package/dist/src/cli/router.js +16 -1
- package/dist/src/config/aiwg-config.js +42 -0
- package/dist/src/config/cli.js +7 -3
- package/dist/src/config/decision-config.js +14 -1
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/benchmark.js +469 -0
- package/dist/src/decision/ledger.js +22 -3
- package/dist/src/decision/pricing.js +147 -0
- package/dist/src/decision/qualification/gate-evidence.js +18 -15
- package/dist/src/decision/qualification/load-staged.js +539 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +5 -2
- package/dist/src/decision/review/service.js +6 -4
- package/dist/src/decision/review/validate.js +23 -2
- package/dist/src/decision/savings.js +8 -3
- package/dist/src/discovery/index.js +492 -0
- package/dist/src/discovery/interview.js +233 -0
- package/dist/src/discovery/plan-apply.js +571 -0
- package/dist/src/discovery/plan-store.js +171 -0
- package/dist/src/discovery/plan-types.js +2 -0
- package/dist/src/discovery/plan-validation.js +139 -0
- package/dist/src/discovery/types.js +2 -0
- package/dist/src/extensions/commands/definitions.js +49 -9
- package/dist/src/extensions/manifest.js +71 -44
- package/dist/src/extensions/project-local-discovery.js +1 -1
- package/dist/src/extensions/project-quickref.js +18 -17
- package/dist/src/extensions/validation.js +32 -29
- package/dist/src/housekeeper/daemon-config.js +148 -0
- package/dist/src/housekeeper/ipc.js +84 -0
- package/dist/src/housekeeper/lifecycle.js +148 -0
- package/dist/src/housekeeper/platform.js +35 -0
- package/dist/src/housekeeper/status.js +70 -0
- package/dist/src/housekeeper/units.js +114 -0
- package/dist/src/jobs/flow.js +22 -21
- package/dist/src/lint/rule-activation.js +20 -5
- package/dist/src/lint/runner.js +1 -1
- package/dist/src/mcp/cli.mjs +5 -6
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/profiles.js +18 -31
- package/dist/src/mcp/profiles.mjs +18 -32
- package/dist/src/mcp/registry.js +7 -27
- package/dist/src/mcp/registry.mjs +7 -28
- package/dist/src/mcp/server.mjs +34 -1
- package/dist/src/mcp/tools/agentic-sandbox.mjs +8 -7
- package/dist/src/mcp/tools/command-run.mjs +3 -2
- package/dist/src/mcp/tools/decision.mjs +49 -48
- package/dist/src/mcp/tools/discovery.mjs +46 -21
- package/dist/src/mcp/tools/orchestration.mjs +22 -22
- package/dist/src/mcp/tools/subsystems.mjs +9 -8
- package/dist/src/mcp/tools/worker-invoke.mjs +17 -4
- package/dist/src/mcp/tools/writer-profiles.mjs +1 -1
- package/dist/src/memory/audit.js +719 -0
- package/dist/src/memory/canonical-context.js +392 -13
- package/dist/src/memory/canonical-maintenance.js +95 -0
- package/dist/src/memory/cli-result.js +22 -0
- package/dist/src/memory/cli.js +176 -0
- package/dist/src/memory/context-pack.js +57 -6
- package/dist/src/memory/cost.js +512 -0
- package/dist/src/memory/firewall-classification.d.mts +7 -0
- package/dist/src/memory/firewall-classification.mjs +32 -0
- package/dist/src/memory/index.js +8 -0
- package/dist/src/memory/line-maintenance.js +68 -0
- package/dist/src/memory/maintenance-cli.js +286 -0
- package/dist/src/memory/maintenance-contract.js +136 -0
- package/dist/src/memory/maintenance-index.js +18 -0
- package/dist/src/memory/maintenance-owner.js +2 -0
- package/dist/src/memory/maintenance-record.js +138 -0
- package/dist/src/memory/maintenance.js +254 -0
- package/dist/src/memory/provider-memory-adapter.js +154 -0
- package/dist/src/memory/recommend-cli.js +83 -0
- package/dist/src/memory/recommend.js +269 -0
- package/dist/src/memory/scheduled.js +81 -0
- package/dist/src/memory/taxonomy.js +44 -0
- package/dist/src/mission-protocol/codecs.js +19 -9
- package/dist/src/models/config-loader.js +11 -1
- package/dist/src/models/model-capabilities.v1.json +74 -50
- package/dist/src/models/provider-policy.js +125 -62
- package/dist/src/models/resolver.js +45 -8
- package/dist/src/models/worker-installation.d.mts +7 -0
- package/dist/src/models/worker-installation.mjs +169 -37
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/providers/deploy-version.d.mts +1 -0
- package/dist/src/providers/deploy-version.mjs +17 -0
- package/dist/src/providers/provider-definitions.js +19 -16
- package/dist/src/providers/skill-ownership.d.mts +11 -2
- package/dist/src/providers/skill-ownership.mjs +35 -17
- package/dist/src/rules/activation.d.mts +11 -2
- package/dist/src/rules/activation.mjs +82 -6
- package/dist/src/rules/inventory.js +1 -1
- package/dist/src/rules/rule-frontmatter.schema.json +2 -0
- package/dist/src/schema/zod-validation.d.mts +6 -0
- package/dist/src/schema/zod-validation.mjs +50 -0
- package/dist/src/serve/executor-registry.js +297 -20
- package/dist/src/serve/executor-token.d.mts +1 -0
- package/dist/src/serve/executor-token.mjs +8 -0
- package/dist/src/serve/shared-host-scheduler.js +1102 -31
- package/dist/src/serve/worker-invocation.js +114 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +33 -19
- package/dist/src/sessions/adapters/copilot.js +14 -13
- package/dist/src/sessions/adapters/cursor.js +19 -18
- package/dist/src/sessions/adapters/deepseek-harness.js +9 -8
- package/dist/src/sessions/adapters/factory.js +11 -10
- package/dist/src/sessions/adapters/generic.js +32 -23
- package/dist/src/sessions/adapters/grokbot.js +35 -35
- package/dist/src/sessions/adapters/hermes.js +20 -19
- package/dist/src/sessions/adapters/muse.js +50 -49
- package/dist/src/sessions/adapters/omp.js +5 -4
- package/dist/src/sessions/adapters/openclaw.js +30 -29
- package/dist/src/sessions/adapters/opencode.js +26 -26
- package/dist/src/sessions/adapters/openhuman.js +24 -23
- package/dist/src/sessions/adapters/pi.js +5 -4
- package/dist/src/sessions/adapters/windsurf.js +8 -8
- package/dist/src/sessions/analytics.js +10 -9
- package/dist/src/sessions/batch-contracts.js +1 -1
- package/dist/src/sessions/batch-import.js +202 -2
- package/dist/src/sessions/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +73 -54
- package/dist/src/sessions/coverage-preflight.js +26 -1
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/policy.js +18 -7
- package/dist/src/sessions/refresh.js +2 -3
- package/dist/src/sessions/repository.js +4 -2
- package/dist/src/sessions/workspace-discovery.js +92 -26
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +42 -6
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +106 -10
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/writing/exemplar-selection.js +6 -6
- package/dist/src/writing/voice-evaluation.js +24 -23
- package/dist/src/writing/writer-migration.js +14 -13
- package/dist/src/writing/writer-profile.js +19 -18
- package/dist/src/writing/writing-brief.js +18 -17
- package/dist/src/writing/writing-receipt.js +47 -46
- package/package.json +5 -2
- package/schemas/decision/DecisionReview.schema.json +78 -1
- package/schemas/mission-v1.schema.json +3 -0
- package/tools/agents/deploy-agents.mjs +67 -39
- package/tools/agents/providers/antigravity.mjs +6 -1
- package/tools/agents/providers/base.mjs +19 -13
- package/tools/agents/providers/claude.mjs +1 -1
- package/tools/agents/providers/codex.mjs +50 -56
- package/tools/agents/providers/copilot.mjs +8 -2
- package/tools/agents/providers/cursor.mjs +1 -1
- package/tools/agents/providers/deepseek-harness.mjs +1 -1
- package/tools/agents/providers/factory.mjs +7 -1
- package/tools/agents/providers/grok-build.mjs +18 -2
- package/tools/agents/providers/grokbot.mjs +1 -1
- package/tools/agents/providers/hermes.mjs +7 -13
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +28 -4
- package/tools/agents/providers/opencode.mjs +14 -5
- package/tools/agents/providers/openhuman.mjs +1 -1
- package/tools/agents/providers/pi.mjs +1 -1
- package/tools/agents/providers/provider-prepare-coordinator.mjs +29 -9
- package/tools/agents/providers/rule-delivery.mjs +263 -71
- package/tools/agents/providers/windsurf.mjs +7 -0
- package/tools/cli/workspace-status.mjs +5 -0
- package/tools/commands/deploy-prompts-codex.mjs +11 -8
- package/tools/ralph-external/lib/codex-adapter.mjs +6 -2
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +65 -9
- package/tools/ralph-external/process-monitor.mjs +7 -4
- package/tools/ralph-external/session-launcher.mjs +280 -32
- package/tools/skills/deploy-skills-codex.mjs +11 -7
- package/vscode-extension/schemas/aiwg.config.v1.json +11 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Activation-selected deployment
|
|
1
|
+
/** Activation-selected deployment shared by provider projections. */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import os from 'node:os';
|
|
@@ -6,12 +6,14 @@ import { stringify } from 'yaml';
|
|
|
6
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
7
7
|
import { importImpl } from '../../_resolve-impl.mjs';
|
|
8
8
|
import {
|
|
9
|
-
listMdFiles,
|
|
9
|
+
listMdFiles, listSkillDirs, getAddonSkillDirs, deploySkillsWithKernelRouting, isKernelSkill,
|
|
10
|
+
discoverAddons, getFrameworksForMode, collectBehaviorDirs,
|
|
10
11
|
resolveAiwgRoot, ruleEnforcementLevel, stripTriggersFromContent,
|
|
11
12
|
addManagedMarker, contentHash, replaceBoundRulesBlock, deployEmulatedBehaviors, filterAgentFiles,
|
|
12
13
|
} from './base.mjs';
|
|
13
14
|
|
|
14
|
-
const { parseRuleFrontmatter, inferRuleActivation, RULE_BINDING_TYPES } = await importImpl(import.meta.url, 'rules/activation.mjs');
|
|
15
|
+
const { parseRuleFrontmatter, inferRuleActivation, RULE_BINDING_TYPES, readCoreBody, validateCoreSet, CORE_RULES_START, CORE_RULES_END } = await importImpl(import.meta.url, 'rules/activation.mjs');
|
|
16
|
+
const { resolveDeployVersion } = await importImpl(import.meta.url, 'providers/deploy-version.mjs');
|
|
15
17
|
const { trackTemporaryDirectory } = await importImpl(import.meta.url, 'utils/temporary-directory.js');
|
|
16
18
|
const { loadRulePinConfigs, installedRuleCapabilities, resolveRulePins } = await importImpl(import.meta.url, 'rules/pins.mjs');
|
|
17
19
|
const { guardRulesInlineBudget } = await importImpl(import.meta.url, 'rules/inline-budget.mjs');
|
|
@@ -65,15 +67,62 @@ function promoteDirectories(source, destination, skip = () => false, prefix = ''
|
|
|
65
67
|
}
|
|
66
68
|
const read = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
67
69
|
const structural = file => /^(RULES-INDEX|RULES-ONDEMAND)\.(md|mdc)$/.test(path.basename(file));
|
|
70
|
+
// Verified startup owners from provider-definitions.ts. These providers do not
|
|
71
|
+
// need a prose fetch of AIWG.md to activate their compact core contracts.
|
|
72
|
+
const inlineStartup = {
|
|
73
|
+
copilot: '.github/copilot-instructions.md', factory: 'AGENTS.md', opencode: 'AGENTS.md',
|
|
74
|
+
windsurf: 'AGENTS.md', antigravity: 'AGENTS.md', muse: 'AGENTS.md',
|
|
75
|
+
omp: '.omp/AGENTS.md', 'grok-build': 'AGENTS.md',
|
|
76
|
+
};
|
|
68
77
|
|
|
69
78
|
/** Collect selected source contracts, before any tier filter or projection. */
|
|
70
79
|
export function collectDeploymentRuleSources(opts) {
|
|
71
|
-
if (opts.ruleSources)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
if (opts.ruleSources) {
|
|
81
|
+
// Pin-only callers enumerate rule files separately from behavior contracts.
|
|
82
|
+
// Retain earlier behaviors only while their owning bundle is still selected.
|
|
83
|
+
const selected = new Set(collectDeploymentRuleSources({ ...opts, ruleSources: undefined }));
|
|
84
|
+
const behaviors = (opts.previousRuleSources || []).filter(file => path.basename(file) === 'BEHAVIOR.md' && selected.has(file));
|
|
85
|
+
return [...new Set([...opts.ruleSources, ...behaviors])];
|
|
86
|
+
}
|
|
87
|
+
const corpus = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
|
|
88
|
+
const bundles = [];
|
|
89
|
+
const frameworks = getFrameworksForMode(corpus, 'all');
|
|
90
|
+
for (const kind of ['frameworks', 'addons', 'extensions', 'plugins']) {
|
|
91
|
+
const dir = path.join(corpus, 'agentic', 'code', kind);
|
|
92
|
+
if (!fs.existsSync(dir)) continue;
|
|
93
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
94
|
+
if (!entry.isDirectory()) continue;
|
|
95
|
+
const root = path.join(dir, entry.name);
|
|
96
|
+
const manifest = JSON.parse(read(path.join(root, 'manifest.json')) || '{}');
|
|
97
|
+
bundles.push({ root, manifest, id: manifest.id || manifest.framework || entry.name,
|
|
98
|
+
aliases: [entry.name, ...(manifest.modeAliases || []), ...(manifest.aliases || []),
|
|
99
|
+
...(kind === 'frameworks' ? frameworks.find(framework => framework.path === root)?.aliases || [] : [])] });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
|
|
103
|
+
// Project installation is authoritative. User-wide discoverability is not a
|
|
104
|
+
// project install, and a previous projection must never reselect retired rules.
|
|
105
|
+
const installed = configs.project.installed || {};
|
|
106
|
+
const names = Array.isArray(installed) ? installed : Object.keys(installed);
|
|
107
|
+
const selected = new Set();
|
|
108
|
+
const select = name => {
|
|
109
|
+
const id = name.replace(/^(framework|addon|extension|plugin):/, '');
|
|
110
|
+
const bundle = bundles.find(b => b.id === id || b.aliases.includes(id));
|
|
111
|
+
if (!bundle || selected.has(bundle)) return;
|
|
112
|
+
selected.add(bundle);
|
|
113
|
+
const required = Array.isArray(bundle.manifest.dependencies) ? bundle.manifest.dependencies : bundle.manifest.dependencies?.required || [];
|
|
114
|
+
for (const dep of Array.isArray(required) ? required : Object.keys(required)) select(dep);
|
|
115
|
+
};
|
|
116
|
+
for (const bundle of bundles.filter(b => b.manifest.core && b.manifest.autoInstall)) select(bundle.id);
|
|
117
|
+
for (const name of names.filter(name => name !== 'all')) select(name);
|
|
118
|
+
const invoked = bundles.find(bundle => path.resolve(bundle.root) === path.resolve(opts.srcRoot));
|
|
119
|
+
if (invoked && !opts.kernelOnly) select(invoked.id);
|
|
120
|
+
// Include the invocation being installed before its config entry is saved.
|
|
121
|
+
if (!opts.kernelOnly) for (const framework of getFrameworksForMode(opts.srcRoot, opts.mode === 'all' && names.length ? 'general' : opts.mode)) select(framework.id);
|
|
122
|
+
const roots = [...(!invoked || !opts.kernelOnly ? [opts.srcRoot] : []), ...(opts.additionalSources || []), ...[...selected].map(bundle => bundle.root)];
|
|
123
|
+
for (const entry of Object.values(installed)) if (entry?.localPath) roots.push(path.resolve(opts.target, entry.localPath));
|
|
124
|
+
const files = roots.flatMap(root => listMdFiles(path.join(root, 'rules'))).filter(file => !structural(file));
|
|
125
|
+
const behaviors = roots.flatMap(root => walk(path.join(root, 'behaviors')).filter(file => path.basename(file) === 'BEHAVIOR.md'));
|
|
77
126
|
return [...new Set([...files, ...behaviors])];
|
|
78
127
|
}
|
|
79
128
|
|
|
@@ -85,12 +134,16 @@ export async function prepareRuleDelivery(opts) {
|
|
|
85
134
|
const behavior = path.basename(file) === 'BEHAVIOR.md';
|
|
86
135
|
const rule = { ...parsed.rule, file, content, body: parsed.body, behavior,
|
|
87
136
|
name: behavior ? `behavior/${path.basename(path.dirname(file))}` : parsed.data.name || path.basename(file, '.md') };
|
|
88
|
-
if (!rule.tagged) rule.activation =
|
|
137
|
+
if (!rule.tagged) rule.activation = inferRuleActivation();
|
|
138
|
+
// Core rules (#3450) carry a compact body beside the full rule; the full text is never inlined.
|
|
139
|
+
if (rule.activation === 'core') rule.coreBody = readCoreBody(file);
|
|
89
140
|
for (const diagnostic of parsed.diagnostics) {
|
|
90
|
-
console.warn(`${opts.logicalInputPath ? opts.logicalInputPath(file) : file}: ${diagnostic.code}: ${diagnostic.code === 'missing-activation' ? `Missing activation; inferred ${rule.activation} delivery (migration fallback).` : diagnostic.message}`);
|
|
141
|
+
console.warn(`${opts.logicalInputPath ? opts.logicalInputPath(file) : file}: ${diagnostic.code}: ${diagnostic.code === 'missing-activation' ? `Missing activation; inferred ${rule.activation} delivery (migration fallback); add explicit activation frontmatter.` : diagnostic.message}`);
|
|
91
142
|
}
|
|
92
143
|
return rule;
|
|
93
144
|
});
|
|
145
|
+
const coreErrors = validateCoreSet([...new Map(rules.filter(rule => rule.coreBody !== undefined).map(rule => [rule.name, { name: rule.name, text: rule.coreBody }])).values()]);
|
|
146
|
+
if (coreErrors.length) throw new Error(coreErrors.join('; '));
|
|
94
147
|
const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
|
|
95
148
|
const installed = installedRuleCapabilities(configs);
|
|
96
149
|
const root = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
|
|
@@ -110,7 +163,8 @@ export async function prepareRuleDelivery(opts) {
|
|
|
110
163
|
const pinsByFile = new Map(pinned.rules.map(rule => [rule.file, rule]));
|
|
111
164
|
for (const rule of rules) {
|
|
112
165
|
const pin = pinsByFile.get(rule.file);
|
|
113
|
-
|
|
166
|
+
// A core rule is already delivered in full; a pin must not re-promote it to a kernel body.
|
|
167
|
+
if (!pin || rule.activation === 'core') continue;
|
|
114
168
|
rule.canonicalActivation = rule.activation;
|
|
115
169
|
rule.pinMatches = pin.matches;
|
|
116
170
|
rule.pinned = true;
|
|
@@ -148,22 +202,19 @@ export async function prepareRuleDelivery(opts) {
|
|
|
148
202
|
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error;
|
|
149
203
|
}
|
|
150
204
|
}
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
return entries;
|
|
166
|
-
} finally { fs.rmSync(indexTemp, { recursive: true, force: true }); }
|
|
205
|
+
const { collectRuleBindingEntries } = await importImpl(import.meta.url, 'artifacts/index-builder.js');
|
|
206
|
+
const sourceEntries = await collectRuleBindingEntries(root,
|
|
207
|
+
root === opts.srcRoot && !fs.existsSync(path.join(root, 'agentic')) ? '.' : 'agentic/code');
|
|
208
|
+
rebuilt = true;
|
|
209
|
+
const entries = sourceEntries.map(entry => ({ ...entry, path: path.resolve(root, entry.path) }));
|
|
210
|
+
if (cacheFile) {
|
|
211
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
212
|
+
// Workers may publish the same immutable source snapshot concurrently.
|
|
213
|
+
const temporary = `${cacheFile}.${process.pid}.tmp`;
|
|
214
|
+
fs.writeFileSync(temporary, JSON.stringify(entries.map(entry => ({ ...entry, path: opts.logicalInputPath ? opts.logicalInputPath(entry.path) : entry.path }))));
|
|
215
|
+
fs.renameSync(temporary, cacheFile);
|
|
216
|
+
}
|
|
217
|
+
return entries;
|
|
167
218
|
};
|
|
168
219
|
const localEntries = opts.bindingEntries || loadEntries(opts.target, Boolean(packageRelations));
|
|
169
220
|
let entries = [...localEntries];
|
|
@@ -244,20 +295,22 @@ export async function prepareRuleDelivery(opts) {
|
|
|
244
295
|
|
|
245
296
|
export function renderRuleKernelIndex(rules, nativePaths = false) {
|
|
246
297
|
const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
|
|
247
|
-
const paths =
|
|
248
|
-
|
|
298
|
+
const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
|
|
299
|
+
const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
|
|
300
|
+
if (!summary.length && !paths.length && !kernels.length) return '';
|
|
249
301
|
const lines = ['# Rule kernel index', ''];
|
|
250
|
-
for (const rule of
|
|
302
|
+
for (const rule of kernels) lines.push(`- ${rule.name} — ${rule.summary || 'Fetch and follow the full rule before governed work.'} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
303
|
+
for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
251
304
|
if (paths.length) {
|
|
252
|
-
lines.push('## Path rules (prompt emulation)', '',
|
|
253
|
-
'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
|
|
254
|
-
for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name}\``, '');
|
|
305
|
+
lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
|
|
306
|
+
nativePaths ? 'The provider loads these rules for matching paths; full contracts remain available through the commands below.' : 'Before reading or changing files, compare intended project-relative paths with the globs below and fetch matching rules before their first read/write. If paths are initially unknown, check when they become known. An agent-only task has no path match. This is prompt emulation, not a filesystem hook.', '');
|
|
307
|
+
for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
|
|
255
308
|
}
|
|
256
309
|
return lines.join('\n');
|
|
257
310
|
}
|
|
258
311
|
|
|
259
312
|
function managed(content, opts) {
|
|
260
|
-
return addManagedMarker(content, opts.deployVersion
|
|
313
|
+
return addManagedMarker(content, opts.deployVersion, opts.deploySource || 'bundled');
|
|
261
314
|
}
|
|
262
315
|
function put(file, content) {
|
|
263
316
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -277,6 +330,15 @@ function putStartup(file, body) {
|
|
|
277
330
|
/** Tagged rules bypass legacy aggregate writers. Untagged projections retain their tier policy. */
|
|
278
331
|
function projectRules(opts, provider) {
|
|
279
332
|
const { rules } = opts.ruleDelivery;
|
|
333
|
+
if (opts.contextFiles !== false && provider.name === 'hermes') {
|
|
334
|
+
// The old context generator carries hardcoded corpus directives. Only the
|
|
335
|
+
// selected contracts below may activate policy; preserve operator prose.
|
|
336
|
+
const file = path.join(opts.target, 'AGENTS.md');
|
|
337
|
+
const content = read(file);
|
|
338
|
+
const clean = content.replace(/(<!-- AIWG:hermes-context:start -->)([\s\S]*?)(<!-- AIWG:hermes-context:end -->)/g,
|
|
339
|
+
(_, start, body, end) => start + body.replace(/^### Rule: [\s\S]*?(?=^#{1,3} |(?![\s\S]))/gm, '') + end);
|
|
340
|
+
if (clean !== content) put(file, clean);
|
|
341
|
+
}
|
|
280
342
|
const nativePaths = ['claude', 'cursor'].includes(provider.name);
|
|
281
343
|
const rulesDir = path.isAbsolute(provider.paths.rules || '') ? provider.paths.rules : path.join(opts.target, provider.paths.rules || `.${provider.name}/rules`);
|
|
282
344
|
const tagged = rules.filter(rule => rule.tagged);
|
|
@@ -285,9 +347,14 @@ function projectRules(opts, provider) {
|
|
|
285
347
|
const name = rule.behavior ? `behaviors/${path.basename(path.dirname(rule.file))}` : path.basename(rule.file, '.md');
|
|
286
348
|
put(path.join(rulesDir, `${name}${extension}`), managed(content, opts));
|
|
287
349
|
};
|
|
350
|
+
// Legacy projectors may have enumerated the whole corpus. Rebuild managed
|
|
351
|
+
// rule output from the canonical selection; preserve operator-owned files.
|
|
352
|
+
for (const file of walk(rulesDir)) {
|
|
353
|
+
if (/\.(md|mdc)$/.test(file) && /aiwg:managed/.test(read(file))) fs.rmSync(file);
|
|
354
|
+
}
|
|
288
355
|
const priorManifestPath = path.join(rulesDir, '.aiwg-manifest.json');
|
|
289
356
|
const priorSources = fs.existsSync(priorManifestPath) ? JSON.parse(read(priorManifestPath)).ruleSources || [] : [];
|
|
290
|
-
for (const source of priorSources.filter(file => !
|
|
357
|
+
for (const source of priorSources.filter(file => !rules.some(rule => rule.file === file))) {
|
|
291
358
|
const name = path.basename(source) === 'BEHAVIOR.md' ? `behaviors/${path.basename(path.dirname(source))}` : path.basename(source, '.md');
|
|
292
359
|
for (const ext of ['.md', '.mdc']) {
|
|
293
360
|
const stale = path.join(rulesDir, name + ext);
|
|
@@ -302,6 +369,26 @@ function projectRules(opts, provider) {
|
|
|
302
369
|
if (/aiwg:managed/.test(read(dest))) fs.rmSync(dest);
|
|
303
370
|
}
|
|
304
371
|
}
|
|
372
|
+
// Core rules (#3450): compact bodies in the provider's always-loaded surface,
|
|
373
|
+
// independent of deploy mode. Native providers get one core-<name> file each;
|
|
374
|
+
// the others get a marked section first in their managed startup block.
|
|
375
|
+
const cores = [...new Map(tagged.filter(rule => rule.activation === 'core').map(rule => [rule.name, rule])).values()];
|
|
376
|
+
if (fs.existsSync(rulesDir)) {
|
|
377
|
+
for (const entry of fs.readdirSync(rulesDir).filter(name => /^core-.+\.(md|mdc)$/.test(name))) {
|
|
378
|
+
if (/aiwg:managed/.test(read(path.join(rulesDir, entry)))) fs.rmSync(path.join(rulesDir, entry));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (nativePaths) {
|
|
382
|
+
for (const rule of cores) {
|
|
383
|
+
const body = provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${rule.coreBody}\n` : `${rule.coreBody}\n`;
|
|
384
|
+
const dest = path.join(rulesDir, `core-${path.basename(rule.file, '.md')}${extension}`);
|
|
385
|
+
if (fs.existsSync(dest) && !/aiwg:managed/.test(read(dest)) && !opts.force) {
|
|
386
|
+
console.warn(`Unmanaged core rule left in place: ${dest}. Re-run with --force to replace it.`);
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
put(dest, managed(body, opts));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
305
392
|
for (const rule of tagged) {
|
|
306
393
|
if (rule.activation === 'kernel' || (nativePaths && rule.activation === 'paths')) {
|
|
307
394
|
let content = stripTriggersFromContent(rule.content);
|
|
@@ -316,6 +403,16 @@ function projectRules(opts, provider) {
|
|
|
316
403
|
writeRule(rule, content);
|
|
317
404
|
}
|
|
318
405
|
}
|
|
406
|
+
// Explicit local/external bundle deployments predate activation metadata.
|
|
407
|
+
// Keep their native rule files reachable while they migrate; missing metadata
|
|
408
|
+
// in the shipped corpus still uses the on-demand fallback (#3463).
|
|
409
|
+
for (const rule of rules.filter(rule => !rule.tagged && !rule.behavior)) {
|
|
410
|
+
const source = opts.logicalInputPath ? opts.logicalInputPath(rule.file) : rule.file;
|
|
411
|
+
if (/(?:^|\/)agentic\/code\//.test(source.replaceAll('\\', '/'))) continue;
|
|
412
|
+
let content = stripTriggersFromContent(rule.content);
|
|
413
|
+
if (provider.name === 'cursor') content = provider.transformRule(rule.file, content, opts);
|
|
414
|
+
writeRule(rule, content);
|
|
415
|
+
}
|
|
319
416
|
// A legacy consolidated index can mention now-bound rules. Replace it with an
|
|
320
417
|
// activation-selected index when migration starts; pure legacy deployments keep it.
|
|
321
418
|
// Hermes's 20K context limit requires a fixed provider projection: only
|
|
@@ -325,8 +422,7 @@ function projectRules(opts, provider) {
|
|
|
325
422
|
const isCritical = rule => String(rule.enforcement || '').trim().toLowerCase() === 'critical';
|
|
326
423
|
const deferredKernels = provider.name === 'hermes'
|
|
327
424
|
? rules.filter(rule => rule.tagged && rule.activation === 'kernel' && !rule.pinned && !isCritical(rule)) : [];
|
|
328
|
-
const indexRules =
|
|
329
|
-
? { ...rule, activation: 'summary', summary: rule.summary || 'Fetch and follow the full rule before governed work.' } : rule) : rules;
|
|
425
|
+
const indexRules = rules;
|
|
330
426
|
const index = renderRuleKernelIndex(indexRules, nativePaths);
|
|
331
427
|
if (tagged.length) {
|
|
332
428
|
for (const ext of ['.md', '.mdc']) {
|
|
@@ -335,6 +431,11 @@ function projectRules(opts, provider) {
|
|
|
335
431
|
}
|
|
336
432
|
if (index) put(path.join(rulesDir, `RULES-INDEX${extension}`), managed(provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${index}` : index, opts));
|
|
337
433
|
}
|
|
434
|
+
const legacyDemand = rules.filter(rule => !rule.tagged && !rule.behavior && rule.activation === 'on-demand');
|
|
435
|
+
if (legacyDemand.length) put(path.join(rulesDir, `RULES-ONDEMAND${extension}`), managed([
|
|
436
|
+
'# On-demand rules', '', 'These rules require explicit activation metadata. Fetch the applicable full rule before governed work.', '',
|
|
437
|
+
...legacyDemand.map(rule => `- \`${rule.name}\` — \`aiwg show rule ${rule.name} --body\``), '',
|
|
438
|
+
].join('\n'), opts));
|
|
338
439
|
fs.rmSync(path.join(rulesDir, '.aiwg-rules-budget.json'), { force: true });
|
|
339
440
|
const manifestPath = path.join(rulesDir, '.aiwg-manifest.json');
|
|
340
441
|
const manifest = fs.existsSync(manifestPath) ? JSON.parse(read(manifestPath)) : { managed: {} };
|
|
@@ -345,7 +446,7 @@ function projectRules(opts, provider) {
|
|
|
345
446
|
}
|
|
346
447
|
for (const name of Object.keys(manifest.managed)) if (!fs.existsSync(path.join(rulesDir, name))) delete manifest.managed[name];
|
|
347
448
|
put(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
348
|
-
if (!nativePaths) {
|
|
449
|
+
if (!nativePaths && opts.contextFiles !== false) {
|
|
349
450
|
// The conventional directory is not a loader: startup reaches the complete
|
|
350
451
|
// selected bodies through the provider's existing context owner.
|
|
351
452
|
const kernels = rules.filter(rule => rule.activation === 'kernel' &&
|
|
@@ -354,8 +455,9 @@ function projectRules(opts, provider) {
|
|
|
354
455
|
const routing = routes.length ? `## Invoked artifact definitions\n\nRead the complete definition before invoking its artifact:\n${routes.map(route => `- ${route}`).join('\n')}` : '';
|
|
355
456
|
let startupIndex = index;
|
|
356
457
|
let startupRouting = routing;
|
|
357
|
-
const fetchKernels = deferredKernels.length ? 'Before every task and subagent task, fetch and follow the kernel rules listed below with `aiwg show rule <name
|
|
358
|
-
const
|
|
458
|
+
const fetchKernels = deferredKernels.length ? 'Before every task and subagent task, fetch and follow the kernel rules listed below with `aiwg show rule <name> --body` before governed work. Their full rules are binding.' : '';
|
|
459
|
+
const coreSection = cores.length ? `${CORE_RULES_START}\n## Core rules\n\n${cores.map(rule => rule.coreBody).join('\n\n')}\n${CORE_RULES_END}` : '';
|
|
460
|
+
const renderBody = () => [coreSection, ...kernels.map(rule => `## Rule: ${rule.name}\n\n${rule.body.trim()}`), fetchKernels, startupIndex, startupRouting].filter(Boolean).join('\n\n');
|
|
359
461
|
const dest = path.join(opts.target, 'AGENTS.md');
|
|
360
462
|
if (provider.name === 'hermes' && block(read(dest), renderBody()).length > 19_000) {
|
|
361
463
|
// The complete summary/path index and definition routes can themselves
|
|
@@ -379,7 +481,7 @@ function projectRules(opts, provider) {
|
|
|
379
481
|
if (!body) {
|
|
380
482
|
// No activation-selected startup contract: retain legacy context owners
|
|
381
483
|
// without inventing an empty policy file or a dangling bootstrap read.
|
|
382
|
-
const contexts = ['AIWG.md', 'AGENTS.md', 'WARP.md', '.hermes.md'].map(name => path.join(opts.target, name));
|
|
484
|
+
const contexts = ['AIWG.md', 'AGENTS.md', 'WARP.md', '.hermes.md', inlineStartup[provider.name]].filter(Boolean).map(name => path.join(opts.target, name));
|
|
383
485
|
if (opts.activeWorkspace) contexts.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
|
|
384
486
|
for (const file of contexts) {
|
|
385
487
|
if (!fs.existsSync(file)) continue;
|
|
@@ -389,7 +491,12 @@ function projectRules(opts, provider) {
|
|
|
389
491
|
}
|
|
390
492
|
return;
|
|
391
493
|
}
|
|
392
|
-
if (provider.name
|
|
494
|
+
if (inlineStartup[provider.name]) {
|
|
495
|
+
// Retire the B1 indirect startup block when migrating to the actual loader.
|
|
496
|
+
const former = path.join(opts.target, 'AIWG.md');
|
|
497
|
+
if (fs.existsSync(former)) put(former, block(read(former), ''));
|
|
498
|
+
putStartup(path.join(opts.target, inlineStartup[provider.name]), body);
|
|
499
|
+
} else if (provider.name === 'hermes') {
|
|
393
500
|
const dest = path.join(opts.target, 'AGENTS.md');
|
|
394
501
|
putStartup(dest, body);
|
|
395
502
|
putStartup(path.join(opts.target, '.hermes.md'), 'Read and follow `AGENTS.md` in this directory before every task and subagent task.');
|
|
@@ -407,14 +514,19 @@ function projectRules(opts, provider) {
|
|
|
407
514
|
|
|
408
515
|
/** External helpers also project skills/flows. Inject into their body, never descriptions. */
|
|
409
516
|
function finishBindings(opts, provider) {
|
|
410
|
-
const roots = [opts.target, opts.deploymentHome,
|
|
517
|
+
const roots = [opts.target, opts.deploymentHome,
|
|
518
|
+
...[opts.kernelSkillsPath, opts.activeWorkspace, ...Object.values(provider.paths)].filter(p => p && path.isAbsolute(p))];
|
|
411
519
|
const files = [...new Set(roots.flatMap(walk))];
|
|
520
|
+
const indexRel = provider.name === 'copilot' ? '.github/RULES-BOUND.md' : `.${provider.name}/RULES-BOUND.md`;
|
|
521
|
+
const indexFile = path.join(opts.target, indexRel);
|
|
522
|
+
const anchor = target => `${target.type}-${target.name}`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
523
|
+
const sections = new Map();
|
|
412
524
|
const targetsBySource = new Map();
|
|
413
525
|
for (const target of opts.ruleDelivery.targets) {
|
|
414
526
|
targetsBySource.set(target.source, [...(targetsBySource.get(target.source) || []), target]);
|
|
415
527
|
}
|
|
416
528
|
for (const file of files.filter(file => /\.(md|toml)$/.test(file))) {
|
|
417
|
-
if (!/(?:[/\\](?:skills
|
|
529
|
+
if (path.basename(file) !== 'SKILL.md' && !/(?:[/\\](?:skills?|agents?|droids|commands?|prompts|modes?)[/\\])/.test(file)) continue;
|
|
418
530
|
if (/[/\\]rules[/\\]/.test(file) || /[/\\]\.aiwg[/\\](?!skills[/\\])/.test(file.replace(opts.target, ''))) continue;
|
|
419
531
|
let content = read(file);
|
|
420
532
|
if (!/aiwg:managed/.test(content) && !fs.existsSync(path.join(path.dirname(file), '.aiwg-managed')) && !fs.existsSync(path.join(path.dirname(file), '.aiwg-manifest.json'))) continue;
|
|
@@ -426,12 +538,30 @@ function finishBindings(opts, provider) {
|
|
|
426
538
|
const flowSource = opts.flowSkillSources?.get(file);
|
|
427
539
|
const targets = [...(flowSource ? targetsBySource.get(flowSource) || [] : []),
|
|
428
540
|
...(source ? targetsBySource.get(path.resolve(source)) || [] : opts.ruleDelivery.targets.filter(target =>
|
|
429
|
-
(skill ? target.type !== 'agent' :
|
|
541
|
+
(skill ? target.type !== 'agent' : /[/\\](?:agents?|droids|modes?)[/\\]/.test(file) ? target.type === 'agent' : target.type === 'flow') &&
|
|
430
542
|
[target.name, path.basename(target.source, '.md'), path.basename(path.dirname(target.source))].includes(name)))];
|
|
431
543
|
const rules = targets.flatMap(target => opts.ruleDelivery.bindings.get(target.source) || []);
|
|
432
544
|
if (!rules.length && !content.includes('<!-- AIWG:bound-rules:start -->')) continue;
|
|
433
|
-
|
|
434
|
-
|
|
545
|
+
const pointer = body => {
|
|
546
|
+
const clean = replaceBoundRulesBlock(body, []);
|
|
547
|
+
if (!rules.length) return clean;
|
|
548
|
+
const identities = [...new Map(targets.map(target => [`${target.type} ${target.name}`, target])).values()];
|
|
549
|
+
for (const target of identities) {
|
|
550
|
+
const bound = opts.ruleDelivery.bindings.get(target.source) || [];
|
|
551
|
+
const unique = [...new Map(bound.map(rule => [rule.name, rule])).values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
552
|
+
if (unique.length) sections.set(anchor(target), `## ${anchor(target)}\n\n` + unique.map(rule =>
|
|
553
|
+
`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`).join('\n'));
|
|
554
|
+
}
|
|
555
|
+
// Skills must carry exact fetch commands themselves: a section pointer
|
|
556
|
+
// alone loses the invocation contract in clean installs (#3473).
|
|
557
|
+
const fetch = skill ? [...new Map(rules.map(rule => [rule.name, rule])).values()]
|
|
558
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
559
|
+
.map(rule => `${rule.summary}\nFetch and follow before governed work: \`aiwg show rule ${rule.name} --body\`.`).join('\n') : '';
|
|
560
|
+
return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\nRead and follow before governed work: ${identities.map(target =>
|
|
561
|
+
`\`${indexRel}#${anchor(target)}\``).join('; ')}.\n${fetch ? fetch + '\n' : ''}<!-- AIWG:bound-rules:end -->\n`;
|
|
562
|
+
};
|
|
563
|
+
if (file.endsWith('.toml')) content = content.replace(/^developer_instructions = (".*")$/m, (_, value) => `developer_instructions = ${JSON.stringify(pointer(JSON.parse(value)))}`);
|
|
564
|
+
else content = pointer(content);
|
|
435
565
|
put(file, content);
|
|
436
566
|
// Keep deployment hashes honest after projection by external helpers.
|
|
437
567
|
if (sidecar?.managed?.[path.basename(file)]) {
|
|
@@ -439,6 +569,10 @@ function finishBindings(opts, provider) {
|
|
|
439
569
|
put(sidecarPath, `${JSON.stringify(sidecar, null, 2)}\n`);
|
|
440
570
|
}
|
|
441
571
|
}
|
|
572
|
+
if (sections.size) put(indexFile, managed('# Bound rules\n\nFetch and follow every full rule in the invoked artifact’s section before governed work.\n\n' +
|
|
573
|
+
[...sections.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, section]) => section).join('\n\n') + '\n', opts));
|
|
574
|
+
else if (/aiwg:managed/.test(read(indexFile))) fs.rmSync(indexFile);
|
|
575
|
+
|
|
442
576
|
}
|
|
443
577
|
|
|
444
578
|
/** Bindings annotate the selected deployment; they do not select standard skills
|
|
@@ -490,7 +624,8 @@ function projectBoundFlowSkills(opts, provider, selected) {
|
|
|
490
624
|
if (!fs.existsSync(file) || receipt?.flowSource === entry.source) {
|
|
491
625
|
const body = read(entry.source);
|
|
492
626
|
const markdown = entry.source.endsWith('.md') ? body : `---\nname: ${name}\nnamespace: aiwg\ndescription: Invoke ${name}.\n---\n\n\`\`\`yaml\n${body}\n\`\`\`\n`;
|
|
493
|
-
const
|
|
627
|
+
const projected = provider.transformSkillMd ? provider.transformSkillMd(markdown, opts) : markdown;
|
|
628
|
+
const content = managed(replaceBoundRulesBlock(projected, opts.ruleDelivery.bindings.get(entry.source)), opts);
|
|
494
629
|
put(file, content);
|
|
495
630
|
put(path.join(dir, '.aiwg-managed'), 'true\n');
|
|
496
631
|
put(receiptFile, `${JSON.stringify({ flowSource: entry.source,
|
|
@@ -512,7 +647,7 @@ export function warpRuleContent(content) {
|
|
|
512
647
|
|
|
513
648
|
function startupFiles(opts, provider) {
|
|
514
649
|
const bridgeNames = { claude: ['CLAUDE.md'], cursor: ['AGENTS.md'], codex: ['AGENTS.md'], hermes: ['AGENTS.md', '.hermes.md'], pi: ['AGENTS.md'], warp: ['WARP.md'], openclaw: [] };
|
|
515
|
-
const paths = ['AIWG.md', 'WORKSPACE.md', '.aiwg/AIWG.md', ...bridgeNames[provider.name]].map(name => path.join(opts.target, name));
|
|
650
|
+
const paths = ['AIWG.md', 'WORKSPACE.md', '.aiwg/AIWG.md', ...(bridgeNames[provider.name] || ['AGENTS.md']), inlineStartup[provider.name]].filter(Boolean).map(name => path.join(opts.target, name));
|
|
516
651
|
if (opts.activeWorkspace) paths.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
|
|
517
652
|
if (['claude', 'cursor', 'openclaw'].includes(provider.name)) {
|
|
518
653
|
const dir = path.isAbsolute(provider.paths.rules) ? provider.paths.rules : path.join(opts.target, provider.paths.rules);
|
|
@@ -524,10 +659,11 @@ function startupFiles(opts, provider) {
|
|
|
524
659
|
const parsed = parseRuleFrontmatter(content);
|
|
525
660
|
if (parsed.data?.paths || parsed.data?.globs) content = content.slice(0, content.length - parsed.body.length);
|
|
526
661
|
}
|
|
527
|
-
// WARP.md
|
|
528
|
-
// (managed startup blocks, legacy inline
|
|
529
|
-
// toward the rules startup budget.
|
|
530
|
-
if (provider.name === 'warp' && path.basename(file) === 'WARP.md')
|
|
662
|
+
// WARP.md and Windsurf's AGENTS.md aggregate agents (and Warp's commands)
|
|
663
|
+
// as well; only their rule content (managed startup blocks, legacy inline
|
|
664
|
+
// rules, and on-demand lists) counts toward the rules startup budget.
|
|
665
|
+
if ((provider.name === 'warp' && path.basename(file) === 'WARP.md')
|
|
666
|
+
|| (provider.name === 'windsurf' && file === path.join(opts.target, 'AGENTS.md'))) content = warpRuleContent(content);
|
|
531
667
|
return { path: file, content };
|
|
532
668
|
});
|
|
533
669
|
}
|
|
@@ -562,17 +698,23 @@ function capturePreimage(file, ancestors = new Set()) {
|
|
|
562
698
|
}
|
|
563
699
|
// cpSync materializes links; compare its output with the recursively resolved
|
|
564
700
|
// content while retaining the original link-shaped preimage for commit drift.
|
|
565
|
-
function materializeInput(from, to, ancestors = new Set()) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
701
|
+
function materializeInput(from, to, ancestors = new Set(), parentReady = false) {
|
|
702
|
+
let stat;
|
|
703
|
+
try { stat = fs.statSync(from); } catch (error) {
|
|
704
|
+
if (error.code === 'ENOENT') return;
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
570
707
|
if (stat.isDirectory()) {
|
|
708
|
+
// Only directories can lead back to an ancestor. Resolve them once and
|
|
709
|
+
// share the immutable ancestry set with their children, including aliases.
|
|
710
|
+
const real = fs.realpathSync(from);
|
|
711
|
+
if (ancestors.has(real)) throw new Error(`Cyclic deployment input: ${from}`);
|
|
712
|
+
const children = new Set([...ancestors, real]);
|
|
571
713
|
fs.mkdirSync(to, { recursive: true });
|
|
572
|
-
for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name),
|
|
714
|
+
for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name), children, true);
|
|
573
715
|
fs.chmodSync(to, stat.mode & 0o7777);
|
|
574
716
|
} else if (stat.isFile()) {
|
|
575
|
-
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
717
|
+
if (!parentReady) fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
576
718
|
fs.copyFileSync(from, to); fs.chmodSync(to, stat.mode & 0o7777);
|
|
577
719
|
} else throw new Error(`Unsupported deployment input: ${from}`);
|
|
578
720
|
}
|
|
@@ -828,6 +970,9 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
828
970
|
const previousText = fs.existsSync(manifestFile) ? read(manifestFile) : '';
|
|
829
971
|
const previous = previousText ? JSON.parse(previousText) : {};
|
|
830
972
|
const corpusRoot = resolveAiwgRoot(original.srcRoot) || original.srcRoot;
|
|
973
|
+
if (!original.deployVersion || /unknown/i.test(original.deployVersion)) {
|
|
974
|
+
original = { ...original, deployVersion: resolveDeployVersion(corpusRoot, original.deployVersion) };
|
|
975
|
+
}
|
|
831
976
|
const sourceTree = fs.existsSync(path.join(corpusRoot, 'agentic', 'code'))
|
|
832
977
|
? path.join(corpusRoot, 'agentic', 'code') : original.srcRoot;
|
|
833
978
|
const pool = original.inputPool && inputPools.get(original.inputPool);
|
|
@@ -846,7 +991,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
846
991
|
const selectedRules = collectDeploymentRuleSources({ ...original, previousRuleSources: previous.ruleSources || [] });
|
|
847
992
|
const initialPreimages = [...(pool ? pool.preimages : snapshotPreimages([sourceTree, path.join(corpusRoot, 'prebuilt', 'framework-relations'), path.join(corpusRoot, 'package.json'), path.join(corpusRoot, 'schemas')])), ...snapshotPreimages([path.join(corpusRoot, '.aiwg', '.index'),
|
|
848
993
|
manifestFile, path.join(original.target, '.aiwg', 'aiwg.config'), path.join(original.target, '.aiwg', 'AIWG.md'), path.join(original.target, '.aiwg', 'skills'), path.join(original.target, '.aiwg-location'), ...projectGraphFiles, ...userGraphFiles, ...localSources,
|
|
849
|
-
...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...selectedRules, ...selectedRules.map(file => path.dirname(file)),
|
|
994
|
+
...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...selectedRules, ...selectedRules.map(file => path.dirname(file)),
|
|
850
995
|
userConfigFile('aiwg.config'), process.env.AIWG_USER_REGISTRY_PATH || userConfigFile('installed.json'),
|
|
851
996
|
...(original.ruleSources || []), ...(previous.ruleSources || []), ...(original.bindingEntries || []).map(entry => entry.path)])];
|
|
852
997
|
let manifestState = initialPreimages.find(item => item.path === path.resolve(manifestFile)).state;
|
|
@@ -928,7 +1073,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
928
1073
|
if (JSON.stringify(capturePreimage(to)) !== JSON.stringify(materializedPreimage(expected || { kind: 'missing' }))) throw new Error(`Project-local input changed while preparing snapshot: ${from}`);
|
|
929
1074
|
sourceMappings.push({ from, to });
|
|
930
1075
|
}
|
|
931
|
-
const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.ruleSources || []), ...(previous.ruleSources || []),
|
|
1076
|
+
const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...(original.ruleSources || []), ...(previous.ruleSources || []),
|
|
932
1077
|
...(original.bindingEntries || []).map(entry => entry.path)].map(file => path.resolve(file)))];
|
|
933
1078
|
for (const [index, from] of externalInputs.entries()) {
|
|
934
1079
|
if (sourceMappings.some(item => from === item.from || from.startsWith(`${item.from}${path.sep}`))) continue;
|
|
@@ -963,6 +1108,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
963
1108
|
return result.split(snapshotRoot).join(corpusRoot);
|
|
964
1109
|
};
|
|
965
1110
|
original = { ...original, config: effectiveConfig, srcRoot: mapSource(original.srcRoot), pinConfigs, mapInputPath: mapSource, logicalInputPath,
|
|
1111
|
+
...(original.additionalSources ? { additionalSources: original.additionalSources.map(mapSource) } : {}),
|
|
966
1112
|
...(original.ruleSources ? { ruleSources: original.ruleSources.map(mapSource) } : {}),
|
|
967
1113
|
...(original.bindingEntries ? { bindingEntries: original.bindingEntries.map(entry => ({ ...entry, path: mapSource(entry.path) })) } : {}) };
|
|
968
1114
|
const target = path.resolve(original.target);
|
|
@@ -979,10 +1125,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
979
1125
|
mappings.push({ from, to, before });
|
|
980
1126
|
};
|
|
981
1127
|
// Mount directories independently so only provider-owned surfaces are promoted.
|
|
982
|
-
|
|
1128
|
+
const relativeRoots = Object.values(provider.paths).filter(dir => dir && !path.isAbsolute(dir))
|
|
1129
|
+
.map(dir => dir.split(/[\\/]/)[0]).filter(dir => dir && dir !== '.' && !path.extname(dir));
|
|
1130
|
+
for (const dir of new Set([`.${provider.name}`, '.agents', '.aiwg/skills', ...relativeRoots])) mount(path.join(target, dir), path.join(stage, dir));
|
|
983
1131
|
fs.mkdirSync(path.join(stage, '.aiwg'), { recursive: true });
|
|
984
1132
|
if (fs.existsSync(projectSnapshot)) materializeInput(projectSnapshot, path.join(stage, '.aiwg', 'aiwg.config'));
|
|
985
1133
|
const rootNames = ['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md', '.aiwg/frameworks/registry.json'];
|
|
1134
|
+
if (provider.name === 'windsurf') rootNames.push('.windsurfrules');
|
|
986
1135
|
for (const framework of getFrameworksForMode(original.srcRoot, original.mode)) {
|
|
987
1136
|
for (const entry of framework.manifest?.memory?.creates || []) {
|
|
988
1137
|
if (typeof entry?.path !== 'string' || !entry.path.startsWith('.aiwg/') || !path.extname(entry.path)) continue;
|
|
@@ -1092,11 +1241,11 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1092
1241
|
preimages.push(...snapshotPreimages([...delivery.rules.map(rule => rule.file), ...delivery.targets.map(entry => entry.source), path.join(target, '.aiwg', 'aiwg.config')]));
|
|
1093
1242
|
const opts = { ...original, target: stage, dryRun: false, ruleDelivery: delivery,
|
|
1094
1243
|
kernelSkillsPath: kernel, deploymentHome: home, activeWorkspace, hermesLegacySkills, legacyCodexSkills, staging: true };
|
|
1095
|
-
await
|
|
1244
|
+
await deploySelectedSurface(opts, provider, deploy);
|
|
1096
1245
|
if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly && opts.deployBehaviors !== false) {
|
|
1097
|
-
deployEmulatedBehaviors(
|
|
1246
|
+
deployEmulatedBehaviors(delivery.rules.filter(rule => rule.behavior).map(rule => path.dirname(rule.file)), provider.name, stage, opts);
|
|
1098
1247
|
}
|
|
1099
|
-
const selectedRoots = [original.srcRoot, ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
|
|
1248
|
+
const selectedRoots = [original.srcRoot, ...(original.additionalSources || []), ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
|
|
1100
1249
|
const selected = entry => selectedRoots.some(root => /^(agents|skills|commands|flows|workflows)[/\\]/.test(path.relative(root, entry.source)));
|
|
1101
1250
|
opts.definitionRoutes = [];
|
|
1102
1251
|
if (provider.name === 'hermes' && !opts.kernelOnly && !opts.skillsOnly && !opts.commandsOnly && !opts.rulesOnly) {
|
|
@@ -1104,7 +1253,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1104
1253
|
const selectedAgents = new Set(filterAgentFiles(agents.map(target => target.source), opts));
|
|
1105
1254
|
for (const target of agents.filter(target => selectedAgents.has(target.source))) {
|
|
1106
1255
|
const source = target.source;
|
|
1107
|
-
put(path.join(stage, '.hermes', 'agents', path.basename(source)), replaceBoundRulesBlock(read(source), delivery.bindings.get(source)));
|
|
1256
|
+
put(path.join(stage, '.hermes', 'agents', path.basename(source)), managed(replaceBoundRulesBlock(read(source), delivery.bindings.get(source)), opts));
|
|
1108
1257
|
opts.definitionRoutes.push(`${target.name}: .hermes/agents/${path.basename(source)}`);
|
|
1109
1258
|
}
|
|
1110
1259
|
}
|
|
@@ -1115,8 +1264,10 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1115
1264
|
if (fs.existsSync(dest)) opts.definitionRoutes.push(`${entry.name}: .warp/agents/${path.basename(entry.source)}`);
|
|
1116
1265
|
}
|
|
1117
1266
|
}
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1267
|
+
if (!opts.deferRuleProjection) {
|
|
1268
|
+
if (opts.kernelOnly || opts.deployRules || opts.rulesOnly || (!opts.skillsOnly && !opts.commandsOnly && (inlineStartup[provider.name] || ['hermes', 'warp'].includes(provider.name)))) projectRules(opts, provider);
|
|
1269
|
+
finishBindings(opts, provider);
|
|
1270
|
+
}
|
|
1120
1271
|
// Normalize all generated path wrappers before counting or promoting bytes.
|
|
1121
1272
|
const normalize = content => {
|
|
1122
1273
|
for (const mapping of [...mappings].sort((a, b) => b.to.length - a.to.length)) content = content.split(mapping.to).join(mapping.from);
|
|
@@ -1150,6 +1301,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1150
1301
|
const delegated = (from, rel) => mappings.some(mapping => mapping.from !== from
|
|
1151
1302
|
&& mapping.from.startsWith(`${from}${path.sep}`)
|
|
1152
1303
|
&& (path.join(from, rel) === mapping.from || path.join(from, rel).startsWith(`${mapping.from}${path.sep}`)));
|
|
1304
|
+
if (original.contextFiles === false) {
|
|
1305
|
+
// Keep context inputs in the snapshot for validation, but exclude them
|
|
1306
|
+
// from both dry-run output and promotion (including unchanged rewrites).
|
|
1307
|
+
for (let i = rootNames.length - 1; i >= 0; i--) {
|
|
1308
|
+
if (['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md'].includes(rootNames[i])) rootNames.splice(i, 1);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1153
1311
|
if (pool) verifyInputPool(pool);
|
|
1154
1312
|
if (original.previewReport) return { report: guarded.report, noop: true };
|
|
1155
1313
|
if (original.dryRun) {
|
|
@@ -1244,7 +1402,21 @@ export function commitRuleDeploymentPlan(plan) {
|
|
|
1244
1402
|
if (before.get(rel) === fileDigest(file)) continue;
|
|
1245
1403
|
const buffer = fs.readFileSync(file);
|
|
1246
1404
|
// Provider output includes binary skill resources; normalize text only.
|
|
1247
|
-
|
|
1405
|
+
let output = buffer.includes(0) ? buffer : Buffer.from(normalize(buffer.toString('utf8')));
|
|
1406
|
+
if (path.basename(file) === '.aiwg-manifest.json') {
|
|
1407
|
+
const receipt = JSON.parse(output.toString('utf8'));
|
|
1408
|
+
for (const [name, record] of Object.entries(receipt.managed || {})) {
|
|
1409
|
+
const recordedFile = path.resolve(path.dirname(file), name);
|
|
1410
|
+
if (!within(path.dirname(file), recordedFile) || !fs.existsSync(recordedFile)) continue;
|
|
1411
|
+
const recorded = fs.readFileSync(recordedFile);
|
|
1412
|
+
// Do not bless operator edits whose bytes already differ from the
|
|
1413
|
+
// ownership receipt. Only normalize hashes of pristine staged files.
|
|
1414
|
+
if (record.hash !== `sha256:${contentHash(recorded)}`) continue;
|
|
1415
|
+
const promoted = recorded.includes(0) ? recorded : Buffer.from(normalize(recorded.toString('utf8')));
|
|
1416
|
+
record.hash = `sha256:${contentHash(promoted)}`;
|
|
1417
|
+
}
|
|
1418
|
+
output = Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`);
|
|
1419
|
+
}
|
|
1248
1420
|
fs.mkdirSync(path.dirname(path.join(from, rel)), { recursive: true });
|
|
1249
1421
|
fs.writeFileSync(path.join(from, rel), output);
|
|
1250
1422
|
}
|
|
@@ -1258,6 +1430,13 @@ export function commitRuleDeploymentPlan(plan) {
|
|
|
1258
1430
|
|
|
1259
1431
|
/** Stage and commit serially for existing provider callers. */
|
|
1260
1432
|
export async function deployWithRuleDelivery(original, provider, deploy) {
|
|
1433
|
+
// Kernel source passes share the same selected rule set. Deploy their
|
|
1434
|
+
// skills directly; the final source projects rules and bindings across the
|
|
1435
|
+
// complete destination. Full passes retain staging for bound flow skills
|
|
1436
|
+
// and provider definitions, but defer rule presentation above.
|
|
1437
|
+
if (original.kernelOnly && original.deferRuleProjection && !globalThis[PREPARATION_HOOK]) {
|
|
1438
|
+
return deploySelectedSurface(original, provider, deploy);
|
|
1439
|
+
}
|
|
1261
1440
|
if (original.ruleDelivery) return deploy(original);
|
|
1262
1441
|
const hook = globalThis[PREPARATION_HOOK];
|
|
1263
1442
|
if (hook) {
|
|
@@ -1268,3 +1447,16 @@ export async function deployWithRuleDelivery(original, provider, deploy) {
|
|
|
1268
1447
|
}
|
|
1269
1448
|
return commitRuleDeploymentPlan(await prepareRuleDeployment(original, provider, deploy));
|
|
1270
1449
|
}
|
|
1450
|
+
|
|
1451
|
+
async function deploySelectedSurface(opts, provider, deploy) {
|
|
1452
|
+
if (!opts.kernelOnly || provider.name !== 'warp') return deploy(opts);
|
|
1453
|
+
// Warp's aggregate writer emits bulk definitions even in skills-only mode.
|
|
1454
|
+
// Both staged and deferred kernel passes use only its shared skill router.
|
|
1455
|
+
const skills = [...listSkillDirs(path.join(opts.srcRoot, 'skills')),
|
|
1456
|
+
...getFrameworksForMode(opts.srcRoot, opts.mode).flatMap(bundle => listSkillDirs(path.join(bundle.path, 'skills'))),
|
|
1457
|
+
...getAddonSkillDirs(opts.srcRoot)].filter(isKernelSkill);
|
|
1458
|
+
const standard = path.join(opts.target, provider.paths.skills);
|
|
1459
|
+
for (const dir of new Set([opts.kernelSkillsPath || provider.kernelSkillsPath, provider.paths.crossAgentSkills])) {
|
|
1460
|
+
if (dir) deploySkillsWithKernelRouting(skills, standard, path.isAbsolute(dir) ? dir : path.join(opts.target, dir), opts);
|
|
1461
|
+
}
|
|
1462
|
+
}
|