@aiwg/cli 2026.10.5 → 2026.10.7
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 +7 -3
- package/dist/src/a2a/agent-card.js +11 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/browser-export.js +1 -0
- package/dist/src/artifacts/citation-parser.js +8 -1
- package/dist/src/artifacts/cli.js +57 -6
- 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/fortemi-shard-contract/2.2.0/contract.json +103 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-config.schema.json +212 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-embedding-set.schema.json +240 -0
- package/dist/src/artifacts/fortemi-shard-contract/2.2.0/full-v1-manifest.schema.json +451 -0
- package/dist/src/artifacts/fortemi-shard-export.js +284 -0
- 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 +143 -14
- package/dist/src/artifacts/index-files.js +15 -4
- package/dist/src/artifacts/index-preparation.js +2 -0
- package/dist/src/artifacts/index-reader.js +44 -0
- package/dist/src/artifacts/index-refresh.js +114 -55
- package/dist/src/artifacts/index-status.js +20 -5
- 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/auth/client.js +5 -1
- package/dist/src/auth/credential-store.js +5 -1
- package/dist/src/auth/sensitive-input.js +151 -0
- package/dist/src/channel/manager.mjs +3 -0
- package/dist/src/cli/handlers/agentcard.js +47 -25
- package/dist/src/cli/handlers/auth.js +22 -2
- package/dist/src/cli/handlers/decision.js +22 -3
- package/dist/src/cli/handlers/effect.js +4 -84
- 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/install.js +6 -0
- package/dist/src/cli/handlers/local-executor.js +5 -3
- package/dist/src/cli/handlers/marketplace.js +15 -4
- 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 +1 -0
- package/dist/src/cli/handlers/setup.js +432 -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 +335 -107
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/prompt-utils.js +81 -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/gitignore.js +4 -0
- package/dist/src/config/native-sandbox-warnings.js +25 -0
- package/dist/src/config/workspace.js +2 -1
- package/dist/src/contributors/validation.js +6 -5
- package/dist/src/decision/adapters/jev.js +20 -31
- package/dist/src/decision/admission.js +7 -3
- package/dist/src/decision/ask.js +6 -1
- package/dist/src/decision/benchmark.js +482 -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 +23 -18
- package/dist/src/decision/qualification/load-staged.js +576 -0
- package/dist/src/decision/qualification/load.js +109 -33
- package/dist/src/decision/qualification/runner.js +12 -5
- 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 +238 -0
- package/dist/src/discovery/plan-apply.js +567 -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/effects/host.js +91 -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-local-gitignore.js +2 -0
- 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/issues/cli.js +16 -5
- package/dist/src/issues/live.js +44 -12
- 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/marketplace/exchange.js +18 -6
- package/dist/src/marketplace/provenance.js +3 -2
- package/dist/src/mcp/adapters/codex-runtime.js +46 -76
- package/dist/src/mcp/cli.mjs +22 -96
- package/dist/src/mcp/config-file.mjs +6 -2
- package/dist/src/mcp/config-layers.mjs +23 -7
- package/dist/src/mcp/grok-build-config.mjs +2 -1
- package/dist/src/mcp/muse-native-config.mjs +2 -1
- 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 +30 -6
- 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/project-registry.js +2 -1
- 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/mission-protocol/worker-status.js +91 -1
- 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 +8 -1
- package/dist/src/models/worker-installation.mjs +231 -38
- package/dist/src/models/wrapper-route.js +1 -1
- package/dist/src/output-modes/registry.js +8 -7
- package/dist/src/packages/adapters/git.js +99 -23
- package/dist/src/packages/adapters/gitea.js +8 -27
- package/dist/src/packages/adapters/github.js +7 -25
- package/dist/src/packages/registry.js +4 -4
- package/dist/src/plugin/metadata-validator.js +1 -1
- package/dist/src/process/cli-command.d.mts +5 -0
- package/dist/src/process/cli-command.mjs +42 -0
- 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 +124 -15
- package/dist/src/sessions/adapters/claude.js +19 -18
- package/dist/src/sessions/adapters/codex.js +10 -9
- 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/candidates.js +7 -6
- package/dist/src/sessions/contracts.js +63 -53
- package/dist/src/sessions/output-registration.js +11 -10
- package/dist/src/sessions/workspace-discovery.js +3 -4
- package/dist/src/smiths/context-pipeline/aiwg-md.js +18 -1
- package/dist/src/smiths/context-pipeline/claude-hook.js +46 -10
- package/dist/src/smiths/context-pipeline/context-file-safety.js +330 -9
- package/dist/src/smiths/context-pipeline/finalization.js +3 -2
- package/dist/src/smiths/context-pipeline/generator.js +7 -19
- package/dist/src/smiths/context-pipeline/legacy-inject.js +3 -10
- package/dist/src/smiths/context-pipeline/managed-hook.js +5 -5
- package/dist/src/smiths/context-pipeline/parallelism-section.js +72 -5
- package/dist/src/smiths/context-pipeline/workspace-context.js +110 -22
- package/dist/src/tracker/project-inputs.js +2 -0
- package/dist/src/update/checker.d.mts +4 -0
- package/dist/src/update/checker.mjs +14 -0
- package/dist/src/update/notifier.mjs +2 -4
- 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 +24 -14
- package/tools/agents/providers/claude.mjs +6 -3
- package/tools/agents/providers/codex.mjs +54 -57
- package/tools/agents/providers/context-write.mjs +28 -0
- package/tools/agents/providers/copilot.mjs +13 -4
- 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 +12 -15
- package/tools/agents/providers/muse-hooks.mjs +21 -14
- package/tools/agents/providers/muse.mjs +7 -2
- package/tools/agents/providers/omp.mjs +31 -5
- 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 +289 -80
- package/tools/agents/providers/warp.mjs +4 -1
- package/tools/agents/providers/windsurf.mjs +13 -3
- 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/consecutive-failure-circuit-breaker.mjs +26 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +7 -1
- package/tools/ralph-external/lib/muse-adapter.mjs +12 -2
- package/tools/ralph-external/lib/recovery-probe.mjs +14 -4
- 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 +49 -8
- package/tools/ralph-external/session-launcher.mjs +405 -35
- 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,17 +6,20 @@ 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');
|
|
18
20
|
const { userConfigFile } = await importImpl(import.meta.url, 'config/user-config-dir.mjs');
|
|
19
21
|
const { resolveProjectAiwgDir } = await importImpl(import.meta.url, 'config/project-artifacts-runtime.mjs');
|
|
22
|
+
import { writeContextFileSync, CONTEXT_STAGE_ENV } from './context-write.mjs';
|
|
20
23
|
const PREPARATION_HOOK = Symbol.for('aiwg.provider-preparation-hook.v1');
|
|
21
24
|
|
|
22
25
|
export function installRuleDeploymentPreparationHook(hook) {
|
|
@@ -65,15 +68,62 @@ function promoteDirectories(source, destination, skip = () => false, prefix = ''
|
|
|
65
68
|
}
|
|
66
69
|
const read = file => fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
67
70
|
const structural = file => /^(RULES-INDEX|RULES-ONDEMAND)\.(md|mdc)$/.test(path.basename(file));
|
|
71
|
+
// Verified startup owners from provider-definitions.ts. These providers do not
|
|
72
|
+
// need a prose fetch of AIWG.md to activate their compact core contracts.
|
|
73
|
+
const inlineStartup = {
|
|
74
|
+
copilot: '.github/copilot-instructions.md', factory: 'AGENTS.md', opencode: 'AGENTS.md',
|
|
75
|
+
windsurf: 'AGENTS.md', antigravity: 'AGENTS.md', muse: 'AGENTS.md',
|
|
76
|
+
omp: '.omp/AGENTS.md', 'grok-build': 'AGENTS.md',
|
|
77
|
+
};
|
|
68
78
|
|
|
69
79
|
/** Collect selected source contracts, before any tier filter or projection. */
|
|
70
80
|
export function collectDeploymentRuleSources(opts) {
|
|
71
|
-
if (opts.ruleSources)
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
81
|
+
if (opts.ruleSources) {
|
|
82
|
+
// Pin-only callers enumerate rule files separately from behavior contracts.
|
|
83
|
+
// Retain earlier behaviors only while their owning bundle is still selected.
|
|
84
|
+
const selected = new Set(collectDeploymentRuleSources({ ...opts, ruleSources: undefined }));
|
|
85
|
+
const behaviors = (opts.previousRuleSources || []).filter(file => path.basename(file) === 'BEHAVIOR.md' && selected.has(file));
|
|
86
|
+
return [...new Set([...opts.ruleSources, ...behaviors])];
|
|
87
|
+
}
|
|
88
|
+
const corpus = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
|
|
89
|
+
const bundles = [];
|
|
90
|
+
const frameworks = getFrameworksForMode(corpus, 'all');
|
|
91
|
+
for (const kind of ['frameworks', 'addons', 'extensions', 'plugins']) {
|
|
92
|
+
const dir = path.join(corpus, 'agentic', 'code', kind);
|
|
93
|
+
if (!fs.existsSync(dir)) continue;
|
|
94
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
95
|
+
if (!entry.isDirectory()) continue;
|
|
96
|
+
const root = path.join(dir, entry.name);
|
|
97
|
+
const manifest = JSON.parse(read(path.join(root, 'manifest.json')) || '{}');
|
|
98
|
+
bundles.push({ root, manifest, id: manifest.id || manifest.framework || entry.name,
|
|
99
|
+
aliases: [entry.name, ...(manifest.modeAliases || []), ...(manifest.aliases || []),
|
|
100
|
+
...(kind === 'frameworks' ? frameworks.find(framework => framework.path === root)?.aliases || [] : [])] });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
|
|
104
|
+
// Project installation is authoritative. User-wide discoverability is not a
|
|
105
|
+
// project install, and a previous projection must never reselect retired rules.
|
|
106
|
+
const installed = configs.project.installed || {};
|
|
107
|
+
const names = Array.isArray(installed) ? installed : Object.keys(installed);
|
|
108
|
+
const selected = new Set();
|
|
109
|
+
const select = name => {
|
|
110
|
+
const id = name.replace(/^(framework|addon|extension|plugin):/, '');
|
|
111
|
+
const bundle = bundles.find(b => b.id === id || b.aliases.includes(id));
|
|
112
|
+
if (!bundle || selected.has(bundle)) return;
|
|
113
|
+
selected.add(bundle);
|
|
114
|
+
const required = Array.isArray(bundle.manifest.dependencies) ? bundle.manifest.dependencies : bundle.manifest.dependencies?.required || [];
|
|
115
|
+
for (const dep of Array.isArray(required) ? required : Object.keys(required)) select(dep);
|
|
116
|
+
};
|
|
117
|
+
for (const bundle of bundles.filter(b => b.manifest.core && b.manifest.autoInstall)) select(bundle.id);
|
|
118
|
+
for (const name of names.filter(name => name !== 'all')) select(name);
|
|
119
|
+
const invoked = bundles.find(bundle => path.resolve(bundle.root) === path.resolve(opts.srcRoot));
|
|
120
|
+
if (invoked && !opts.kernelOnly) select(invoked.id);
|
|
121
|
+
// Include the invocation being installed before its config entry is saved.
|
|
122
|
+
if (!opts.kernelOnly) for (const framework of getFrameworksForMode(opts.srcRoot, opts.mode === 'all' && names.length ? 'general' : opts.mode)) select(framework.id);
|
|
123
|
+
const roots = [...(!invoked || !opts.kernelOnly ? [opts.srcRoot] : []), ...(opts.additionalSources || []), ...[...selected].map(bundle => bundle.root)];
|
|
124
|
+
for (const entry of Object.values(installed)) if (entry?.localPath) roots.push(path.resolve(opts.target, entry.localPath));
|
|
125
|
+
const files = roots.flatMap(root => listMdFiles(path.join(root, 'rules'))).filter(file => !structural(file));
|
|
126
|
+
const behaviors = roots.flatMap(root => walk(path.join(root, 'behaviors')).filter(file => path.basename(file) === 'BEHAVIOR.md'));
|
|
77
127
|
return [...new Set([...files, ...behaviors])];
|
|
78
128
|
}
|
|
79
129
|
|
|
@@ -85,12 +135,16 @@ export async function prepareRuleDelivery(opts) {
|
|
|
85
135
|
const behavior = path.basename(file) === 'BEHAVIOR.md';
|
|
86
136
|
const rule = { ...parsed.rule, file, content, body: parsed.body, behavior,
|
|
87
137
|
name: behavior ? `behavior/${path.basename(path.dirname(file))}` : parsed.data.name || path.basename(file, '.md') };
|
|
88
|
-
if (!rule.tagged) rule.activation =
|
|
138
|
+
if (!rule.tagged) rule.activation = inferRuleActivation();
|
|
139
|
+
// Core rules (#3450) carry a compact body beside the full rule; the full text is never inlined.
|
|
140
|
+
if (rule.activation === 'core') rule.coreBody = readCoreBody(file);
|
|
89
141
|
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}`);
|
|
142
|
+
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
143
|
}
|
|
92
144
|
return rule;
|
|
93
145
|
});
|
|
146
|
+
const coreErrors = validateCoreSet([...new Map(rules.filter(rule => rule.coreBody !== undefined).map(rule => [rule.name, { name: rule.name, text: rule.coreBody }])).values()]);
|
|
147
|
+
if (coreErrors.length) throw new Error(coreErrors.join('; '));
|
|
94
148
|
const configs = opts.pinConfigs || loadRulePinConfigs(opts.target, opts.config);
|
|
95
149
|
const installed = installedRuleCapabilities(configs);
|
|
96
150
|
const root = resolveAiwgRoot(opts.srcRoot) || opts.srcRoot;
|
|
@@ -110,7 +164,8 @@ export async function prepareRuleDelivery(opts) {
|
|
|
110
164
|
const pinsByFile = new Map(pinned.rules.map(rule => [rule.file, rule]));
|
|
111
165
|
for (const rule of rules) {
|
|
112
166
|
const pin = pinsByFile.get(rule.file);
|
|
113
|
-
|
|
167
|
+
// A core rule is already delivered in full; a pin must not re-promote it to a kernel body.
|
|
168
|
+
if (!pin || rule.activation === 'core') continue;
|
|
114
169
|
rule.canonicalActivation = rule.activation;
|
|
115
170
|
rule.pinMatches = pin.matches;
|
|
116
171
|
rule.pinned = true;
|
|
@@ -136,34 +191,35 @@ export async function prepareRuleDelivery(opts) {
|
|
|
136
191
|
const key = `${anchor}\0${entry.path}`;
|
|
137
192
|
if (!entryPaths.has(key)) entryPaths.set(key, path.resolve(anchor, entry.path));
|
|
138
193
|
const resolvedPath = entryPaths.get(key);
|
|
139
|
-
|
|
194
|
+
// Canonical IDs hash the indexed package-relative path. Keep it through
|
|
195
|
+
// resolution; the selected target is anchored to root below.
|
|
196
|
+
return { ...entry, path: entry.path.startsWith('agentic/code/') ? entry.path
|
|
197
|
+
: opts.mapInputPath ? opts.mapInputPath(resolvedPath) : resolvedPath };
|
|
140
198
|
});
|
|
141
199
|
let rebuilt = false;
|
|
142
200
|
const cacheDir = process.env.AIWG_RULE_BINDING_INDEX_CACHE;
|
|
143
201
|
const cacheFile = cacheDir && path.join(cacheDir, `${contentHash(opts.logicalInputPath ? opts.logicalInputPath(root) : root)}.json`);
|
|
144
202
|
const buildSourceEntries = async (refresh = false) => {
|
|
145
203
|
if (!refresh && cacheFile) {
|
|
146
|
-
try { return JSON.parse(fs.readFileSync(cacheFile, 'utf8')).map(entry => ({ ...entry, path: opts.mapInputPath ? opts.mapInputPath(entry.path) : entry.path })); }
|
|
204
|
+
try { return JSON.parse(fs.readFileSync(cacheFile, 'utf8')).map(entry => ({ ...entry, path: path.isAbsolute(entry.path) && opts.mapInputPath ? opts.mapInputPath(entry.path) : entry.path })); }
|
|
147
205
|
catch (error) {
|
|
148
206
|
if (error.code !== 'ENOENT' && !(error instanceof SyntaxError)) throw error;
|
|
149
207
|
}
|
|
150
208
|
}
|
|
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 }); }
|
|
209
|
+
const { collectRuleBindingEntries } = await importImpl(import.meta.url, 'artifacts/index-builder.js');
|
|
210
|
+
const sourceEntries = await collectRuleBindingEntries(root,
|
|
211
|
+
root === opts.srcRoot && !fs.existsSync(path.join(root, 'agentic')) ? '.' : 'agentic/code');
|
|
212
|
+
rebuilt = true;
|
|
213
|
+
const entries = sourceEntries.map(entry => ({ ...entry, path: entry.path.startsWith('agentic/code/')
|
|
214
|
+
? entry.path : path.resolve(root, entry.path) }));
|
|
215
|
+
if (cacheFile) {
|
|
216
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
217
|
+
// Workers may publish the same immutable source snapshot concurrently.
|
|
218
|
+
const temporary = `${cacheFile}.${process.pid}.tmp`;
|
|
219
|
+
fs.writeFileSync(temporary, JSON.stringify(entries.map(entry => ({ ...entry, path: opts.logicalInputPath ? opts.logicalInputPath(entry.path) : entry.path }))));
|
|
220
|
+
fs.renameSync(temporary, cacheFile);
|
|
221
|
+
}
|
|
222
|
+
return entries;
|
|
167
223
|
};
|
|
168
224
|
const localEntries = opts.bindingEntries || loadEntries(opts.target, Boolean(packageRelations));
|
|
169
225
|
let entries = [...localEntries];
|
|
@@ -244,20 +300,22 @@ export async function prepareRuleDelivery(opts) {
|
|
|
244
300
|
|
|
245
301
|
export function renderRuleKernelIndex(rules, nativePaths = false) {
|
|
246
302
|
const summary = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'summary');
|
|
247
|
-
const paths =
|
|
248
|
-
|
|
303
|
+
const paths = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'paths');
|
|
304
|
+
const kernels = rules.filter(rule => rule.tagged && (rule.canonicalActivation || rule.activation) === 'kernel');
|
|
305
|
+
if (!summary.length && !paths.length && !kernels.length) return '';
|
|
249
306
|
const lines = ['# Rule kernel index', ''];
|
|
250
|
-
for (const rule of
|
|
307
|
+
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\`.`, '');
|
|
308
|
+
for (const rule of summary) lines.push(`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`, '');
|
|
251
309
|
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}\``, '');
|
|
310
|
+
lines.push(nativePaths ? '## Path rules (native)' : '## Path rules (prompt emulation)', '',
|
|
311
|
+
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.', '');
|
|
312
|
+
for (const rule of paths) lines.push(`- ${rule.name} | ${rule.paths.map(glob => `\`${glob}\``).join(', ')} | ${rule.summary} | \`aiwg show rule ${rule.name} --body\``, '');
|
|
255
313
|
}
|
|
256
314
|
return lines.join('\n');
|
|
257
315
|
}
|
|
258
316
|
|
|
259
317
|
function managed(content, opts) {
|
|
260
|
-
return addManagedMarker(content, opts.deployVersion
|
|
318
|
+
return addManagedMarker(content, opts.deployVersion, opts.deploySource || 'bundled');
|
|
261
319
|
}
|
|
262
320
|
function put(file, content) {
|
|
263
321
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
@@ -271,12 +329,21 @@ function block(content, body) {
|
|
|
271
329
|
|
|
272
330
|
function putStartup(file, body) {
|
|
273
331
|
const content = fs.existsSync(file) ? read(file) : '<!-- aiwg-managed -->\n';
|
|
274
|
-
|
|
332
|
+
writeContextFileSync(file, block(content, body));
|
|
275
333
|
}
|
|
276
334
|
|
|
277
335
|
/** Tagged rules bypass legacy aggregate writers. Untagged projections retain their tier policy. */
|
|
278
336
|
function projectRules(opts, provider) {
|
|
279
337
|
const { rules } = opts.ruleDelivery;
|
|
338
|
+
if (opts.contextFiles !== false && provider.name === 'hermes') {
|
|
339
|
+
// The old context generator carries hardcoded corpus directives. Only the
|
|
340
|
+
// selected contracts below may activate policy; preserve operator prose.
|
|
341
|
+
const file = path.join(opts.target, 'AGENTS.md');
|
|
342
|
+
const content = read(file);
|
|
343
|
+
const clean = content.replace(/(<!-- AIWG:hermes-context:start -->)([\s\S]*?)(<!-- AIWG:hermes-context:end -->)/g,
|
|
344
|
+
(_, start, body, end) => start + body.replace(/^### Rule: [\s\S]*?(?=^#{1,3} |(?![\s\S]))/gm, '') + end);
|
|
345
|
+
if (clean !== content) writeContextFileSync(file, clean);
|
|
346
|
+
}
|
|
280
347
|
const nativePaths = ['claude', 'cursor'].includes(provider.name);
|
|
281
348
|
const rulesDir = path.isAbsolute(provider.paths.rules || '') ? provider.paths.rules : path.join(opts.target, provider.paths.rules || `.${provider.name}/rules`);
|
|
282
349
|
const tagged = rules.filter(rule => rule.tagged);
|
|
@@ -285,9 +352,14 @@ function projectRules(opts, provider) {
|
|
|
285
352
|
const name = rule.behavior ? `behaviors/${path.basename(path.dirname(rule.file))}` : path.basename(rule.file, '.md');
|
|
286
353
|
put(path.join(rulesDir, `${name}${extension}`), managed(content, opts));
|
|
287
354
|
};
|
|
355
|
+
// Legacy projectors may have enumerated the whole corpus. Rebuild managed
|
|
356
|
+
// rule output from the canonical selection; preserve operator-owned files.
|
|
357
|
+
for (const file of walk(rulesDir)) {
|
|
358
|
+
if (/\.(md|mdc)$/.test(file) && /aiwg:managed/.test(read(file))) fs.rmSync(file);
|
|
359
|
+
}
|
|
288
360
|
const priorManifestPath = path.join(rulesDir, '.aiwg-manifest.json');
|
|
289
361
|
const priorSources = fs.existsSync(priorManifestPath) ? JSON.parse(read(priorManifestPath)).ruleSources || [] : [];
|
|
290
|
-
for (const source of priorSources.filter(file => !
|
|
362
|
+
for (const source of priorSources.filter(file => !rules.some(rule => rule.file === file))) {
|
|
291
363
|
const name = path.basename(source) === 'BEHAVIOR.md' ? `behaviors/${path.basename(path.dirname(source))}` : path.basename(source, '.md');
|
|
292
364
|
for (const ext of ['.md', '.mdc']) {
|
|
293
365
|
const stale = path.join(rulesDir, name + ext);
|
|
@@ -302,6 +374,26 @@ function projectRules(opts, provider) {
|
|
|
302
374
|
if (/aiwg:managed/.test(read(dest))) fs.rmSync(dest);
|
|
303
375
|
}
|
|
304
376
|
}
|
|
377
|
+
// Core rules (#3450): compact bodies in the provider's always-loaded surface,
|
|
378
|
+
// independent of deploy mode. Native providers get one core-<name> file each;
|
|
379
|
+
// the others get a marked section first in their managed startup block.
|
|
380
|
+
const cores = [...new Map(tagged.filter(rule => rule.activation === 'core').map(rule => [rule.name, rule])).values()];
|
|
381
|
+
if (fs.existsSync(rulesDir)) {
|
|
382
|
+
for (const entry of fs.readdirSync(rulesDir).filter(name => /^core-.+\.(md|mdc)$/.test(name))) {
|
|
383
|
+
if (/aiwg:managed/.test(read(path.join(rulesDir, entry)))) fs.rmSync(path.join(rulesDir, entry));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (nativePaths) {
|
|
387
|
+
for (const rule of cores) {
|
|
388
|
+
const body = provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${rule.coreBody}\n` : `${rule.coreBody}\n`;
|
|
389
|
+
const dest = path.join(rulesDir, `core-${path.basename(rule.file, '.md')}${extension}`);
|
|
390
|
+
if (fs.existsSync(dest) && !/aiwg:managed/.test(read(dest)) && !opts.force) {
|
|
391
|
+
console.warn(`Unmanaged core rule left in place: ${dest}. Re-run with --force to replace it.`);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
put(dest, managed(body, opts));
|
|
395
|
+
}
|
|
396
|
+
}
|
|
305
397
|
for (const rule of tagged) {
|
|
306
398
|
if (rule.activation === 'kernel' || (nativePaths && rule.activation === 'paths')) {
|
|
307
399
|
let content = stripTriggersFromContent(rule.content);
|
|
@@ -316,6 +408,16 @@ function projectRules(opts, provider) {
|
|
|
316
408
|
writeRule(rule, content);
|
|
317
409
|
}
|
|
318
410
|
}
|
|
411
|
+
// Explicit local/external bundle deployments predate activation metadata.
|
|
412
|
+
// Keep their native rule files reachable while they migrate; missing metadata
|
|
413
|
+
// in the shipped corpus still uses the on-demand fallback (#3463).
|
|
414
|
+
for (const rule of rules.filter(rule => !rule.tagged && !rule.behavior)) {
|
|
415
|
+
const source = opts.logicalInputPath ? opts.logicalInputPath(rule.file) : rule.file;
|
|
416
|
+
if (/(?:^|\/)agentic\/code\//.test(source.replaceAll('\\', '/'))) continue;
|
|
417
|
+
let content = stripTriggersFromContent(rule.content);
|
|
418
|
+
if (provider.name === 'cursor') content = provider.transformRule(rule.file, content, opts);
|
|
419
|
+
writeRule(rule, content);
|
|
420
|
+
}
|
|
319
421
|
// A legacy consolidated index can mention now-bound rules. Replace it with an
|
|
320
422
|
// activation-selected index when migration starts; pure legacy deployments keep it.
|
|
321
423
|
// Hermes's 20K context limit requires a fixed provider projection: only
|
|
@@ -325,8 +427,7 @@ function projectRules(opts, provider) {
|
|
|
325
427
|
const isCritical = rule => String(rule.enforcement || '').trim().toLowerCase() === 'critical';
|
|
326
428
|
const deferredKernels = provider.name === 'hermes'
|
|
327
429
|
? 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;
|
|
430
|
+
const indexRules = rules;
|
|
330
431
|
const index = renderRuleKernelIndex(indexRules, nativePaths);
|
|
331
432
|
if (tagged.length) {
|
|
332
433
|
for (const ext of ['.md', '.mdc']) {
|
|
@@ -335,6 +436,11 @@ function projectRules(opts, provider) {
|
|
|
335
436
|
}
|
|
336
437
|
if (index) put(path.join(rulesDir, `RULES-INDEX${extension}`), managed(provider.name === 'cursor' ? `---\nalwaysApply: true\n---\n${index}` : index, opts));
|
|
337
438
|
}
|
|
439
|
+
const legacyDemand = rules.filter(rule => !rule.tagged && !rule.behavior && rule.activation === 'on-demand');
|
|
440
|
+
if (legacyDemand.length) put(path.join(rulesDir, `RULES-ONDEMAND${extension}`), managed([
|
|
441
|
+
'# On-demand rules', '', 'These rules require explicit activation metadata. Fetch the applicable full rule before governed work.', '',
|
|
442
|
+
...legacyDemand.map(rule => `- \`${rule.name}\` — \`aiwg show rule ${rule.name} --body\``), '',
|
|
443
|
+
].join('\n'), opts));
|
|
338
444
|
fs.rmSync(path.join(rulesDir, '.aiwg-rules-budget.json'), { force: true });
|
|
339
445
|
const manifestPath = path.join(rulesDir, '.aiwg-manifest.json');
|
|
340
446
|
const manifest = fs.existsSync(manifestPath) ? JSON.parse(read(manifestPath)) : { managed: {} };
|
|
@@ -345,7 +451,7 @@ function projectRules(opts, provider) {
|
|
|
345
451
|
}
|
|
346
452
|
for (const name of Object.keys(manifest.managed)) if (!fs.existsSync(path.join(rulesDir, name))) delete manifest.managed[name];
|
|
347
453
|
put(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
348
|
-
if (!nativePaths) {
|
|
454
|
+
if (!nativePaths && opts.contextFiles !== false) {
|
|
349
455
|
// The conventional directory is not a loader: startup reaches the complete
|
|
350
456
|
// selected bodies through the provider's existing context owner.
|
|
351
457
|
const kernels = rules.filter(rule => rule.activation === 'kernel' &&
|
|
@@ -354,8 +460,9 @@ function projectRules(opts, provider) {
|
|
|
354
460
|
const routing = routes.length ? `## Invoked artifact definitions\n\nRead the complete definition before invoking its artifact:\n${routes.map(route => `- ${route}`).join('\n')}` : '';
|
|
355
461
|
let startupIndex = index;
|
|
356
462
|
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
|
|
463
|
+
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.' : '';
|
|
464
|
+
const coreSection = cores.length ? `${CORE_RULES_START}\n## Core rules\n\n${cores.map(rule => rule.coreBody).join('\n\n')}\n${CORE_RULES_END}` : '';
|
|
465
|
+
const renderBody = () => [coreSection, ...kernels.map(rule => `## Rule: ${rule.name}\n\n${rule.body.trim()}`), fetchKernels, startupIndex, startupRouting].filter(Boolean).join('\n\n');
|
|
359
466
|
const dest = path.join(opts.target, 'AGENTS.md');
|
|
360
467
|
if (provider.name === 'hermes' && block(read(dest), renderBody()).length > 19_000) {
|
|
361
468
|
// The complete summary/path index and definition routes can themselves
|
|
@@ -379,17 +486,22 @@ function projectRules(opts, provider) {
|
|
|
379
486
|
if (!body) {
|
|
380
487
|
// No activation-selected startup contract: retain legacy context owners
|
|
381
488
|
// 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));
|
|
489
|
+
const contexts = ['AIWG.md', 'AGENTS.md', 'WARP.md', '.hermes.md', inlineStartup[provider.name]].filter(Boolean).map(name => path.join(opts.target, name));
|
|
383
490
|
if (opts.activeWorkspace) contexts.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
|
|
384
491
|
for (const file of contexts) {
|
|
385
492
|
if (!fs.existsSync(file)) continue;
|
|
386
493
|
const content = read(file);
|
|
387
494
|
const clean = block(content, '');
|
|
388
|
-
if (clean !== content)
|
|
495
|
+
if (clean !== content) writeContextFileSync(file, clean);
|
|
389
496
|
}
|
|
390
497
|
return;
|
|
391
498
|
}
|
|
392
|
-
if (provider.name
|
|
499
|
+
if (inlineStartup[provider.name]) {
|
|
500
|
+
// Retire the B1 indirect startup block when migrating to the actual loader.
|
|
501
|
+
const former = path.join(opts.target, 'AIWG.md');
|
|
502
|
+
if (fs.existsSync(former)) writeContextFileSync(former, block(read(former), ''));
|
|
503
|
+
putStartup(path.join(opts.target, inlineStartup[provider.name]), body);
|
|
504
|
+
} else if (provider.name === 'hermes') {
|
|
393
505
|
const dest = path.join(opts.target, 'AGENTS.md');
|
|
394
506
|
putStartup(dest, body);
|
|
395
507
|
putStartup(path.join(opts.target, '.hermes.md'), 'Read and follow `AGENTS.md` in this directory before every task and subagent task.');
|
|
@@ -407,14 +519,19 @@ function projectRules(opts, provider) {
|
|
|
407
519
|
|
|
408
520
|
/** External helpers also project skills/flows. Inject into their body, never descriptions. */
|
|
409
521
|
function finishBindings(opts, provider) {
|
|
410
|
-
const roots = [opts.target, opts.deploymentHome,
|
|
522
|
+
const roots = [opts.target, opts.deploymentHome,
|
|
523
|
+
...[opts.kernelSkillsPath, opts.activeWorkspace, ...Object.values(provider.paths)].filter(p => p && path.isAbsolute(p))];
|
|
411
524
|
const files = [...new Set(roots.flatMap(walk))];
|
|
525
|
+
const indexRel = provider.name === 'copilot' ? '.github/RULES-BOUND.md' : `.${provider.name}/RULES-BOUND.md`;
|
|
526
|
+
const indexFile = path.join(opts.target, indexRel);
|
|
527
|
+
const anchor = target => `${target.type}-${target.name}`.toLowerCase().replace(/[^a-z0-9_-]/g, '-');
|
|
528
|
+
const sections = new Map();
|
|
412
529
|
const targetsBySource = new Map();
|
|
413
530
|
for (const target of opts.ruleDelivery.targets) {
|
|
414
531
|
targetsBySource.set(target.source, [...(targetsBySource.get(target.source) || []), target]);
|
|
415
532
|
}
|
|
416
533
|
for (const file of files.filter(file => /\.(md|toml)$/.test(file))) {
|
|
417
|
-
if (!/(?:[/\\](?:skills
|
|
534
|
+
if (path.basename(file) !== 'SKILL.md' && !/(?:[/\\](?:skills?|agents?|droids|commands?|prompts|modes?)[/\\])/.test(file)) continue;
|
|
418
535
|
if (/[/\\]rules[/\\]/.test(file) || /[/\\]\.aiwg[/\\](?!skills[/\\])/.test(file.replace(opts.target, ''))) continue;
|
|
419
536
|
let content = read(file);
|
|
420
537
|
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 +543,30 @@ function finishBindings(opts, provider) {
|
|
|
426
543
|
const flowSource = opts.flowSkillSources?.get(file);
|
|
427
544
|
const targets = [...(flowSource ? targetsBySource.get(flowSource) || [] : []),
|
|
428
545
|
...(source ? targetsBySource.get(path.resolve(source)) || [] : opts.ruleDelivery.targets.filter(target =>
|
|
429
|
-
(skill ? target.type !== 'agent' :
|
|
546
|
+
(skill ? target.type !== 'agent' : /[/\\](?:agents?|droids|modes?)[/\\]/.test(file) ? target.type === 'agent' : target.type === 'flow') &&
|
|
430
547
|
[target.name, path.basename(target.source, '.md'), path.basename(path.dirname(target.source))].includes(name)))];
|
|
431
548
|
const rules = targets.flatMap(target => opts.ruleDelivery.bindings.get(target.source) || []);
|
|
432
549
|
if (!rules.length && !content.includes('<!-- AIWG:bound-rules:start -->')) continue;
|
|
433
|
-
|
|
434
|
-
|
|
550
|
+
const pointer = body => {
|
|
551
|
+
const clean = replaceBoundRulesBlock(body, []);
|
|
552
|
+
if (!rules.length) return clean;
|
|
553
|
+
const identities = [...new Map(targets.map(target => [`${target.type} ${target.name}`, target])).values()];
|
|
554
|
+
for (const target of identities) {
|
|
555
|
+
const bound = opts.ruleDelivery.bindings.get(target.source) || [];
|
|
556
|
+
const unique = [...new Map(bound.map(rule => [rule.name, rule])).values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
557
|
+
if (unique.length) sections.set(anchor(target), `## ${anchor(target)}\n\n` + unique.map(rule =>
|
|
558
|
+
`- ${rule.name} — ${rule.summary} Fetch and follow: \`aiwg show rule ${rule.name} --body\`.`).join('\n'));
|
|
559
|
+
}
|
|
560
|
+
// Skills must carry exact fetch commands themselves: a section pointer
|
|
561
|
+
// alone loses the invocation contract in clean installs (#3473).
|
|
562
|
+
const fetch = skill ? [...new Map(rules.map(rule => [rule.name, rule])).values()]
|
|
563
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
564
|
+
.map(rule => `${rule.summary}\nFetch and follow before governed work: \`aiwg show rule ${rule.name} --body\`.`).join('\n') : '';
|
|
565
|
+
return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\nRead and follow before governed work: ${identities.map(target =>
|
|
566
|
+
`\`${indexRel}#${anchor(target)}\``).join('; ')}.\n${fetch ? fetch + '\n' : ''}<!-- AIWG:bound-rules:end -->\n`;
|
|
567
|
+
};
|
|
568
|
+
if (file.endsWith('.toml')) content = content.replace(/^developer_instructions = (".*")$/m, (_, value) => `developer_instructions = ${JSON.stringify(pointer(JSON.parse(value)))}`);
|
|
569
|
+
else content = pointer(content);
|
|
435
570
|
put(file, content);
|
|
436
571
|
// Keep deployment hashes honest after projection by external helpers.
|
|
437
572
|
if (sidecar?.managed?.[path.basename(file)]) {
|
|
@@ -439,6 +574,10 @@ function finishBindings(opts, provider) {
|
|
|
439
574
|
put(sidecarPath, `${JSON.stringify(sidecar, null, 2)}\n`);
|
|
440
575
|
}
|
|
441
576
|
}
|
|
577
|
+
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' +
|
|
578
|
+
[...sections.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([, section]) => section).join('\n\n') + '\n', opts));
|
|
579
|
+
else if (/aiwg:managed/.test(read(indexFile))) fs.rmSync(indexFile);
|
|
580
|
+
|
|
442
581
|
}
|
|
443
582
|
|
|
444
583
|
/** Bindings annotate the selected deployment; they do not select standard skills
|
|
@@ -490,7 +629,8 @@ function projectBoundFlowSkills(opts, provider, selected) {
|
|
|
490
629
|
if (!fs.existsSync(file) || receipt?.flowSource === entry.source) {
|
|
491
630
|
const body = read(entry.source);
|
|
492
631
|
const markdown = entry.source.endsWith('.md') ? body : `---\nname: ${name}\nnamespace: aiwg\ndescription: Invoke ${name}.\n---\n\n\`\`\`yaml\n${body}\n\`\`\`\n`;
|
|
493
|
-
const
|
|
632
|
+
const projected = provider.transformSkillMd ? provider.transformSkillMd(markdown, opts) : markdown;
|
|
633
|
+
const content = managed(replaceBoundRulesBlock(projected, opts.ruleDelivery.bindings.get(entry.source)), opts);
|
|
494
634
|
put(file, content);
|
|
495
635
|
put(path.join(dir, '.aiwg-managed'), 'true\n');
|
|
496
636
|
put(receiptFile, `${JSON.stringify({ flowSource: entry.source,
|
|
@@ -512,7 +652,7 @@ export function warpRuleContent(content) {
|
|
|
512
652
|
|
|
513
653
|
function startupFiles(opts, provider) {
|
|
514
654
|
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));
|
|
655
|
+
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
656
|
if (opts.activeWorkspace) paths.push(path.join(opts.activeWorkspace, 'AGENTS.md'));
|
|
517
657
|
if (['claude', 'cursor', 'openclaw'].includes(provider.name)) {
|
|
518
658
|
const dir = path.isAbsolute(provider.paths.rules) ? provider.paths.rules : path.join(opts.target, provider.paths.rules);
|
|
@@ -524,10 +664,11 @@ function startupFiles(opts, provider) {
|
|
|
524
664
|
const parsed = parseRuleFrontmatter(content);
|
|
525
665
|
if (parsed.data?.paths || parsed.data?.globs) content = content.slice(0, content.length - parsed.body.length);
|
|
526
666
|
}
|
|
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')
|
|
667
|
+
// WARP.md and Windsurf's AGENTS.md aggregate agents (and Warp's commands)
|
|
668
|
+
// as well; only their rule content (managed startup blocks, legacy inline
|
|
669
|
+
// rules, and on-demand lists) counts toward the rules startup budget.
|
|
670
|
+
if ((provider.name === 'warp' && path.basename(file) === 'WARP.md')
|
|
671
|
+
|| (provider.name === 'windsurf' && file === path.join(opts.target, 'AGENTS.md'))) content = warpRuleContent(content);
|
|
531
672
|
return { path: file, content };
|
|
532
673
|
});
|
|
533
674
|
}
|
|
@@ -562,17 +703,23 @@ function capturePreimage(file, ancestors = new Set()) {
|
|
|
562
703
|
}
|
|
563
704
|
// cpSync materializes links; compare its output with the recursively resolved
|
|
564
705
|
// content while retaining the original link-shaped preimage for commit drift.
|
|
565
|
-
function materializeInput(from, to, ancestors = new Set()) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
706
|
+
function materializeInput(from, to, ancestors = new Set(), parentReady = false) {
|
|
707
|
+
let stat;
|
|
708
|
+
try { stat = fs.statSync(from); } catch (error) {
|
|
709
|
+
if (error.code === 'ENOENT') return;
|
|
710
|
+
throw error;
|
|
711
|
+
}
|
|
570
712
|
if (stat.isDirectory()) {
|
|
713
|
+
// Only directories can lead back to an ancestor. Resolve them once and
|
|
714
|
+
// share the immutable ancestry set with their children, including aliases.
|
|
715
|
+
const real = fs.realpathSync(from);
|
|
716
|
+
if (ancestors.has(real)) throw new Error(`Cyclic deployment input: ${from}`);
|
|
717
|
+
const children = new Set([...ancestors, real]);
|
|
571
718
|
fs.mkdirSync(to, { recursive: true });
|
|
572
|
-
for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name),
|
|
719
|
+
for (const name of fs.readdirSync(from)) materializeInput(path.join(from, name), path.join(to, name), children, true);
|
|
573
720
|
fs.chmodSync(to, stat.mode & 0o7777);
|
|
574
721
|
} else if (stat.isFile()) {
|
|
575
|
-
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
722
|
+
if (!parentReady) fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
576
723
|
fs.copyFileSync(from, to); fs.chmodSync(to, stat.mode & 0o7777);
|
|
577
724
|
} else throw new Error(`Unsupported deployment input: ${from}`);
|
|
578
725
|
}
|
|
@@ -774,12 +921,12 @@ function expectedAnchorPaths(paths) {
|
|
|
774
921
|
/** Adopt a detached worker plan into the coordinator's process-local lease. */
|
|
775
922
|
export function adoptRuleDeploymentPlan(detached, { scratch, target, inputPool, cleanup }) {
|
|
776
923
|
const invalid = () => { throw new Error('Invalid transferred rule deployment plan'); };
|
|
777
|
-
if (!exactObjectKeys(detached, ['version', 'lease', 'temp', 'target', 'stage', 'rootNames', 'workspaceDirs', 'preimages', 'poolSeal', 'anchors', 'snapshotRoot', 'corpusRoot', 'sourceMappings', 'staged', 'report', 'mappings']) ||
|
|
924
|
+
if (!exactObjectKeys(detached, ['version', 'lease', 'temp', 'target', 'stage', 'rootNames', 'workspaceDirs', 'preimages', 'poolSeal', 'anchors', 'snapshotRoot', 'corpusRoot', 'sourceMappings', 'staged', 'report', 'mappings', 'contextWrites']) ||
|
|
778
925
|
detached.version !== 1 || typeof detached.lease !== 'string' || !(detached.poolSeal === null || typeof detached.poolSeal === 'string') ||
|
|
779
926
|
!detached.report || typeof detached.report !== 'object' || Array.isArray(detached.report) || typeof scratch !== 'string' || typeof target !== 'string' || typeof cleanup !== 'function') invalid();
|
|
780
927
|
const root = path.resolve(scratch), expectedTarget = path.resolve(target);
|
|
781
928
|
if (path.resolve(detached.temp || '') !== root || path.resolve(detached.stage || '') !== path.join(root, 'project') || path.resolve(detached.target || '') !== expectedTarget) invalid();
|
|
782
|
-
if (!Array.isArray(detached.staged) || !Array.isArray(detached.mappings) || !Array.isArray(detached.rootNames) || !Array.isArray(detached.preimages) ||
|
|
929
|
+
if (!Array.isArray(detached.contextWrites) || detached.contextWrites.some(file => typeof file !== 'string' || !within(root, file)) || !Array.isArray(detached.staged) || !Array.isArray(detached.mappings) || !Array.isArray(detached.rootNames) || !Array.isArray(detached.preimages) ||
|
|
783
930
|
!Array.isArray(detached.workspaceDirs) || !Array.isArray(detached.anchors) || !Array.isArray(detached.sourceMappings) ||
|
|
784
931
|
typeof detached.snapshotRoot !== 'string' || !path.isAbsolute(detached.snapshotRoot) || typeof detached.corpusRoot !== 'string' || !path.isAbsolute(detached.corpusRoot)) invalid();
|
|
785
932
|
if (detached.rootNames.some(name => !safeRelative(expectedTarget, name))) invalid();
|
|
@@ -828,6 +975,9 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
828
975
|
const previousText = fs.existsSync(manifestFile) ? read(manifestFile) : '';
|
|
829
976
|
const previous = previousText ? JSON.parse(previousText) : {};
|
|
830
977
|
const corpusRoot = resolveAiwgRoot(original.srcRoot) || original.srcRoot;
|
|
978
|
+
if (!original.deployVersion || /unknown/i.test(original.deployVersion)) {
|
|
979
|
+
original = { ...original, deployVersion: resolveDeployVersion(corpusRoot, original.deployVersion) };
|
|
980
|
+
}
|
|
831
981
|
const sourceTree = fs.existsSync(path.join(corpusRoot, 'agentic', 'code'))
|
|
832
982
|
? path.join(corpusRoot, 'agentic', 'code') : original.srcRoot;
|
|
833
983
|
const pool = original.inputPool && inputPools.get(original.inputPool);
|
|
@@ -846,7 +996,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
846
996
|
const selectedRules = collectDeploymentRuleSources({ ...original, previousRuleSources: previous.ruleSources || [] });
|
|
847
997
|
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
998
|
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)),
|
|
999
|
+
...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...selectedRules, ...selectedRules.map(file => path.dirname(file)),
|
|
850
1000
|
userConfigFile('aiwg.config'), process.env.AIWG_USER_REGISTRY_PATH || userConfigFile('installed.json'),
|
|
851
1001
|
...(original.ruleSources || []), ...(previous.ruleSources || []), ...(original.bindingEntries || []).map(entry => entry.path)])];
|
|
852
1002
|
let manifestState = initialPreimages.find(item => item.path === path.resolve(manifestFile)).state;
|
|
@@ -866,6 +1016,10 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
866
1016
|
const removeTemp = () => { removeStage(); if (pool) releaseInputPool(pool); };
|
|
867
1017
|
const lease = randomUUID();
|
|
868
1018
|
stagedCleanups.set(lease, { temp, cleanup: removeTemp, detach: () => { if (pool) releaseInputPool(pool); }, pool, seal: null, transferable });
|
|
1019
|
+
const priorContextStage = process.env[CONTEXT_STAGE_ENV];
|
|
1020
|
+
const contextStageLog = path.join(temp, 'context-writes.jsonl');
|
|
1021
|
+
fs.writeFileSync(contextStageLog, '');
|
|
1022
|
+
process.env[CONTEXT_STAGE_ENV] = contextStageLog;
|
|
869
1023
|
const mappings = [];
|
|
870
1024
|
const preimages = [...initialPreimages];
|
|
871
1025
|
let retained = false;
|
|
@@ -928,7 +1082,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
928
1082
|
if (JSON.stringify(capturePreimage(to)) !== JSON.stringify(materializedPreimage(expected || { kind: 'missing' }))) throw new Error(`Project-local input changed while preparing snapshot: ${from}`);
|
|
929
1083
|
sourceMappings.push({ from, to });
|
|
930
1084
|
}
|
|
931
|
-
const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.ruleSources || []), ...(previous.ruleSources || []),
|
|
1085
|
+
const externalInputs = [...new Set([...(path.resolve(original.srcRoot) === path.resolve(corpusRoot) ? [] : [original.srcRoot]), ...(original.additionalSources || []), ...(original.ruleSources || []), ...(previous.ruleSources || []),
|
|
932
1086
|
...(original.bindingEntries || []).map(entry => entry.path)].map(file => path.resolve(file)))];
|
|
933
1087
|
for (const [index, from] of externalInputs.entries()) {
|
|
934
1088
|
if (sourceMappings.some(item => from === item.from || from.startsWith(`${item.from}${path.sep}`))) continue;
|
|
@@ -963,6 +1117,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
963
1117
|
return result.split(snapshotRoot).join(corpusRoot);
|
|
964
1118
|
};
|
|
965
1119
|
original = { ...original, config: effectiveConfig, srcRoot: mapSource(original.srcRoot), pinConfigs, mapInputPath: mapSource, logicalInputPath,
|
|
1120
|
+
...(original.additionalSources ? { additionalSources: original.additionalSources.map(mapSource) } : {}),
|
|
966
1121
|
...(original.ruleSources ? { ruleSources: original.ruleSources.map(mapSource) } : {}),
|
|
967
1122
|
...(original.bindingEntries ? { bindingEntries: original.bindingEntries.map(entry => ({ ...entry, path: mapSource(entry.path) })) } : {}) };
|
|
968
1123
|
const target = path.resolve(original.target);
|
|
@@ -979,10 +1134,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
979
1134
|
mappings.push({ from, to, before });
|
|
980
1135
|
};
|
|
981
1136
|
// Mount directories independently so only provider-owned surfaces are promoted.
|
|
982
|
-
|
|
1137
|
+
const relativeRoots = Object.values(provider.paths).filter(dir => dir && !path.isAbsolute(dir))
|
|
1138
|
+
.map(dir => dir.split(/[\\/]/)[0]).filter(dir => dir && dir !== '.' && !path.extname(dir));
|
|
1139
|
+
for (const dir of new Set([`.${provider.name}`, '.agents', '.aiwg/skills', ...relativeRoots])) mount(path.join(target, dir), path.join(stage, dir));
|
|
983
1140
|
fs.mkdirSync(path.join(stage, '.aiwg'), { recursive: true });
|
|
984
1141
|
if (fs.existsSync(projectSnapshot)) materializeInput(projectSnapshot, path.join(stage, '.aiwg', 'aiwg.config'));
|
|
985
1142
|
const rootNames = ['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md', '.aiwg/frameworks/registry.json'];
|
|
1143
|
+
if (provider.name === 'windsurf') rootNames.push('.windsurfrules');
|
|
986
1144
|
for (const framework of getFrameworksForMode(original.srcRoot, original.mode)) {
|
|
987
1145
|
for (const entry of framework.manifest?.memory?.creates || []) {
|
|
988
1146
|
if (typeof entry?.path !== 'string' || !entry.path.startsWith('.aiwg/') || !path.extname(entry.path)) continue;
|
|
@@ -1092,11 +1250,11 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1092
1250
|
preimages.push(...snapshotPreimages([...delivery.rules.map(rule => rule.file), ...delivery.targets.map(entry => entry.source), path.join(target, '.aiwg', 'aiwg.config')]));
|
|
1093
1251
|
const opts = { ...original, target: stage, dryRun: false, ruleDelivery: delivery,
|
|
1094
1252
|
kernelSkillsPath: kernel, deploymentHome: home, activeWorkspace, hermesLegacySkills, legacyCodexSkills, staging: true };
|
|
1095
|
-
await
|
|
1253
|
+
await deploySelectedSurface(opts, provider, deploy);
|
|
1096
1254
|
if (!opts.commandsOnly && !opts.skillsOnly && !opts.rulesOnly && opts.deployBehaviors !== false) {
|
|
1097
|
-
deployEmulatedBehaviors(
|
|
1255
|
+
deployEmulatedBehaviors(delivery.rules.filter(rule => rule.behavior).map(rule => path.dirname(rule.file)), provider.name, stage, opts);
|
|
1098
1256
|
}
|
|
1099
|
-
const selectedRoots = [original.srcRoot, ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
|
|
1257
|
+
const selectedRoots = [original.srcRoot, ...(original.additionalSources || []), ...getFrameworksForMode(original.srcRoot, original.mode).map(framework => framework.path), ...discoverAddons(original.srcRoot).map(addon => addon.path)];
|
|
1100
1258
|
const selected = entry => selectedRoots.some(root => /^(agents|skills|commands|flows|workflows)[/\\]/.test(path.relative(root, entry.source)));
|
|
1101
1259
|
opts.definitionRoutes = [];
|
|
1102
1260
|
if (provider.name === 'hermes' && !opts.kernelOnly && !opts.skillsOnly && !opts.commandsOnly && !opts.rulesOnly) {
|
|
@@ -1104,7 +1262,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1104
1262
|
const selectedAgents = new Set(filterAgentFiles(agents.map(target => target.source), opts));
|
|
1105
1263
|
for (const target of agents.filter(target => selectedAgents.has(target.source))) {
|
|
1106
1264
|
const source = target.source;
|
|
1107
|
-
put(path.join(stage, '.hermes', 'agents', path.basename(source)), replaceBoundRulesBlock(read(source), delivery.bindings.get(source)));
|
|
1265
|
+
put(path.join(stage, '.hermes', 'agents', path.basename(source)), managed(replaceBoundRulesBlock(read(source), delivery.bindings.get(source)), opts));
|
|
1108
1266
|
opts.definitionRoutes.push(`${target.name}: .hermes/agents/${path.basename(source)}`);
|
|
1109
1267
|
}
|
|
1110
1268
|
}
|
|
@@ -1115,8 +1273,10 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1115
1273
|
if (fs.existsSync(dest)) opts.definitionRoutes.push(`${entry.name}: .warp/agents/${path.basename(entry.source)}`);
|
|
1116
1274
|
}
|
|
1117
1275
|
}
|
|
1118
|
-
if (
|
|
1119
|
-
|
|
1276
|
+
if (!opts.deferRuleProjection) {
|
|
1277
|
+
if (opts.kernelOnly || opts.deployRules || opts.rulesOnly || (!opts.skillsOnly && !opts.commandsOnly && (inlineStartup[provider.name] || ['hermes', 'warp'].includes(provider.name)))) projectRules(opts, provider);
|
|
1278
|
+
finishBindings(opts, provider);
|
|
1279
|
+
}
|
|
1120
1280
|
// Normalize all generated path wrappers before counting or promoting bytes.
|
|
1121
1281
|
const normalize = content => {
|
|
1122
1282
|
for (const mapping of [...mappings].sort((a, b) => b.to.length - a.to.length)) content = content.split(mapping.to).join(mapping.from);
|
|
@@ -1150,6 +1310,13 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1150
1310
|
const delegated = (from, rel) => mappings.some(mapping => mapping.from !== from
|
|
1151
1311
|
&& mapping.from.startsWith(`${from}${path.sep}`)
|
|
1152
1312
|
&& (path.join(from, rel) === mapping.from || path.join(from, rel).startsWith(`${mapping.from}${path.sep}`)));
|
|
1313
|
+
if (original.contextFiles === false) {
|
|
1314
|
+
// Keep context inputs in the snapshot for validation, but exclude them
|
|
1315
|
+
// from both dry-run output and promotion (including unchanged rewrites).
|
|
1316
|
+
for (let i = rootNames.length - 1; i >= 0; i--) {
|
|
1317
|
+
if (['AGENTS.md', 'AIWG.md', 'WORKSPACE.md', 'CLAUDE.md', 'WARP.md', '.hermes.md', '.aiwg/AIWG.md'].includes(rootNames[i])) rootNames.splice(i, 1);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1153
1320
|
if (pool) verifyInputPool(pool);
|
|
1154
1321
|
if (original.previewReport) return { report: guarded.report, noop: true };
|
|
1155
1322
|
if (original.dryRun) {
|
|
@@ -1180,7 +1347,8 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1180
1347
|
}
|
|
1181
1348
|
};
|
|
1182
1349
|
collectDirs(path.join(stage, '.aiwg'), '.aiwg');
|
|
1183
|
-
const
|
|
1350
|
+
const contextWrites = [...new Set(fs.readFileSync(contextStageLog, 'utf8').split('\n').filter(Boolean).map(line => JSON.parse(line)))];
|
|
1351
|
+
const plan = { version: 1, lease, temp, target, stage, contextWrites, rootNames, workspaceDirs, preimages, poolSeal: pool?.inventorySeal || null,
|
|
1184
1352
|
anchors: captureAnchors([target, ...mappings.map(mapping => mapping.from), ...workspaceDirs.map(dir => path.join(target, dir))]),
|
|
1185
1353
|
snapshotRoot, corpusRoot, sourceMappings, staged: snapshotPreimages([stage, ...mappings.map(mapping => mapping.to)]), report: guarded.report,
|
|
1186
1354
|
mappings: mappings.map(({ from, to, before }) => ({ from, to, before: [...before] })) };
|
|
@@ -1188,6 +1356,7 @@ export async function prepareRuleDeployment(original, provider, deploy) {
|
|
|
1188
1356
|
retained = true;
|
|
1189
1357
|
return plan;
|
|
1190
1358
|
} finally {
|
|
1359
|
+
if (priorContextStage === undefined) delete process.env[CONTEXT_STAGE_ENV]; else process.env[CONTEXT_STAGE_ENV] = priorContextStage;
|
|
1191
1360
|
Object.assign(provider.paths, originals);
|
|
1192
1361
|
if (priorAiwgRoot === undefined) delete process.env.AIWG_ROOT; else process.env.AIWG_ROOT = priorAiwgRoot;
|
|
1193
1362
|
if (priorRegistry === undefined) delete process.env.AIWG_USER_REGISTRY_PATH; else process.env.AIWG_USER_REGISTRY_PATH = priorRegistry;
|
|
@@ -1244,12 +1413,32 @@ export function commitRuleDeploymentPlan(plan) {
|
|
|
1244
1413
|
if (before.get(rel) === fileDigest(file)) continue;
|
|
1245
1414
|
const buffer = fs.readFileSync(file);
|
|
1246
1415
|
// Provider output includes binary skill resources; normalize text only.
|
|
1247
|
-
|
|
1416
|
+
let output = buffer.includes(0) ? buffer : Buffer.from(normalize(buffer.toString('utf8')));
|
|
1417
|
+
if (path.basename(file) === '.aiwg-manifest.json') {
|
|
1418
|
+
const receipt = JSON.parse(output.toString('utf8'));
|
|
1419
|
+
for (const [name, record] of Object.entries(receipt.managed || {})) {
|
|
1420
|
+
const recordedFile = path.resolve(path.dirname(file), name);
|
|
1421
|
+
if (!within(path.dirname(file), recordedFile) || !fs.existsSync(recordedFile)) continue;
|
|
1422
|
+
const recorded = fs.readFileSync(recordedFile);
|
|
1423
|
+
// Do not bless operator edits whose bytes already differ from the
|
|
1424
|
+
// ownership receipt. Only normalize hashes of pristine staged files.
|
|
1425
|
+
if (record.hash !== `sha256:${contentHash(recorded)}`) continue;
|
|
1426
|
+
const promoted = recorded.includes(0) ? recorded : Buffer.from(normalize(recorded.toString('utf8')));
|
|
1427
|
+
record.hash = `sha256:${contentHash(promoted)}`;
|
|
1428
|
+
}
|
|
1429
|
+
output = Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`);
|
|
1430
|
+
}
|
|
1248
1431
|
fs.mkdirSync(path.dirname(path.join(from, rel)), { recursive: true });
|
|
1249
|
-
|
|
1432
|
+
if (plan.contextWrites.includes(file)) writeContextFileSync(path.join(from, rel), output);
|
|
1433
|
+
else fs.writeFileSync(path.join(from, rel), output);
|
|
1250
1434
|
}
|
|
1251
1435
|
}
|
|
1252
|
-
for (const name of rootNames) if (fs.existsSync(path.join(stage, name)))
|
|
1436
|
+
for (const name of rootNames) if (fs.existsSync(path.join(stage, name))) {
|
|
1437
|
+
const output = normalize(read(path.join(stage, name)));
|
|
1438
|
+
if (output === read(path.join(target, name))) continue;
|
|
1439
|
+
const write = plan.contextWrites.includes(path.join(stage, name)) ? writeContextFileSync : put;
|
|
1440
|
+
write(path.join(target, name), output);
|
|
1441
|
+
}
|
|
1253
1442
|
return plan.report;
|
|
1254
1443
|
} finally {
|
|
1255
1444
|
disposeRuleDeploymentPlan(plan);
|
|
@@ -1258,6 +1447,13 @@ export function commitRuleDeploymentPlan(plan) {
|
|
|
1258
1447
|
|
|
1259
1448
|
/** Stage and commit serially for existing provider callers. */
|
|
1260
1449
|
export async function deployWithRuleDelivery(original, provider, deploy) {
|
|
1450
|
+
// Kernel source passes share the same selected rule set. Deploy their
|
|
1451
|
+
// skills directly; the final source projects rules and bindings across the
|
|
1452
|
+
// complete destination. Full passes retain staging for bound flow skills
|
|
1453
|
+
// and provider definitions, but defer rule presentation above.
|
|
1454
|
+
if (original.kernelOnly && original.deferRuleProjection && !globalThis[PREPARATION_HOOK]) {
|
|
1455
|
+
return deploySelectedSurface(original, provider, deploy);
|
|
1456
|
+
}
|
|
1261
1457
|
if (original.ruleDelivery) return deploy(original);
|
|
1262
1458
|
const hook = globalThis[PREPARATION_HOOK];
|
|
1263
1459
|
if (hook) {
|
|
@@ -1268,3 +1464,16 @@ export async function deployWithRuleDelivery(original, provider, deploy) {
|
|
|
1268
1464
|
}
|
|
1269
1465
|
return commitRuleDeploymentPlan(await prepareRuleDeployment(original, provider, deploy));
|
|
1270
1466
|
}
|
|
1467
|
+
|
|
1468
|
+
async function deploySelectedSurface(opts, provider, deploy) {
|
|
1469
|
+
if (!opts.kernelOnly || provider.name !== 'warp') return deploy(opts);
|
|
1470
|
+
// Warp's aggregate writer emits bulk definitions even in skills-only mode.
|
|
1471
|
+
// Both staged and deferred kernel passes use only its shared skill router.
|
|
1472
|
+
const skills = [...listSkillDirs(path.join(opts.srcRoot, 'skills')),
|
|
1473
|
+
...getFrameworksForMode(opts.srcRoot, opts.mode).flatMap(bundle => listSkillDirs(path.join(bundle.path, 'skills'))),
|
|
1474
|
+
...getAddonSkillDirs(opts.srcRoot)].filter(isKernelSkill);
|
|
1475
|
+
const standard = path.join(opts.target, provider.paths.skills);
|
|
1476
|
+
for (const dir of new Set([opts.kernelSkillsPath || provider.kernelSkillsPath, provider.paths.crossAgentSkills])) {
|
|
1477
|
+
if (dir) deploySkillsWithKernelRouting(skills, standard, path.isAbsolute(dir) ? dir : path.join(opts.target, dir), opts);
|
|
1478
|
+
}
|
|
1479
|
+
}
|