@aiwg/cli 2026.10.3 → 2026.10.5
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/README.md +2 -2
- package/agentic/code/providers/model-capabilities.v1.json +96 -4
- package/bin/aiwg.mjs +50 -27
- package/dist/src/agents/grounding/knowledge-base.js +54 -6
- package/dist/src/artifacts/browser-export.js +6 -0
- package/dist/src/artifacts/checksum-manifest.js +1 -0
- package/dist/src/artifacts/citation-parser.js +173 -92
- package/dist/src/artifacts/citation-table-schema.js +59 -0
- package/dist/src/artifacts/cli.js +69 -18
- package/dist/src/artifacts/corpus-tools/snapshot.js +1 -1
- package/dist/src/artifacts/corpus-tools/vision-extract.js +2 -0
- package/dist/src/artifacts/corpus-views/ref-parser.js +3 -1
- package/dist/src/artifacts/corpus-views/renderers.js +3 -3
- package/dist/src/artifacts/discovery-eval.js +6 -4
- package/dist/src/artifacts/fortemi-core-query-adapter.js +36 -3
- package/dist/src/artifacts/fortemi-core-sync.js +166 -40
- package/dist/src/artifacts/framework-capabilities.js +171 -0
- package/dist/src/artifacts/framework-index-paths.js +73 -0
- package/dist/src/artifacts/framework-source-digest.js +75 -0
- package/dist/src/artifacts/index-builder.js +286 -131
- package/dist/src/artifacts/index-files.js +132 -18
- package/dist/src/artifacts/index-preparation.js +130 -0
- package/dist/src/artifacts/index-refresh.js +591 -0
- package/dist/src/artifacts/index-worker.mjs +11 -0
- package/dist/src/artifacts/prebuilt-build-lock.js +47 -4
- package/dist/src/artifacts/prebuilt-deployment.js +46 -0
- package/dist/src/artifacts/prebuilt-relations.js +80 -0
- package/dist/src/artifacts/query-engine.js +410 -88
- package/dist/src/artifacts/relations-builder.js +168 -0
- package/dist/src/artifacts/relations.js +148 -0
- package/dist/src/artifacts/stats.js +5 -2
- package/dist/src/artifacts/types.js +30 -10
- package/dist/src/artifacts/watcher.js +1 -1
- package/dist/src/auth/credential-store.js +2 -1
- package/dist/src/catalog/cli.d.mts +5 -0
- package/dist/src/channel/manager.d.mts +20 -0
- package/dist/src/channel/manager.mjs +19 -2
- package/dist/src/cli/command-log.js +2 -3
- package/dist/src/cli/handlers/artifacts.js +3 -0
- package/dist/src/cli/handlers/auth.js +3 -0
- package/dist/src/cli/handlers/decision.js +19 -12
- package/dist/src/cli/handlers/diagnose.js +4 -3
- package/dist/src/cli/handlers/effect.js +4 -2
- package/dist/src/cli/handlers/evidence.js +3 -0
- package/dist/src/cli/handlers/help.js +1 -0
- package/dist/src/cli/handlers/index.js +3 -1
- package/dist/src/cli/handlers/init.js +4 -1
- package/dist/src/cli/handlers/lint.js +5 -0
- package/dist/src/cli/handlers/models.js +104 -8
- package/dist/src/cli/handlers/ralph-launcher.js +2 -0
- package/dist/src/cli/handlers/refresh.js +12 -0
- package/dist/src/cli/handlers/resource-versions.js +3 -0
- package/dist/src/cli/handlers/rules.js +195 -0
- package/dist/src/cli/handlers/run.js +4 -0
- package/dist/src/cli/handlers/runtime-info.js +1 -2
- package/dist/src/cli/handlers/script-runner.js +3 -2
- package/dist/src/cli/handlers/session.js +19 -9
- package/dist/src/cli/handlers/sessions.js +137 -43
- package/dist/src/cli/handlers/setup-manifest.js +39 -44
- package/dist/src/cli/handlers/setup.js +41 -0
- package/dist/src/cli/handlers/steward.js +11 -0
- package/dist/src/cli/handlers/subcommands.js +57 -2
- package/dist/src/cli/handlers/uhp.js +3 -0
- package/dist/src/cli/handlers/use.js +294 -55
- package/dist/src/cli/handlers/utilities.js +26 -10
- package/dist/src/cli/handlers/version.js +8 -0
- package/dist/src/cli/router.js +15 -1
- package/dist/src/cli/services/deployment-verification.js +19 -9
- package/dist/src/cli/services/provider-prepare-coordinator.js +24 -0
- package/dist/src/cli/skill-usage.js +2 -3
- package/dist/src/config/aiwg-config.js +87 -37
- package/dist/src/config/cli.js +111 -5
- package/dist/src/config/decision-config.js +106 -0
- package/dist/src/config/dispatch-thresholds-runtime.d.mts +8 -0
- package/dist/src/config/gitignore.js +50 -0
- package/dist/src/config/index-graph-overrides.js +36 -0
- package/dist/src/config/project-artifacts-health.d.mts +37 -0
- package/dist/src/config/project-artifacts-runtime.d.mts +16 -0
- package/dist/src/config/user-config-dir.d.mts +9 -0
- package/dist/src/config/user-config.js +2 -1
- package/dist/src/config/user-registry.js +23 -15
- package/dist/src/config/user-state.js +7 -0
- package/dist/src/decision/ask.js +203 -43
- package/dist/src/decision/context-live-qualification.js +2 -2
- package/dist/src/decision/driver.js +13 -8
- package/dist/src/decision/ensemble/aggregate.js +23 -10
- package/dist/src/decision/ensemble-study/multifact-study.js +6 -2
- package/dist/src/decision/entry.js +2 -15
- package/dist/src/decision/graph-live-qualification.js +1 -1
- package/dist/src/decision/graph-run.js +9 -0
- package/dist/src/decision/heldout/collector.js +5 -13
- package/dist/src/decision/heldout/contract.js +30 -7
- package/dist/src/decision/jev-credentials.js +15 -1
- package/dist/src/decision/ledger.js +131 -28
- package/dist/src/decision/offload-activation.js +137 -0
- package/dist/src/decision/offload.js +550 -0
- package/dist/src/decision/qualification/load.js +4 -6
- package/dist/src/decision/savings.js +28 -0
- package/dist/src/effects/types.js +5 -1
- package/dist/src/effects/verifiers/git.js +2 -1
- package/dist/src/effects/verifiers/tracker.js +26 -14
- package/dist/src/extensions/commands/definitions.js +19 -0
- package/dist/src/extensions/project-local-remove.js +36 -1
- package/dist/src/gates/providers/loader.js +61 -17
- package/dist/src/installation/manager-command.d.mts +18 -0
- package/dist/src/installation/manager-command.mjs +14 -2
- package/dist/src/installation/manager.d.mts +49 -0
- package/dist/src/installation/manager.mjs +78 -3
- package/dist/src/issues/cli.js +4 -4
- package/dist/src/issues/local.js +24 -5
- package/dist/src/jobs/runner.js +1 -1
- package/dist/src/lint/cli.js +27 -1
- package/dist/src/lint/loader.js +2 -1
- package/dist/src/lint/reporters.js +25 -1
- package/dist/src/lint/rule-activation.js +43 -0
- package/dist/src/lint/runner.js +137 -62
- package/dist/src/mcp/adapters/codex-runtime.js +7 -1
- package/dist/src/mcp/claude-install.d.mts +4 -0
- package/dist/src/mcp/claude-install.mjs +41 -0
- package/dist/src/mcp/cli.d.mts +1 -0
- package/dist/src/mcp/cli.mjs +36 -11
- package/dist/src/mcp/config-file.d.mts +28 -0
- package/dist/src/mcp/config-layers.d.mts +36 -0
- package/dist/src/mcp/credentials.d.mts +51 -0
- package/dist/src/mcp/grok-build-config.d.mts +6 -0
- package/dist/src/mcp/grok-build-config.mjs +1 -1
- package/dist/src/mcp/helpers.mjs +3 -2
- package/dist/src/mcp/muse-native-config.d.mts +13 -0
- package/dist/src/mcp/muse-native-config.mjs +1 -0
- package/dist/src/mcp/omp-config.d.mts +4 -0
- package/dist/src/mcp/registry.js +3 -93
- package/dist/src/mcp/registry.mjs +3 -87
- package/dist/src/mcp/server-config.d.mts +3 -0
- package/dist/src/mcp/server-config.mjs +89 -0
- package/dist/src/mcp/server.mjs +37 -375
- package/dist/src/mcp/test-simple.mjs +2 -1
- package/dist/src/mcp/toml-editor.d.mts +4 -0
- package/dist/src/mcp/toml-string.d.mts +1 -0
- package/dist/src/mcp/toml-strip-mcp.d.mts +1 -0
- package/dist/src/mcp/tool-filters.d.mts +46 -0
- package/dist/src/mcp/tools/decision.mjs +12 -11
- package/dist/src/mcp/tools/discovery.mjs +62 -55
- package/dist/src/mcp/tools/orchestration.mjs +12 -2
- package/dist/src/mcp/tools/worker-invoke.mjs +1340 -23
- package/dist/src/mcp/tools/worker-shell-control.mjs +52 -21
- package/dist/src/mcp/tools/worker-stdin-runtime.mjs +3 -3
- package/dist/src/mission-protocol/codecs.js +8 -0
- package/dist/src/models/config-loader.js +7 -2
- package/dist/src/models/model-capabilities.v1.json +96 -4
- package/dist/src/models/model-discovery.js +76 -11
- package/dist/src/models/provider-policy.js +101 -7
- package/dist/src/models/qualification-digest.d.mts +2 -0
- package/dist/src/models/resolver.js +12 -10
- package/dist/src/models/worker-direct-readiness.js +65 -44
- package/dist/src/models/worker-dispatch-offer.js +3 -3
- package/dist/src/models/worker-health-readiness.js +16 -3
- package/dist/src/models/worker-installation.d.mts +39 -0
- package/dist/src/models/worker-installation.mjs +135 -28
- package/dist/src/process/bounded-child-process.d.mts +84 -0
- package/dist/src/process/bounded-child-process.mjs +43 -0
- package/dist/src/process/cli-command.d.mts +16 -0
- package/dist/src/process/signal-child.d.mts +6 -0
- package/dist/src/process/signal-child.mjs +7 -0
- package/dist/src/providers/deployed-skill-roots.js +20 -0
- package/dist/src/providers/grok-build-acp.js +3 -2
- package/dist/src/providers/grok-build-headless.js +12 -4
- package/dist/src/providers/grok-build-inspect.js +1 -0
- package/dist/src/providers/omp-agent.d.mts +8 -0
- package/dist/src/providers/omp-diagnostics.d.mts +1 -0
- package/dist/src/providers/omp-paths.d.mts +13 -0
- package/dist/src/providers/skill-ownership.d.mts +9 -0
- package/dist/src/providers/skill-ownership.mjs +88 -0
- package/dist/src/rules/activation.d.mts +21 -0
- package/dist/src/rules/activation.mjs +62 -0
- package/dist/src/rules/inline-budget.d.mts +26 -0
- package/dist/src/rules/inline-budget.mjs +66 -0
- package/dist/src/rules/inventory.js +129 -0
- package/dist/src/rules/pin-state.js +73 -0
- package/dist/src/rules/pins.d.mts +14 -0
- package/dist/src/rules/pins.mjs +112 -0
- package/dist/src/rules/rule-frontmatter.schema.json +197 -0
- package/dist/src/serve/mission-conductor.js +38 -12
- package/dist/src/serve/worker-invocation.js +30 -10
- package/dist/src/serve/worker-stdin-frame.d.mts +10 -0
- package/dist/src/sessions/adapter-registry.js +23 -19
- package/dist/src/sessions/adapters/claude.js +39 -54
- package/dist/src/sessions/adapters/codex.js +95 -67
- package/dist/src/sessions/adapters/copilot.js +4 -4
- package/dist/src/sessions/adapters/cursor.js +40 -57
- package/dist/src/sessions/adapters/deepseek-harness.js +15 -12
- package/dist/src/sessions/adapters/factory.js +31 -27
- package/dist/src/sessions/adapters/generic.js +10 -10
- package/dist/src/sessions/adapters/grok-build.js +11 -11
- package/dist/src/sessions/adapters/grokbot.js +24 -13
- package/dist/src/sessions/adapters/hermes.js +27 -29
- package/dist/src/sessions/adapters/muse-workspace-scan.js +31 -11
- package/dist/src/sessions/adapters/muse.js +150 -93
- package/dist/src/sessions/adapters/omp.js +28 -14
- package/dist/src/sessions/adapters/openclaw.js +89 -79
- package/dist/src/sessions/adapters/opencode.js +27 -24
- package/dist/src/sessions/adapters/openhuman.js +18 -60
- package/dist/src/sessions/adapters/pi.js +29 -14
- package/dist/src/sessions/adapters/warp.js +3 -3
- package/dist/src/sessions/adapters/windsurf.js +19 -48
- package/dist/src/sessions/batch-contracts.js +16 -8
- package/dist/src/sessions/batch-import.js +252 -12
- package/dist/src/sessions/contracts.js +50 -3
- package/dist/src/sessions/coverage-preflight.js +27 -2
- package/dist/src/sessions/export-plan.js +4 -3
- package/dist/src/sessions/import-exclusions.js +72 -0
- package/dist/src/sessions/import-lease.js +34 -18
- package/dist/src/sessions/import-limits.js +37 -0
- package/dist/src/sessions/importer.js +60 -38
- package/dist/src/sessions/lifecycle.js +45 -0
- package/dist/src/sessions/policy.js +145 -26
- package/dist/src/sessions/readers.js +106 -53
- package/dist/src/sessions/refresh.js +105 -0
- package/dist/src/sessions/repository.js +151 -54
- package/dist/src/sessions/workspace-discovery.js +292 -60
- package/dist/src/sessions/workspace-matching.js +107 -0
- package/dist/src/skills/importer.js +1 -0
- package/dist/src/smiths/context-pipeline/aiwg-md.js +11 -0
- package/dist/src/smiths/context-pipeline/claude-hook.js +3 -26
- package/dist/src/smiths/context-pipeline/finalization.js +6 -5
- package/dist/src/smiths/context-pipeline/generator.js +21 -4
- package/dist/src/smiths/context-pipeline/parallelism-section.js +7 -15
- package/dist/src/smiths/mcpsmith/generator.js +1 -1
- package/dist/src/smiths/toolsmith/runtime-discovery.d.mts +10 -0
- package/dist/src/smiths/toolsmith/runtime-discovery.mjs +76 -18
- package/dist/src/storage/backends/fs.js +18 -76
- package/dist/src/storage/backends/obsidian.js +1 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/update/checker.d.mts +2 -0
- package/dist/src/update/service.d.mts +22 -0
- package/dist/src/utils/contained-filesystem.d.mts +37 -0
- package/dist/src/utils/contained-filesystem.mjs +683 -0
- package/dist/src/utils/temporary-directory.js +36 -0
- package/dist/src/writing/example-generator.js +1 -1
- package/package.json +15 -2
- package/schemas/models/model-catalog.v1.schema.json +1 -1
- package/schemas/models/provider-model-capabilities.v1.schema.json +6 -0
- package/tools/_resolve-impl.mjs +13 -15
- package/tools/agents/deploy-agents.mjs +49 -21
- package/tools/agents/providers/base.mjs +104 -134
- package/tools/agents/providers/claude.mjs +8 -24
- package/tools/agents/providers/codex.mjs +18 -11
- package/tools/agents/providers/cursor.mjs +19 -18
- package/tools/agents/providers/grok-build.mjs +3 -1
- package/tools/agents/providers/hermes.mjs +57 -47
- package/tools/agents/providers/openclaw.mjs +9 -4
- package/tools/agents/providers/pi.mjs +5 -0
- package/tools/agents/providers/provider-prepare-channel.mjs +5 -0
- package/tools/agents/providers/provider-prepare-coordinator.mjs +247 -0
- package/tools/agents/providers/provider-prepare-worker.mjs +84 -0
- package/tools/agents/providers/rule-delivery.mjs +1270 -0
- package/tools/agents/providers/warp.mjs +20 -26
- package/tools/cli/decision-offload-report.mjs +40 -0
- package/tools/cli/workspace-status.mjs +862 -0
- package/tools/plugin/package-plugins.mjs +186 -8
- package/tools/plugin/plugin-links.mjs +114 -0
- package/tools/providers/antigravity-transport.mjs +3 -1
- package/tools/providers/deepseek-harness-transport.mjs +6 -4
- package/tools/providers/omp-transport.mjs +4 -2
- package/tools/ralph-external/lib/consecutive-failure-circuit-breaker.mjs +1748 -0
- package/tools/ralph-external/lib/launcher-recovery-evidence.mjs +55 -0
- package/tools/ralph-external/lib/omp-adapter.mjs +1 -1
- package/tools/ralph-external/lib/pi-adapter.mjs +0 -4
- package/tools/ralph-external/lib/provider-adapter.mjs +59 -1
- package/tools/ralph-external/lib/recovery-probe.mjs +72 -0
- package/tools/ralph-external/lib/session-environment.mjs +28 -0
- package/tools/ralph-external/lib/systemd-client.mjs +49 -0
- package/tools/ralph-external/lib/worker-health.mjs +1 -1
- package/tools/ralph-external/lib/worker-isolation.mjs +717 -0
- package/tools/ralph-external/lib/worker-overflow.mjs +50 -0
- package/tools/ralph-external/process-monitor.mjs +1017 -0
- package/tools/ralph-external/session-launcher.mjs +5057 -0
- package/tools/scripts/resource-preflight.mjs +541 -0
- package/tools/skills/deploy-skills-codex.mjs +2 -1
- package/vscode-extension/schemas/aiwg.config.v1.json +1194 -231
|
@@ -17,7 +17,13 @@ import { createHash } from 'crypto';
|
|
|
17
17
|
import { createRequire } from 'module';
|
|
18
18
|
import { importImpl } from '../../_resolve-impl.mjs';
|
|
19
19
|
|
|
20
|
+
const { parseRuleFrontmatter, inferRuleActivation } = await importImpl(import.meta.url, 'rules/activation.mjs');
|
|
21
|
+
const { resolveRulesInlineBudget, DEFAULT_RULES_INLINE_BUDGET_TOKENS } = await importImpl(import.meta.url, 'rules/inline-budget.mjs');
|
|
22
|
+
export { DEFAULT_RULES_INLINE_BUDGET_TOKENS, parseRuleFrontmatter as parseRuleActivation };
|
|
20
23
|
const { boundedExecFileSync } = await importImpl(import.meta.url, 'process/bounded-child-process.mjs');
|
|
24
|
+
const { extractFrameworkSlug, sameSkillOwner, canDeploySkill, pruneRecordedSkillFiles, releaseSkillOwnership } =
|
|
25
|
+
await importImpl(import.meta.url, 'providers/skill-ownership.mjs');
|
|
26
|
+
export { extractFrameworkSlug, sameSkillOwner, pruneRecordedSkillFiles, releaseSkillOwnership };
|
|
21
27
|
|
|
22
28
|
// Use graceful-fs to prevent EMFILE crashes on systems with low ulimit.
|
|
23
29
|
// graceful-fs queues open() calls when FD pressure is detected and retries
|
|
@@ -243,8 +249,9 @@ export function updateSidecarManifest(dir, deployedEntries, opts) {
|
|
|
243
249
|
const existing = readSidecarManifest(dir) || { managed: {} };
|
|
244
250
|
|
|
245
251
|
for (const entry of deployedEntries) {
|
|
246
|
-
const { filename, hash, frameworkSlug, kind } = entry;
|
|
252
|
+
const { filename, hash, frameworkSlug, kind, sourcePath } = entry;
|
|
247
253
|
const sidecarEntry = { hash: `sha256:${hash}`, source, version };
|
|
254
|
+
if (sourcePath) sidecarEntry.sourcePath = sourcePath;
|
|
248
255
|
if (frameworkSlug) sidecarEntry.frameworkSlug = frameworkSlug;
|
|
249
256
|
// `kind` marks artifacts whose lifecycle is governed elsewhere — currently
|
|
250
257
|
// only `skill-command` wrappers, which follow their source skill (#2507).
|
|
@@ -255,26 +262,6 @@ export function updateSidecarManifest(dir, deployedEntries, opts) {
|
|
|
255
262
|
writeSidecarManifest(dir, existing, dryRun);
|
|
256
263
|
}
|
|
257
264
|
|
|
258
|
-
// ============================================================================
|
|
259
|
-
// Cross-Framework Collision Detection (#1169)
|
|
260
|
-
// ============================================================================
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Extract the framework slug from a source file path.
|
|
264
|
-
*
|
|
265
|
-
* Recognizes paths under `agentic/code/frameworks/<slug>/...` and
|
|
266
|
-
* `agentic/code/addons/<slug>/...`. Returns null for paths outside
|
|
267
|
-
* those namespaces (operator-authored bundles, addons under
|
|
268
|
-
* `.aiwg/addons/`, etc.) — those don't get collision-tracked.
|
|
269
|
-
*/
|
|
270
|
-
export function extractFrameworkSlug(srcPath) {
|
|
271
|
-
if (typeof srcPath !== 'string') return null;
|
|
272
|
-
// Normalize separators for cross-platform matching
|
|
273
|
-
const normalized = srcPath.replace(/\\/g, '/');
|
|
274
|
-
const m = normalized.match(/agentic\/code\/(?:frameworks|addons)\/([^/]+)\//);
|
|
275
|
-
return m ? m[1] : null;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
265
|
// ============================================================================
|
|
279
266
|
// Model Configuration
|
|
280
267
|
// ============================================================================
|
|
@@ -762,7 +749,7 @@ export function deployFiles(files, destDir, opts, transformFn) {
|
|
|
762
749
|
|
|
763
750
|
// Read and transform source content (needed for content-equality check
|
|
764
751
|
// and the collision-vs-duplicate distinction)
|
|
765
|
-
const srcContent = fs.readFileSync(f, 'utf8');
|
|
752
|
+
const srcContent = injectBoundRules(fs.readFileSync(f, 'utf8'), f, opts);
|
|
766
753
|
let transformedContent = transformFn ? transformFn(f, srcContent, opts) : srcContent;
|
|
767
754
|
|
|
768
755
|
// Rule triggers are index metadata, not something the reading agent needs.
|
|
@@ -901,7 +888,7 @@ export function deployFiles(files, destDir, opts, transformFn) {
|
|
|
901
888
|
if (dryRun) console.log(`[dry-run] deploy ${a.src} -> ${a.dest} (${a.reason})`);
|
|
902
889
|
else writeFile(a.dest, a.content, false);
|
|
903
890
|
if (verbose) console.log(`deployed ${path.basename(a.src)} -> ${path.relative(process.cwd(), a.dest)} (${a.reason})`);
|
|
904
|
-
deployedEntries.push({ filename: path.basename(a.dest), hash: a.hash, frameworkSlug: a.frameworkSlug });
|
|
891
|
+
deployedEntries.push({ filename: path.basename(a.dest), hash: a.hash, frameworkSlug: a.frameworkSlug, sourcePath: path.resolve(a.src) });
|
|
905
892
|
} else if (a.type === 'skip') {
|
|
906
893
|
if (verbose) console.log(`skip (${a.reason}): ${path.basename(a.dest)}`);
|
|
907
894
|
// Preserve existing sidecar entries for skipped files
|
|
@@ -1113,7 +1100,7 @@ export function deploySkillsWithKernelRouting(
|
|
|
1113
1100
|
// Standard-dir cleanup (#1217): when we're NOT copying standard
|
|
1114
1101
|
// skills, anything AIWG-named under standardDestDir is a legacy
|
|
1115
1102
|
// mirror from a rc.13-or-earlier deploy. Prune to clean up.
|
|
1116
|
-
let prunedFromStandardDir =
|
|
1103
|
+
let prunedFromStandardDir = pruneMissingStandardSkills(standardDestDir, opts);
|
|
1117
1104
|
if (
|
|
1118
1105
|
!copyStandardSkills &&
|
|
1119
1106
|
standardDestDir &&
|
|
@@ -1550,6 +1537,32 @@ export function pruneStaleAiwgFiles(destDir, desiredStems, opts = {}) {
|
|
|
1550
1537
|
return removed;
|
|
1551
1538
|
}
|
|
1552
1539
|
|
|
1540
|
+
/** Source-scoped cleanup also works when a removed standard skill is absent from this deploy's input. */
|
|
1541
|
+
function pruneMissingStandardSkills(destDir, opts) {
|
|
1542
|
+
if (!destDir || !opts?.srcRoot || opts.dryRun || !fs.existsSync(destDir)) return 0;
|
|
1543
|
+
const sourceRoot = path.resolve(opts.srcRoot);
|
|
1544
|
+
let pruned = 0;
|
|
1545
|
+
for (const entry of fs.readdirSync(destDir, { withFileTypes: true })) {
|
|
1546
|
+
if (!entry.isDirectory()) continue;
|
|
1547
|
+
const skillDir = path.join(destDir, entry.name);
|
|
1548
|
+
const receipt = readSidecarManifest(skillDir);
|
|
1549
|
+
if (!receipt?.skillSource || !receipt.managed?.['SKILL.md']) continue;
|
|
1550
|
+
const relative = path.relative(sourceRoot, receipt.skillSource);
|
|
1551
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) continue;
|
|
1552
|
+
try {
|
|
1553
|
+
fs.statSync(path.join(receipt.skillSource, 'SKILL.md'));
|
|
1554
|
+
continue;
|
|
1555
|
+
} catch (error) {
|
|
1556
|
+
if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR') continue;
|
|
1557
|
+
}
|
|
1558
|
+
if (releaseSkillOwnership(skillDir, {
|
|
1559
|
+
frameworkSlug: receipt.managed['SKILL.md'].frameworkSlug,
|
|
1560
|
+
skillSource: receipt.skillSource,
|
|
1561
|
+
}, opts)) pruned++;
|
|
1562
|
+
}
|
|
1563
|
+
return pruned;
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1553
1566
|
/**
|
|
1554
1567
|
* Deploy a skill directory (copy recursively).
|
|
1555
1568
|
*
|
|
@@ -1574,6 +1587,10 @@ export function deploySkillDir(skillDir, destDir, opts) {
|
|
|
1574
1587
|
}
|
|
1575
1588
|
|
|
1576
1589
|
const destSkillDir = path.join(destDir, skillName);
|
|
1590
|
+
const previous = readSidecarManifest(destSkillDir);
|
|
1591
|
+
const frameworkSlug = extractFrameworkSlug(skillMdPath);
|
|
1592
|
+
if (!canDeploySkill(previous, skillDir, opts)) return;
|
|
1593
|
+
const managed = {};
|
|
1577
1594
|
if (!dryRun) ensureDir(destSkillDir);
|
|
1578
1595
|
|
|
1579
1596
|
function copyRecursive(src, dest) {
|
|
@@ -1601,6 +1618,15 @@ export function deploySkillDir(skillDir, destDir, opts) {
|
|
|
1601
1618
|
}
|
|
1602
1619
|
}
|
|
1603
1620
|
|
|
1621
|
+
if (entry.name === 'SKILL.md') srcContent = injectBoundRules(srcContent, srcPath, opts);
|
|
1622
|
+
|
|
1623
|
+
const relative = path.relative(destSkillDir, destPath);
|
|
1624
|
+
managed[relative] = {
|
|
1625
|
+
hash: `sha256:${contentHash(srcContent)}`,
|
|
1626
|
+
source: opts.deploySource || 'bundled',
|
|
1627
|
+
version: opts.deployVersion || 'unknown',
|
|
1628
|
+
...(frameworkSlug ? { frameworkSlug } : {}),
|
|
1629
|
+
};
|
|
1604
1630
|
if (fs.existsSync(destPath)) {
|
|
1605
1631
|
const destContent = fs.readFileSync(destPath, 'utf8');
|
|
1606
1632
|
if (destContent === srcContent && !force) {
|
|
@@ -1620,6 +1646,8 @@ export function deploySkillDir(skillDir, destDir, opts) {
|
|
|
1620
1646
|
}
|
|
1621
1647
|
|
|
1622
1648
|
copyRecursive(skillDir, destSkillDir);
|
|
1649
|
+
pruneRecordedSkillFiles(destSkillDir, previous?.managed, new Set(Object.keys(managed)), opts);
|
|
1650
|
+
writeSidecarManifest(destSkillDir, { managed, skillSource: path.resolve(skillDir) }, dryRun);
|
|
1623
1651
|
|
|
1624
1652
|
// Drop a `.aiwg-managed` marker so future cleanup runs can identify
|
|
1625
1653
|
// AIWG-deployed skills regardless of frontmatter shape (some providers
|
|
@@ -2417,15 +2445,8 @@ export function ruleEnforcementLevel(content) {
|
|
|
2417
2445
|
* deployment dispatchable; `AIWG_RULES_INLINE_BUDGET_TOKENS=0` disables the
|
|
2418
2446
|
* budget, any other value overrides it.
|
|
2419
2447
|
*/
|
|
2420
|
-
export const DEFAULT_RULES_INLINE_BUDGET_TOKENS = 64_000;
|
|
2421
|
-
const RULE_CHARS_PER_TOKEN = 4;
|
|
2422
|
-
|
|
2423
2448
|
export function resolveRulesInlineBudgetTokens(env = process.env) {
|
|
2424
|
-
|
|
2425
|
-
if (raw === undefined || raw === '') return DEFAULT_RULES_INLINE_BUDGET_TOKENS;
|
|
2426
|
-
const parsed = Number(raw);
|
|
2427
|
-
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_RULES_INLINE_BUDGET_TOKENS;
|
|
2428
|
-
return parsed;
|
|
2449
|
+
return resolveRulesInlineBudget({}, env);
|
|
2429
2450
|
}
|
|
2430
2451
|
|
|
2431
2452
|
/** Sidecar recording which rules the inline budget moved on demand (#2562). */
|
|
@@ -2441,96 +2462,13 @@ export function readRuleBudgetSidecar(destDir) {
|
|
|
2441
2462
|
}
|
|
2442
2463
|
}
|
|
2443
2464
|
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
* then one per addon), so the budget is enforced against what is actually on
|
|
2449
|
-
* disk after each pass rather than against any single pass's file list. When
|
|
2450
|
-
* the inlined set exceeds the budget, the largest HIGH rules (then unlabelled
|
|
2451
|
-
* ones; never CRITICAL or the indexes) are removed from the directory and
|
|
2452
|
-
* recorded in the sidecar; the next pass skips re-deploying them and the
|
|
2453
|
-
* on-demand index lists them as binding rules fetched via `aiwg show rule`.
|
|
2454
|
-
* A changed budget invalidates the sidecar so the set is recomputed.
|
|
2455
|
-
*
|
|
2456
|
-
* Returns `{ demoted, removed }` — all demoted stems (sidecar state) and the
|
|
2457
|
-
* stems removed by this call.
|
|
2458
|
-
*/
|
|
2459
|
-
export function reconcileInlineRuleBudget(destDir, budgetTokens = resolveRulesInlineBudgetTokens(), opts = {}) {
|
|
2460
|
-
const sidecarPath = path.join(destDir, RULE_BUDGET_SIDECAR);
|
|
2461
|
-
if (!budgetTokens || budgetTokens <= 0) {
|
|
2462
|
-
if (fs.existsSync(sidecarPath) && !opts.dryRun) fs.rmSync(sidecarPath, { force: true });
|
|
2463
|
-
return { demoted: [], removed: [] };
|
|
2464
|
-
}
|
|
2465
|
-
const previous = readRuleBudgetSidecar(destDir);
|
|
2466
|
-
const carried = previous && previous.budgetTokens === budgetTokens ? previous.demoted : [];
|
|
2467
|
-
let files = [];
|
|
2468
|
-
try {
|
|
2469
|
-
files = fs.readdirSync(destDir, { withFileTypes: true })
|
|
2470
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
2471
|
-
.map((entry) => path.join(destDir, entry.name))
|
|
2472
|
-
.filter((file) => {
|
|
2473
|
-
const base = path.basename(file);
|
|
2474
|
-
if (base === 'RULES-INDEX.md' || base === 'RULES-ONDEMAND.md') return false;
|
|
2475
|
-
try { return MANAGED_MARKER_RE.test(fs.readFileSync(file, 'utf8')); } catch { return false; }
|
|
2476
|
-
});
|
|
2477
|
-
} catch {
|
|
2478
|
-
return { demoted: carried, removed: [] };
|
|
2479
|
-
}
|
|
2480
|
-
const { demoted } = applyInlineRuleBudget(files, budgetTokens);
|
|
2481
|
-
const removed = demoted.map((file) => artifactStem(path.basename(file)));
|
|
2482
|
-
if (!opts.dryRun) {
|
|
2483
|
-
for (const file of demoted) fs.rmSync(file, { force: true });
|
|
2484
|
-
}
|
|
2485
|
-
const all = [...new Set([...carried, ...removed])].sort();
|
|
2486
|
-
if (!opts.dryRun) {
|
|
2487
|
-
if (all.length > 0) {
|
|
2488
|
-
fs.writeFileSync(sidecarPath, `${JSON.stringify({ budgetTokens, demoted: all }, null, 2)}\n`, 'utf8');
|
|
2489
|
-
} else if (fs.existsSync(sidecarPath)) {
|
|
2490
|
-
fs.rmSync(sidecarPath, { force: true });
|
|
2491
|
-
}
|
|
2492
|
-
}
|
|
2493
|
-
return { demoted: all, removed };
|
|
2465
|
+
// Compatibility exports: budgets never change selection or retain demotions.
|
|
2466
|
+
export function reconcileInlineRuleBudget(destDir, budgetTokens, opts = {}) {
|
|
2467
|
+
if (!opts.dryRun) fs.rmSync(path.join(destDir, RULE_BUDGET_SIDECAR), { force: true });
|
|
2468
|
+
return { demoted: [], removed: [] };
|
|
2494
2469
|
}
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
* Fit an always-on rule set under the inline budget (#2562).
|
|
2498
|
-
*
|
|
2499
|
-
* CRITICAL rules and the generated indexes are never demoted. When the
|
|
2500
|
-
* remaining set still exceeds the budget, HIGH rules are demoted largest-first
|
|
2501
|
-
* (fewest rules leave the always-on set), then rules with no declared level.
|
|
2502
|
-
* Demoted rules stay reachable via `aiwg show rule <name>` and are listed in
|
|
2503
|
-
* RULES-ONDEMAND.md under their own heading. Returns `{ inline, demoted }`
|
|
2504
|
-
* with the original order preserved for `inline`.
|
|
2505
|
-
*/
|
|
2506
|
-
export function applyInlineRuleBudget(ruleFiles, budgetTokens = resolveRulesInlineBudgetTokens()) {
|
|
2507
|
-
const files = [...(ruleFiles || [])];
|
|
2508
|
-
if (!budgetTokens || budgetTokens <= 0) return { inline: files, demoted: [] };
|
|
2509
|
-
const sized = files.map((file) => {
|
|
2510
|
-
const base = path.basename(file);
|
|
2511
|
-
let content = '';
|
|
2512
|
-
try { content = fs.readFileSync(file, 'utf8'); } catch { /* unreadable → keep inline */ }
|
|
2513
|
-
const level = base === 'RULES-INDEX.md' || base === 'RULES-ONDEMAND.md' ? 'critical' : ruleEnforcementLevel(content);
|
|
2514
|
-
return { file, tokens: Math.ceil(Buffer.byteLength(content, 'utf8') / RULE_CHARS_PER_TOKEN), level };
|
|
2515
|
-
});
|
|
2516
|
-
let total = sized.reduce((sum, item) => sum + item.tokens, 0);
|
|
2517
|
-
if (total <= budgetTokens) return { inline: files, demoted: [] };
|
|
2518
|
-
const demotable = sized
|
|
2519
|
-
.filter((item) => item.level !== 'critical')
|
|
2520
|
-
.sort((a, b) => {
|
|
2521
|
-
const rank = (level) => (level === 'high' ? 0 : level === null ? 1 : 2);
|
|
2522
|
-
return rank(a.level) - rank(b.level) || b.tokens - a.tokens;
|
|
2523
|
-
});
|
|
2524
|
-
const demoted = new Set();
|
|
2525
|
-
for (const item of demotable) {
|
|
2526
|
-
if (total <= budgetTokens) break;
|
|
2527
|
-
demoted.add(item.file);
|
|
2528
|
-
total -= item.tokens;
|
|
2529
|
-
}
|
|
2530
|
-
return {
|
|
2531
|
-
inline: files.filter((file) => !demoted.has(file)),
|
|
2532
|
-
demoted: files.filter((file) => demoted.has(file)),
|
|
2533
|
-
};
|
|
2470
|
+
export function applyInlineRuleBudget(ruleFiles) {
|
|
2471
|
+
return { inline: [...(ruleFiles || [])], demoted: [] };
|
|
2534
2472
|
}
|
|
2535
2473
|
|
|
2536
2474
|
/**
|
|
@@ -2544,10 +2482,14 @@ export function isAlwaysOnRule(filePath) {
|
|
|
2544
2482
|
const base = path.basename(filePath);
|
|
2545
2483
|
if (base === 'RULES-INDEX.md' || base === 'RULES-ONDEMAND.md') return true;
|
|
2546
2484
|
try {
|
|
2547
|
-
const
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2485
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
2486
|
+
const parsed = parseRuleFrontmatter(content);
|
|
2487
|
+
if (!parsed.valid) throw new Error(`${filePath}: ${parsed.diagnostics.map(d => d.message).join('; ')}`);
|
|
2488
|
+
const activation = parsed.rule.tagged ? parsed.rule.activation : inferRuleActivation(ruleEnforcementLevel(content));
|
|
2489
|
+
return activation === 'kernel' || activation === 'paths';
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
if (error.code === 'ENOENT') return true;
|
|
2492
|
+
throw error;
|
|
2551
2493
|
}
|
|
2552
2494
|
}
|
|
2553
2495
|
|
|
@@ -2565,7 +2507,9 @@ export function listOnDemandRuleFiles(srcRoot, excludeAddons = []) {
|
|
|
2565
2507
|
for (const f of listMdFiles(dir)) {
|
|
2566
2508
|
const b = path.basename(f);
|
|
2567
2509
|
if (b === 'RULES-INDEX.md' || b === 'RULES-ONDEMAND.md') continue;
|
|
2568
|
-
|
|
2510
|
+
const parsed = parseRuleFrontmatter(fs.readFileSync(f, 'utf8'));
|
|
2511
|
+
if (!parsed.valid) throw new Error(`${f}: invalid rule frontmatter`);
|
|
2512
|
+
if (!parsed.rule.tagged && !isAlwaysOnRule(f)) out.push(f);
|
|
2569
2513
|
}
|
|
2570
2514
|
};
|
|
2571
2515
|
for (const addon of discoverAddons(aiwgRoot)) {
|
|
@@ -2678,19 +2622,23 @@ export function getAddonRuleFiles(srcRoot, excludeAddons = [], options = {}) {
|
|
|
2678
2622
|
for (const addon of addons) {
|
|
2679
2623
|
if (excludeAddons.includes(addon.name)) continue;
|
|
2680
2624
|
|
|
2681
|
-
// Skip addons with consolidated rule indexes — their rules are
|
|
2682
|
-
// included via assembleRulesIndex() instead of individual files
|
|
2683
|
-
if (addon.manifest?.consolidation?.deployIndexOnly) continue;
|
|
2684
|
-
|
|
2685
2625
|
const rulesDir = path.join(addon.path, 'rules');
|
|
2686
2626
|
if (fs.existsSync(rulesDir)) {
|
|
2687
|
-
|
|
2627
|
+
for (const file of listMdFiles(rulesDir)) {
|
|
2628
|
+
// Consolidation remains a legacy inventory policy. Explicit activation
|
|
2629
|
+
// owns delivery, including the desired set used by the outer prune.
|
|
2630
|
+
if (addon.manifest?.consolidation?.deployIndexOnly) {
|
|
2631
|
+
const parsed = parseRuleFrontmatter(fs.readFileSync(file, 'utf8'));
|
|
2632
|
+
if (!parsed.valid) throw new Error(`${file}: invalid rule frontmatter`);
|
|
2633
|
+
if (!parsed.rule.tagged) continue;
|
|
2634
|
+
}
|
|
2635
|
+
files.push(file);
|
|
2636
|
+
}
|
|
2688
2637
|
}
|
|
2689
2638
|
}
|
|
2690
2639
|
|
|
2691
|
-
//
|
|
2692
|
-
//
|
|
2693
|
-
// desired-set (computeAllArtifactBasenames) exclude them for every provider.
|
|
2640
|
+
// Activation selects bodies; only untagged rules use the old severity tier.
|
|
2641
|
+
// The deploy enumeration and prune desired set must select the same bodies.
|
|
2694
2642
|
return files.filter(isAlwaysOnRule);
|
|
2695
2643
|
}
|
|
2696
2644
|
|
|
@@ -3382,3 +3330,25 @@ export const ProviderInterface = {
|
|
|
3382
3330
|
// ── File Extension ────────────────────────────────────────────────
|
|
3383
3331
|
getFileExtension(type) { return '.md'; }
|
|
3384
3332
|
};
|
|
3333
|
+
|
|
3334
|
+
/** Replace only the generated binding block; keep authored bytes intact. */
|
|
3335
|
+
export function replaceBoundRulesBlock(content, rules = []) {
|
|
3336
|
+
const clean = content.replace(/\n?<!-- AIWG:bound-rules:start -->[\s\S]*?<!-- AIWG:bound-rules:end -->\n?/g, '');
|
|
3337
|
+
if (!rules.length) return clean;
|
|
3338
|
+
const unique = [...new Map(rules.map(rule => [rule.name, rule])).values()]
|
|
3339
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
3340
|
+
return `${clean}${clean.endsWith('\n') ? '' : '\n'}\n<!-- AIWG:bound-rules:start -->\n## Bound rules\n\nRequired reading on every invocation: fetch and follow every bound rule below before starting governed work. A summary or discovery pointer does not discharge this obligation.\n\n${unique.map(rule =>
|
|
3341
|
+
`- ${rule.name} — ${rule.summary}\n Fetch and follow before governed work: \`aiwg show rule ${rule.name}\`.`
|
|
3342
|
+
).join('\n')}\n<!-- AIWG:bound-rules:end -->\n`;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
export function injectBoundRules(content, source, opts = {}) {
|
|
3346
|
+
if (!opts.ruleDelivery) return content;
|
|
3347
|
+
return replaceBoundRulesBlock(content, opts.ruleDelivery.bindings.get(path.resolve(source)) || []);
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
/** All provider output is staged; the complete startup guard runs before promotion. */
|
|
3351
|
+
export async function withRuleDeployment(opts, provider, deploy) {
|
|
3352
|
+
const { deployWithRuleDelivery } = await import('./rule-delivery.mjs');
|
|
3353
|
+
return deployWithRuleDelivery(opts, provider, deploy);
|
|
3354
|
+
}
|
|
@@ -29,6 +29,7 @@ import path from 'path';
|
|
|
29
29
|
import { tmpdir } from 'os';
|
|
30
30
|
import { classifyModelRole, modelForRole } from './model-role.mjs';
|
|
31
31
|
import {
|
|
32
|
+
withRuleDeployment,
|
|
32
33
|
ensureDir,
|
|
33
34
|
listMdFiles,
|
|
34
35
|
listMdFilesRecursive,
|
|
@@ -49,9 +50,7 @@ import {
|
|
|
49
50
|
getAddonRuleFiles,
|
|
50
51
|
listOnDemandRuleFiles,
|
|
51
52
|
writeOnDemandRuleIndex,
|
|
52
|
-
readRuleBudgetSidecar,
|
|
53
53
|
reconcileInlineRuleBudget,
|
|
54
|
-
resolveRulesInlineBudgetTokens,
|
|
55
54
|
assembleRulesIndex,
|
|
56
55
|
normalizeDeploymentMode,
|
|
57
56
|
collectFrameworkArtifacts,
|
|
@@ -521,6 +520,10 @@ export function getFileExtension(type) {
|
|
|
521
520
|
* Orchestrates deployment of agents, commands, skills, and rules
|
|
522
521
|
*/
|
|
523
522
|
export async function deploy(opts) {
|
|
523
|
+
return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
async function deploySelected(opts) {
|
|
524
527
|
const {
|
|
525
528
|
srcRoot,
|
|
526
529
|
target,
|
|
@@ -619,7 +622,7 @@ export async function deploy(opts) {
|
|
|
619
622
|
// Deploy based on flags — track counts for summary
|
|
620
623
|
const counts = { agents: 0, commands: 0, skills: 0, rules: 0, souls: 0 };
|
|
621
624
|
|
|
622
|
-
if (!commandsOnly && !skillsOnly) {
|
|
625
|
+
if (!commandsOnly && !skillsOnly && !rulesOnly) {
|
|
623
626
|
// Apply filters if specified
|
|
624
627
|
const filteredAgents = filterAgentFiles(agentFiles, opts);
|
|
625
628
|
if (verbose && (opts.filter || opts.filterRole)) {
|
|
@@ -664,17 +667,7 @@ export async function deploy(opts) {
|
|
|
664
667
|
pruneStaleAiwgSkills(kernelDestDir, computeAllKernelNames(srcRoot), opts);
|
|
665
668
|
}
|
|
666
669
|
|
|
667
|
-
const inlineBudgetTokens = resolveRulesInlineBudgetTokens();
|
|
668
|
-
let demotedRuleNames = [];
|
|
669
670
|
if (shouldDeployRules || rulesOnly) {
|
|
670
|
-
// Inline budget (#2562): rules a previous pass already moved on demand are
|
|
671
|
-
// not re-inlined; the directory is reconciled against the budget after
|
|
672
|
-
// this pass deploys (see below).
|
|
673
|
-
const sidecar = readRuleBudgetSidecar(path.join(target, paths.rules));
|
|
674
|
-
if (sidecar && sidecar.budgetTokens === inlineBudgetTokens && sidecar.demoted.length > 0) {
|
|
675
|
-
const skip = new Set(sidecar.demoted);
|
|
676
|
-
ruleFiles = ruleFiles.filter((f) => !skip.has(path.basename(f).replace(/\.md$/, '')));
|
|
677
|
-
}
|
|
678
671
|
// Try assembled rules index (combines all component indexes)
|
|
679
672
|
const assembled = assembleRulesIndex(srcRoot);
|
|
680
673
|
if (assembled) {
|
|
@@ -707,17 +700,8 @@ export async function deploy(opts) {
|
|
|
707
700
|
// On-demand index (#1673): list the MEDIUM/LOW rules that were tier-gated
|
|
708
701
|
// out of the always-on set so agents can fetch them via `aiwg show rule`.
|
|
709
702
|
const rulesDestDir = path.join(target, paths.rules);
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
// in RULES-ONDEMAND.md with their fetch hint.
|
|
713
|
-
const budget = reconcileInlineRuleBudget(rulesDestDir, inlineBudgetTokens, opts);
|
|
714
|
-
demotedRuleNames = budget.demoted;
|
|
715
|
-
if (budget.removed.length > 0 && !opts.quiet) {
|
|
716
|
-
console.log(` Inline rule budget: ${budget.removed.length} HIGH rule(s) moved on demand to fit ${inlineBudgetTokens.toLocaleString()} tokens: ${budget.removed.join(', ')} (see RULES-ONDEMAND.md)`);
|
|
717
|
-
}
|
|
718
|
-
const onDemandCount = writeOnDemandRuleIndex(rulesDestDir, listOnDemandRuleFiles(srcRoot), {
|
|
719
|
-
...opts, demotedNames: demotedRuleNames, inlineBudgetTokens,
|
|
720
|
-
});
|
|
703
|
+
reconcileInlineRuleBudget(rulesDestDir, undefined, opts);
|
|
704
|
+
const onDemandCount = writeOnDemandRuleIndex(rulesDestDir, listOnDemandRuleFiles(srcRoot), opts);
|
|
721
705
|
if (verbose && onDemandCount > 0) {
|
|
722
706
|
console.log(` On-demand rules (not inlined): ${onDemandCount} → RULES-ONDEMAND.md`);
|
|
723
707
|
}
|
|
@@ -38,6 +38,7 @@ import { spawn } from 'child_process';
|
|
|
38
38
|
import { load as loadYaml } from 'js-yaml';
|
|
39
39
|
import { classifyModelRole, modelForRole } from './model-role.mjs';
|
|
40
40
|
import {
|
|
41
|
+
withRuleDeployment,
|
|
41
42
|
ensureDir,
|
|
42
43
|
listMdFiles,
|
|
43
44
|
listMdFilesRecursive,
|
|
@@ -269,7 +270,8 @@ export async function deployCommands(targetDir, srcRoot, opts) {
|
|
|
269
270
|
|
|
270
271
|
const child = spawn('node', [scriptPath, ...args], {
|
|
271
272
|
stdio: 'inherit',
|
|
272
|
-
cwd: helperRoot
|
|
273
|
+
cwd: helperRoot,
|
|
274
|
+
env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env
|
|
273
275
|
});
|
|
274
276
|
|
|
275
277
|
child.on('close', (code) => {
|
|
@@ -318,7 +320,8 @@ export async function deploySkills(targetDir, srcRoot, opts) {
|
|
|
318
320
|
|
|
319
321
|
const child = spawn('node', [scriptPath, ...args], {
|
|
320
322
|
stdio: 'inherit',
|
|
321
|
-
cwd: helperRoot
|
|
323
|
+
cwd: helperRoot,
|
|
324
|
+
env: opts.deploymentHome ? { ...process.env, HOME: opts.deploymentHome } : process.env
|
|
322
325
|
});
|
|
323
326
|
|
|
324
327
|
child.on('close', (code) => {
|
|
@@ -332,7 +335,7 @@ export async function deploySkills(targetDir, srcRoot, opts) {
|
|
|
332
335
|
// Self-heal: prune AIWG-managed skill dirs left behind in the legacy
|
|
333
336
|
// ~/.codex/skills/ home location by pre-fix versions, so codex-rs stops
|
|
334
337
|
// listing each skill twice.
|
|
335
|
-
pruneLegacyCodexSkills(opts);
|
|
338
|
+
pruneLegacyCodexSkills(opts, opts.legacyCodexSkills || legacyHomeSkillsDir);
|
|
336
339
|
}
|
|
337
340
|
|
|
338
341
|
/**
|
|
@@ -398,17 +401,16 @@ export function deployRules(ruleFiles, targetDir, opts) {
|
|
|
398
401
|
* Aggregate agents to single AGENTS.md file
|
|
399
402
|
*/
|
|
400
403
|
export function aggregateToAgentsMd(agentFiles, destPath, opts) {
|
|
401
|
-
const
|
|
402
|
-
for (const
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
blocks.push(content);
|
|
404
|
+
const lines = ['# AIWG agent definitions', '', 'Read the selected definition before invoking an agent.', ''];
|
|
405
|
+
for (const file of agentFiles) {
|
|
406
|
+
const { metadata } = parseFrontmatter(fs.readFileSync(file, 'utf8'));
|
|
407
|
+
const name = metadata.name || path.basename(file, '.md');
|
|
408
|
+
lines.push(`- ${name}: .codex/agents/${path.basename(file, '.md')}.toml`);
|
|
407
409
|
}
|
|
408
|
-
const out =
|
|
410
|
+
const out = `${lines.join('\n')}\n`;
|
|
409
411
|
if (opts.dryRun) console.log(`[dry-run] write ${destPath}`);
|
|
410
412
|
else fs.writeFileSync(destPath, out, 'utf8');
|
|
411
|
-
|
|
413
|
+
|
|
412
414
|
}
|
|
413
415
|
|
|
414
416
|
// ============================================================================
|
|
@@ -536,6 +538,10 @@ export function getFileExtension(type) {
|
|
|
536
538
|
* Main deployment function for Codex provider
|
|
537
539
|
*/
|
|
538
540
|
export async function deploy(opts) {
|
|
541
|
+
return withRuleDeployment(opts, { name, paths, kernelSkillsPath }, deploySelected);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
async function deploySelected(opts) {
|
|
539
545
|
const {
|
|
540
546
|
srcRoot,
|
|
541
547
|
target,
|
|
@@ -628,6 +634,7 @@ export async function deploy(opts) {
|
|
|
628
634
|
// Aggregate to single AGENTS.md
|
|
629
635
|
const destPath = path.join(target, 'AGENTS.md');
|
|
630
636
|
console.log(`\nAggregating ${agentFiles.length} agents to AGENTS.md...`);
|
|
637
|
+
deployAgents(agentFiles, target, opts);
|
|
631
638
|
aggregateToAgentsMd(agentFiles, destPath, opts);
|
|
632
639
|
} else {
|
|
633
640
|
console.log(`\nDeploying ${agentFiles.length} agents...`);
|
|
@@ -27,6 +27,8 @@ try { const gfs = _require('graceful-fs'); gfs.gracefulify(realFs); fs = realFs;
|
|
|
27
27
|
import path from 'path';
|
|
28
28
|
import { spawn } from 'child_process';
|
|
29
29
|
import {
|
|
30
|
+
withRuleDeployment,
|
|
31
|
+
parseRuleActivation,
|
|
30
32
|
ensureDir,
|
|
31
33
|
listMdFiles,
|
|
32
34
|
listMdFilesRecursive,
|
|
@@ -117,25 +119,20 @@ export function transformCommand(srcPath, content, opts) {
|
|
|
117
119
|
return content;
|
|
118
120
|
}
|
|
119
121
|
|
|
120
|
-
/**
|
|
121
|
-
* Transform rule content for Cursor — inject MDC frontmatter with the
|
|
122
|
-
* activation mode mapping per ADR-2 (rule activation mode schema).
|
|
123
|
-
*
|
|
124
|
-
* Reads the source rule's `activation:` field (or defaults to alwaysApply
|
|
125
|
-
* per ADR-2 §2 default-preservation) and emits the corresponding MDC
|
|
126
|
-
* frontmatter fields:
|
|
127
|
-
* - alwaysApply (true): activation === 'alwaysApply' (default)
|
|
128
|
-
* - alwaysApply (false), no globs: activation === 'auto'
|
|
129
|
-
* - globs: activation === 'glob' + globs: '<pattern>'
|
|
130
|
-
* - alwaysApply (false), description: activation === 'manual'
|
|
131
|
-
*
|
|
132
|
-
* Per ADR-2 §5: until the live-Cursor smoke test gate is in CI, all rules
|
|
133
|
-
* deploy with alwaysApply: true regardless of source `activation` value
|
|
134
|
-
* (with a deploy-time warning when the source declared a non-alwaysApply
|
|
135
|
-
* mode). The deploy-time warning is emitted by the deployer, not this
|
|
136
|
-
* transform.
|
|
137
|
-
*/
|
|
122
|
+
/** Compile the shared five-mode activation contract to native MDC. */
|
|
138
123
|
export function transformRule(srcPath, content, opts) {
|
|
124
|
+
const parsed = parseRuleActivation(content);
|
|
125
|
+
if (!parsed.valid) throw new Error(`${srcPath}: ${parsed.diagnostics.map(d => d.message).join('; ')}`);
|
|
126
|
+
if (parsed.rule.tagged) {
|
|
127
|
+
const rule = parsed.rule;
|
|
128
|
+
// Bound/on-demand rules have no auto-selection description or glob hint.
|
|
129
|
+
const fields = rule.activation === 'paths'
|
|
130
|
+
? `globs: ${JSON.stringify(rule.paths)}\nalwaysApply: false`
|
|
131
|
+
: rule.activation === 'summary'
|
|
132
|
+
? `description: ${JSON.stringify(rule.summary)}\nalwaysApply: false`
|
|
133
|
+
: `alwaysApply: ${rule.activation === 'kernel'}`;
|
|
134
|
+
return `---\n${fields}\n---\n${parsed.body}`;
|
|
135
|
+
}
|
|
139
136
|
// Detect proper YAML frontmatter — must close within the first 30 lines.
|
|
140
137
|
// Files with `---` horizontal rules deeper in markdown content (like
|
|
141
138
|
// RULES-INDEX.md) can have multiple `---` lines without being frontmatter;
|
|
@@ -451,6 +448,10 @@ export function getFileExtension(type) {
|
|
|
451
448
|
* Orchestrates deployment of agents, commands, skills, and rules
|
|
452
449
|
*/
|
|
453
450
|
export async function deploy(opts) {
|
|
451
|
+
return withRuleDeployment(opts, { name, paths, kernelSkillsPath, transformRule }, deploySelected);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function deploySelected(opts) {
|
|
454
455
|
const {
|
|
455
456
|
srcRoot,
|
|
456
457
|
target,
|
|
@@ -216,7 +216,9 @@ function regularFiles(dir, prefix = '') {
|
|
|
216
216
|
function matchesSkillSource(source, target) {
|
|
217
217
|
try {
|
|
218
218
|
const expected = regularFiles(source);
|
|
219
|
-
|
|
219
|
+
// The marker and the per-skill ownership receipt are AIWG bookkeeping,
|
|
220
|
+
// not skill content, so neither makes a deployed skill look modified.
|
|
221
|
+
const actual = regularFiles(target).filter(name => name !== '.aiwg-managed' && name !== '.aiwg-manifest.json');
|
|
220
222
|
if (JSON.stringify(expected) !== JSON.stringify(actual)) return false;
|
|
221
223
|
return expected.every(relative => {
|
|
222
224
|
if (relative.endsWith(path.sep)) return true;
|