@kuznai/inception-engine 0.25.1 → 1.0.2
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 +68 -4
- package/dist/src/core/adapters/agent-definitions.d.ts +2 -1
- package/dist/src/core/adapters/agent-definitions.js +14 -5
- package/dist/src/core/adapters/execution-config.js +2 -4
- package/dist/src/core/adapters/index.d.ts +2 -1
- package/dist/src/core/adapters/index.js +3 -3
- package/dist/src/core/adapters/rules.d.ts +2 -1
- package/dist/src/core/adapters/rules.js +12 -5
- package/dist/src/core/concurrency.d.ts +5 -0
- package/dist/src/core/concurrency.js +19 -0
- package/dist/src/core/deploy.js +158 -100
- package/dist/src/core/init.d.ts +1 -0
- package/dist/src/core/init.js +79 -28
- package/dist/src/core/preflight.js +6 -4
- package/dist/src/core/revert.js +2 -4
- package/dist/src/core/validation.d.ts +31 -3
- package/dist/src/core/validation.js +64 -10
- package/dist/src/formatters.js +14 -11
- package/dist/src/schemas/manifest.d.ts +64 -64
- package/dist/src/schemas/manifest.js +1 -1
- package/dist/src/schemas/registry.d.ts +27 -27
- package/dist/src/types.d.ts +1 -1
- package/dist/test/unit/adapters.test.js +84 -83
- package/dist/test/unit/cli.test.js +57 -1
- package/dist/test/unit/deploy.test.js +253 -0
- package/dist/test/unit/formatters.test.js +4 -3
- package/dist/test/unit/gemini-north-star.test.js +9 -8
- package/dist/test/unit/init.test.d.ts +1 -0
- package/dist/test/unit/init.test.js +14 -0
- package/dist/test/unit/manifest.test.js +25 -0
- package/dist/test/unit/preflight.test.js +29 -0
- package/dist/test/unit/validation.test.d.ts +1 -0
- package/dist/test/unit/validation.test.js +102 -0
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -10,6 +10,27 @@ The broader portability layer is the roadmap direction, but this README focuses
|
|
|
10
10
|
|
|
11
11
|
`init` is available as a bootstrap command. It scans for directories containing `SKILL.md`, discovers agent-rules Markdown files using the Claude-first portability conventions, and reads `mcp-servers.json` from the repo root to populate `mcpServers`. Shared surfaces now default to the primary deploy target in generated manifests: for example, `init` emits `claude-code` rather than `github-copilot` for shared Claude-native skills and rules, and emits `gemini-cli` rather than `antigravity` for shared `GEMINI.md` rules surfaces. `files` and `configs` remain empty — `init` emits guidance when it detects a `files/` or `configs/` directory.
|
|
12
12
|
|
|
13
|
+
## SBOM
|
|
14
|
+
|
|
15
|
+
This repo can generate an SPDX SBOM without adding any new runtime dependency by using npm's built-in `sbom` command:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm ci
|
|
19
|
+
npm sbom --sbom-format spdx --omit dev > sbom.spdx.json
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`--omit dev` keeps the SBOM focused on the published CLI's runtime dependency graph.
|
|
23
|
+
|
|
24
|
+
For GitHub Actions, the repository includes [`.github/workflows/sbom.yml`](.github/workflows/sbom.yml). It:
|
|
25
|
+
|
|
26
|
+
- installs dependencies and runs the normal release checks
|
|
27
|
+
- creates the npm package tarball with `npm pack`
|
|
28
|
+
- generates `dist-artifacts/sbom.spdx.json` via `npm sbom`
|
|
29
|
+
- uploads both the tarball and SBOM as workflow artifacts
|
|
30
|
+
- creates a GitHub artifact attestation that binds the SBOM to the packaged tarball
|
|
31
|
+
|
|
32
|
+
Run it manually with `workflow_dispatch`, or let it run automatically when a GitHub Release is published.
|
|
33
|
+
|
|
13
34
|
## Quick Start
|
|
14
35
|
|
|
15
36
|
```bash
|
|
@@ -28,6 +49,7 @@ inception-engine reads a manifest file (`inception.json`) from the target direct
|
|
|
28
49
|
Managed skills overwrite their previous version. If a target exists but was not created by inception-engine, deployment refuses to replace it. On POSIX systems, symlinks mean updates to the source repo are reflected immediately.
|
|
29
50
|
|
|
30
51
|
Before executing, the deploy command runs preflight analysis on instruction files and capability planning inputs. It validates that `agentRules` and `agentDefinitions` for targets requiring specific structure (like `github-copilot` and `antigravity`) include valid YAML frontmatter with `name` and `description` fields. For `github-copilot`, it further ensures either `tools` or `instructions` are defined; for `antigravity`, it validates the shape of any `mcp-servers` or `mcpServers` defined in the frontmatter. It also warns when a manifest targets a surface that is implementation-only, planned, unsupported, or shared through another agent, when the same agent will have multiple `agentRules` scopes active simultaneously, when the same source file is deployed to multiple scopes (duplicate-content risk), when `agentRules` or `agentDefinitions` source files exceed 50 KB (context-budget risk), and when GitHub Copilot appears to be running under enterprise-managed policy that may override local configuration. Warnings are printed but do not block deployment; structural validation failures block deployment for the affected targets.
|
|
52
|
+
Agent-specific preflight reads and deploy actions targeting distinct files now run with bounded concurrency, while warning order, dry-run output, and deploy result ordering remain deterministic.
|
|
31
53
|
|
|
32
54
|
## Agent Compatibility Matrix
|
|
33
55
|
|
|
@@ -183,7 +205,7 @@ Each **file** entry deploys a single file to an agent's configuration location:
|
|
|
183
205
|
|
|
184
206
|
- **name** - Unique identifier (same format as skill names)
|
|
185
207
|
- **path** - Relative path to the source file within the repo
|
|
186
|
-
- **target** - Destination path using a placeholder prefix: `{home}`, `{appdata}`
|
|
208
|
+
- **target** - Destination path using a placeholder prefix: `{home}`, `{appdata}`, `{xdg_config}`, `{repo}`, or `{workspace}`. For example: `{home}/.claude/settings.json`
|
|
187
209
|
- **agents** - Array of agent IDs to deploy this file to
|
|
188
210
|
|
|
189
211
|
Each **config** entry applies a [JSON merge patch (RFC 7386)](https://datatracker.ietf.org/doc/html/rfc7386) to an existing agent config file:
|
|
@@ -230,6 +252,7 @@ Each **agentRules** entry deploys a Markdown instruction file to an agent's supp
|
|
|
230
252
|
- `"workspace"` — deploys to the workspace-root instruction file when an agent exposes one (e.g., `{workspace}/CLAUDE.md` for `claude-code`); when unsupported, deployment is skipped with a warning
|
|
231
253
|
- `"copilot-repo"` — deploys to GitHub Copilot's native repo-level instruction file at `{repo}/.github/copilot-instructions.md` (`github-copilot` only; other agents are warned and skipped)
|
|
232
254
|
- `"copilot-scoped"` — deploys to `{repo}/.github/instructions/{name}.instructions.md` where `{name}` is the manifest entry name (`github-copilot` only; other agents are warned and skipped)
|
|
255
|
+
- **targetDir** - Optional relative subdirectory under the selected `{repo}` or `{workspace}` root. Only valid with `scope: "repo"` or `scope: "workspace"`. This lets one manifest entry target nested workspaces such as `apps/frontend/CLAUDE.md` without changing the source file path.
|
|
233
256
|
|
|
234
257
|
Instruction rule deployment is supported for implemented global, repo, and workspace surfaces. The target path depends on the agent and the `scope`:
|
|
235
258
|
|
|
@@ -256,7 +279,7 @@ Each **permissions** entry deploys execution and safety-oriented configuration t
|
|
|
256
279
|
- **agents** - Array of agent IDs to deploy this entry to
|
|
257
280
|
- **config** - Permission config payload; shape is validated per agent (see below)
|
|
258
281
|
|
|
259
|
-
Permission deployment is currently supported for `claude-code` and `
|
|
282
|
+
Permission deployment is currently supported for `claude-code`, `codex`, and `opencode`. Other agents emit a warning and are skipped.
|
|
260
283
|
|
|
261
284
|
For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the `permissions` key is accepted:
|
|
262
285
|
|
|
@@ -289,6 +312,24 @@ For `codex`, the config is merged into `~/.codex/config.toml`. Only the `approva
|
|
|
289
312
|
|
|
290
313
|
Valid `approval_policy` values are `"auto"`, `"manual"`, `"suggest"`, and `"on-failure"`.
|
|
291
314
|
|
|
315
|
+
For `opencode`, the config is merged into `~/.config/opencode/opencode.json` on POSIX or `%APPDATA%\\opencode\\opencode.json` on Windows. Only the `permissions` key is accepted:
|
|
316
|
+
|
|
317
|
+
```json
|
|
318
|
+
{
|
|
319
|
+
"name": "opencode-permissions",
|
|
320
|
+
"agents": ["opencode"],
|
|
321
|
+
"config": {
|
|
322
|
+
"permissions": {
|
|
323
|
+
"allow": ["Read", "Glob"],
|
|
324
|
+
"ask": ["Bash(git push:*)"],
|
|
325
|
+
"deny": ["Bash(rm:*)"]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`allow`, `ask`, and `deny` are optional string arrays.
|
|
332
|
+
|
|
292
333
|
Revert restores the previous config values using the undo patch recorded at deploy time.
|
|
293
334
|
|
|
294
335
|
Each **hooks** entry deploys lifecycle-binding configurations to an agent's execution hook surface:
|
|
@@ -325,6 +366,29 @@ For `claude-code`, the config is merged into `~/.claude/settings.json`. Only the
|
|
|
325
366
|
|
|
326
367
|
Revert restores the previous config values using the undo patch recorded at deploy time.
|
|
327
368
|
|
|
369
|
+
Each **executionConfigs** entry deploys agent execution or safety settings to an agent's modeled runtime config surface:
|
|
370
|
+
|
|
371
|
+
- **name** - Unique identifier (same format as skill names)
|
|
372
|
+
- **agents** - Array of agent IDs to deploy this entry to
|
|
373
|
+
- **config** - Raw execution-config payload; today this is modeled for `gemini-cli` and patched into its settings file
|
|
374
|
+
|
|
375
|
+
Execution-config deployment is currently supported for `gemini-cli`. Other agents emit a warning and are skipped. Because this surface is still treated as provisional, preflight emits a config-authority warning when you target it so you can sanity-check the current upstream behavior.
|
|
376
|
+
|
|
377
|
+
For `gemini-cli`, the config is merged into `~/.gemini/settings.json`:
|
|
378
|
+
|
|
379
|
+
```json
|
|
380
|
+
{
|
|
381
|
+
"name": "gemini-safety",
|
|
382
|
+
"agents": ["gemini-cli"],
|
|
383
|
+
"config": {
|
|
384
|
+
"safeMode": true,
|
|
385
|
+
"sandbox": "workspace-write"
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Values are passed through as a JSON merge patch so the tool can model emerging Gemini CLI execution settings without inventing a separate schema for each key. Revert restores the previous config values using the undo patch recorded at deploy time.
|
|
391
|
+
|
|
328
392
|
Each **agentDefinitions** entry deploys an agent-definition file to the agent-definition directory of each targeted agent. `scope: "repo"` is the default, and some agents also support `scope: "global"`:
|
|
329
393
|
|
|
330
394
|
- **name** - Unique identifier (same format as skill names); used as the definition filename
|
|
@@ -371,7 +435,7 @@ The `name` and `description` fields in the frontmatter are used by most agents.
|
|
|
371
435
|
|
|
372
436
|
## `init` Command
|
|
373
437
|
|
|
374
|
-
`init` is meant to bootstrap a repository that already has skills and related manifest assets. It recursively scans the target directory, treats any directory containing `SKILL.md` as a skill, discovers supported instruction and MCP conventions, and writes a starter `inception.json`.
|
|
438
|
+
`init` is meant to bootstrap a repository that already has skills and related manifest assets. It recursively scans the target directory, treats any directory containing `SKILL.md` as a skill, discovers supported instruction and MCP conventions, and writes a starter `inception.json`. The scan uses a bounded-concurrency streaming directory walk so large repositories scale more predictably without changing discovery behavior.
|
|
375
439
|
|
|
376
440
|
Current `init` behavior:
|
|
377
441
|
|
|
@@ -479,7 +543,7 @@ inception-engine init <directory> [options]
|
|
|
479
543
|
| `--debug` | Show full error stack traces |
|
|
480
544
|
| `--help` | Show help message |
|
|
481
545
|
|
|
482
|
-
With `--plan`, deploy and revert print a grouped action preview by agent. Each planned change includes the source path when applicable, the resolved target path, and action-specific details such as JSON/TOML patch
|
|
546
|
+
With `--plan`, deploy and revert print a grouped action preview by agent. Each planned change includes the action verb, the source path when applicable, the resolved target path, and action-specific details such as indented JSON/TOML patch payload blocks or emitted frontmatter content.
|
|
483
547
|
|
|
484
548
|
### Examples
|
|
485
549
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AgentDefinitionEntry } from "../../schemas/manifest.ts";
|
|
2
2
|
import type { AgentId, FileWriteDeployAction, FileWriteRevertAction, PlanWarning } from "../../types.ts";
|
|
3
|
+
import { type SourcePathValidator } from "../validation.ts";
|
|
3
4
|
export interface AgentDefinitionsAdapterResult {
|
|
4
5
|
actions: FileWriteDeployAction[];
|
|
5
6
|
warnings: PlanWarning[];
|
|
6
7
|
}
|
|
7
|
-
export declare function compileAgentDefinitionActions(entry: AgentDefinitionEntry, sourceDir: string, resolvedSourceDir: string,
|
|
8
|
+
export declare function compileAgentDefinitionActions(entry: AgentDefinitionEntry, sourceDir: string, resolvedSourceDir: string, validateSource: SourcePathValidator, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<AgentDefinitionsAdapterResult>;
|
|
8
9
|
export declare function compileAgentDefinitionReverts(entry: AgentDefinitionEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): FileWriteRevertAction[];
|
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { AGENT_REGISTRY_BY_ID } from "../../config/agents.js";
|
|
3
3
|
import { planCapabilityForDeploy, resolveCapabilitySurface, } from "../capabilities.js";
|
|
4
4
|
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
5
|
-
import { validateAgentRuleMarkdownPath,
|
|
5
|
+
import { instructionRequiresFrontmatter, parseInstructionDocument, validateAgentRuleMarkdownPath, validateInstructionAgentRequirements, validateSourceFile, } from "../validation.js";
|
|
6
6
|
/**
|
|
7
7
|
* Compiles deploy actions for agentDefinitions manifest entries.
|
|
8
8
|
*
|
|
@@ -30,7 +30,7 @@ function resolveTomlDefinitionsSurface(agentId, scope) {
|
|
|
30
30
|
? (agent.agentDefinitionsTomlRepoSupport ??
|
|
31
31
|
agent.agentDefinitionsTomlSupport)
|
|
32
32
|
: agent.agentDefinitionsTomlSupport;
|
|
33
|
-
if (
|
|
33
|
+
if (tomlSupport?.status !== "supported")
|
|
34
34
|
return undefined;
|
|
35
35
|
return tomlSupport;
|
|
36
36
|
}
|
|
@@ -93,11 +93,20 @@ function resolveSupportedTargets(entry, targetAgents, home, repo, workspace) {
|
|
|
93
93
|
async function createAgentDefinitionActions(entry, source, supportedTargets, home, repo, workspace) {
|
|
94
94
|
const actions = [];
|
|
95
95
|
const isToml = path.extname(entry.path).toLowerCase() === ".toml";
|
|
96
|
+
// Parse the shared source document once when any supported target requires
|
|
97
|
+
// frontmatter validation, then reuse the attributes for per-agent checks.
|
|
98
|
+
const needsFrontmatter = !isToml &&
|
|
99
|
+
supportedTargets.some((target) => instructionRequiresFrontmatter(target.agentId));
|
|
100
|
+
const parsedAttributes = needsFrontmatter
|
|
101
|
+
? (await parseInstructionDocument(source, entry.path)).attributes
|
|
102
|
+
: null;
|
|
96
103
|
for (const target of supportedTargets) {
|
|
97
104
|
// TOML definition files have no YAML frontmatter — skip Markdown validation.
|
|
98
105
|
if (!isToml) {
|
|
99
106
|
validateAgentRuleMarkdownPath(entry.path, target.agentId);
|
|
100
|
-
|
|
107
|
+
if (parsedAttributes !== null) {
|
|
108
|
+
validateInstructionAgentRequirements(parsedAttributes, entry.path, target.agentId);
|
|
109
|
+
}
|
|
101
110
|
}
|
|
102
111
|
let migratedFrom;
|
|
103
112
|
if (target.agentId === "github-copilot") {
|
|
@@ -117,7 +126,7 @@ async function createAgentDefinitionActions(entry, source, supportedTargets, hom
|
|
|
117
126
|
}
|
|
118
127
|
return actions;
|
|
119
128
|
}
|
|
120
|
-
export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir,
|
|
129
|
+
export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, validateSource, detectedAgents, home, repo, workspace) {
|
|
121
130
|
const targetAgents = entry.agents.filter((agentId) => detectedAgents.includes(agentId));
|
|
122
131
|
if (targetAgents.length === 0) {
|
|
123
132
|
return { actions: [], warnings: [] };
|
|
@@ -128,7 +137,7 @@ export async function compileAgentDefinitionActions(entry, sourceDir, resolvedSo
|
|
|
128
137
|
}
|
|
129
138
|
// Validate the shared source file only when at least one target is active.
|
|
130
139
|
const source = path.resolve(sourceDir, entry.path);
|
|
131
|
-
await
|
|
140
|
+
await validateSource(source, entry.path, resolvedSourceDir);
|
|
132
141
|
await validateSourceFile(source, entry.path);
|
|
133
142
|
const actions = await createAgentDefinitionActions(entry, source, supportedTargets, home, repo, workspace);
|
|
134
143
|
return { actions, warnings };
|
|
@@ -15,8 +15,7 @@ export function compileExecutionConfigActions(entry, detectedAgents, home) {
|
|
|
15
15
|
if (!detectedAgents.includes(agentId))
|
|
16
16
|
continue;
|
|
17
17
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
18
|
-
if (
|
|
19
|
-
agent.executionConfigSupport.status !== "supported") {
|
|
18
|
+
if (agent.executionConfigSupport?.status !== "supported") {
|
|
20
19
|
warnings.push({
|
|
21
20
|
kind: "confidence",
|
|
22
21
|
message: `Agent "${agentId}" does not support modeled execution configuration surfaces — skipping "${entry.name}"`,
|
|
@@ -43,8 +42,7 @@ export function compileExecutionConfigReverts(entry, agentFilter, home) {
|
|
|
43
42
|
if (agentFilter && !agentFilter.includes(agentId))
|
|
44
43
|
continue;
|
|
45
44
|
const agent = AGENT_REGISTRY_BY_ID[agentId];
|
|
46
|
-
if (
|
|
47
|
-
agent.executionConfigSupport.status !== "supported") {
|
|
45
|
+
if (agent.executionConfigSupport?.status !== "supported") {
|
|
48
46
|
continue;
|
|
49
47
|
}
|
|
50
48
|
const rawTarget = resolvePlaceholders(agent.executionConfigSupport.path[platform], entry.name, home);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AgentDefinitionEntry, AgentRuleEntry, ExecutionConfigEntry, HookEntry, McpServerEntry, PermissionsEntry } from "../../schemas/manifest.ts";
|
|
2
2
|
import type { AgentId, ConfigPatchDeployAction, FileWriteDeployAction, FrontmatterEmitDeployAction, PlanWarning, TomlPatchDeployAction } from "../../types.ts";
|
|
3
|
+
import type { SourcePathValidator } from "../validation.ts";
|
|
3
4
|
import { compileAgentDefinitionReverts } from "./agent-definitions.ts";
|
|
4
5
|
import { compileExecutionConfigReverts } from "./execution-config.ts";
|
|
5
6
|
import { compileHookReverts } from "./hooks.ts";
|
|
@@ -12,4 +13,4 @@ export interface AdapterResult {
|
|
|
12
13
|
actions: AdapterAction[];
|
|
13
14
|
warnings: PlanWarning[];
|
|
14
15
|
}
|
|
15
|
-
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string,
|
|
16
|
+
export declare function compileAdapterActions(mcpServers: McpServerEntry[], agentRules: AgentRuleEntry[], permissions: PermissionsEntry[], sourceDir: string, resolvedSourceDir: string, validateSource: SourcePathValidator, detectedAgents: AgentId[], home: string, repo?: string, agentDefinitions?: AgentDefinitionEntry[], workspace?: string, hooks?: HookEntry[], executionConfigs?: ExecutionConfigEntry[]): Promise<AdapterResult>;
|
|
@@ -12,14 +12,14 @@ async function compileAll(entries, fn) {
|
|
|
12
12
|
warnings: results.flatMap((r) => r.warnings),
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir,
|
|
15
|
+
export async function compileAdapterActions(mcpServers, agentRules, permissions, sourceDir, resolvedSourceDir, validateSource, detectedAgents, home, repo, agentDefinitions, workspace, hooks = [], executionConfigs = []) {
|
|
16
16
|
const actions = [];
|
|
17
17
|
const warnings = [];
|
|
18
18
|
const [mcp, rules, perms, defs, hookResults, execResults] = await Promise.all([
|
|
19
19
|
compileAll(mcpServers, (entry) => compileMcpServerActions(entry, detectedAgents, home, repo, workspace)),
|
|
20
|
-
compileAll(agentRules, (entry) => compileAgentRuleActions(entry, sourceDir, resolvedSourceDir,
|
|
20
|
+
compileAll(agentRules, (entry) => compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, validateSource, detectedAgents, home, repo, workspace)),
|
|
21
21
|
compileAll(permissions, (entry) => compilePermissionsActions(entry, detectedAgents, home)),
|
|
22
|
-
compileAll(agentDefinitions ?? [], (entry) => compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir,
|
|
22
|
+
compileAll(agentDefinitions ?? [], (entry) => compileAgentDefinitionActions(entry, sourceDir, resolvedSourceDir, validateSource, detectedAgents, home, repo, workspace)),
|
|
23
23
|
compileAll(hooks, (entry) => compileHookActions(entry, detectedAgents, home)),
|
|
24
24
|
compileAll(executionConfigs, (entry) => compileExecutionConfigActions(entry, detectedAgents, home)),
|
|
25
25
|
]);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { AgentRuleEntry } from "../../schemas/manifest.ts";
|
|
2
2
|
import type { AgentId, FileWriteDeployAction, FileWriteRevertAction, PlanWarning } from "../../types.ts";
|
|
3
|
+
import { type SourcePathValidator } from "../validation.ts";
|
|
3
4
|
export interface RulesAdapterResult {
|
|
4
5
|
actions: FileWriteDeployAction[];
|
|
5
6
|
warnings: PlanWarning[];
|
|
6
7
|
}
|
|
7
|
-
export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string,
|
|
8
|
+
export declare function compileAgentRuleActions(entry: AgentRuleEntry, sourceDir: string, resolvedSourceDir: string, validateSource: SourcePathValidator, detectedAgents: AgentId[], home: string, repo?: string, workspace?: string): Promise<RulesAdapterResult>;
|
|
8
9
|
export declare function compileAgentRuleReverts(entry: AgentRuleEntry, agentFilter: AgentId[] | null, home: string, repo?: string, workspace?: string): FileWriteRevertAction[];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { planCapabilityForDeploy, resolveCapabilitySurface, } from "../capabilities.js";
|
|
3
3
|
import { getPlatformKey, resolvePlaceholders } from "../resolve.js";
|
|
4
|
-
import { validateAgentRuleMarkdownPath,
|
|
4
|
+
import { instructionRequiresFrontmatter, parseInstructionDocument, validateAgentRuleMarkdownPath, validateInstructionAgentRequirements, validateSourceFile, } from "../validation.js";
|
|
5
5
|
function requiresRepoPath(scope) {
|
|
6
6
|
return (scope === "repo" || scope === "copilot-repo" || scope === "copilot-scoped");
|
|
7
7
|
}
|
|
@@ -72,7 +72,7 @@ function resolveAgentTarget(agentId, entry, home, repo, platform, workspace, all
|
|
|
72
72
|
target: resolvePlaceholders(injectTargetDir(support?.path[platform] ?? [], entry.targetDir), entry.name, home, repo, workspace),
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir,
|
|
75
|
+
export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDir, validateSource, detectedAgents, home, repo, workspace) {
|
|
76
76
|
const actions = [];
|
|
77
77
|
const warnings = [];
|
|
78
78
|
const platform = getPlatformKey();
|
|
@@ -103,17 +103,24 @@ export async function compileAgentRuleActions(entry, sourceDir, resolvedSourceDi
|
|
|
103
103
|
// Validate the shared source file only when at least one target uses the
|
|
104
104
|
// current rules adapter surface.
|
|
105
105
|
const source = path.resolve(sourceDir, entry.path);
|
|
106
|
-
await
|
|
106
|
+
await validateSource(source, entry.path, resolvedSourceDir);
|
|
107
107
|
await validateSourceFile(source, entry.path);
|
|
108
108
|
// Native Copilot instruction files (.github/copilot-instructions.md and
|
|
109
109
|
// .github/instructions/*.instructions.md) are plain markdown and do not
|
|
110
110
|
// require agent-definition-style frontmatter (tools/instructions keys).
|
|
111
111
|
// Skip the instructionFrontmatterRequired check for these scopes.
|
|
112
112
|
const skipFrontmatterValidation = entry.scope === "copilot-repo" || entry.scope === "copilot-scoped";
|
|
113
|
+
// Parse the shared source document once when any deduped target requires
|
|
114
|
+
// frontmatter validation, then reuse the attributes for per-agent checks.
|
|
115
|
+
const needsFrontmatter = !skipFrontmatterValidation &&
|
|
116
|
+
dedupedTargets.some((target) => instructionRequiresFrontmatter(target.agentId));
|
|
117
|
+
const parsedAttributes = needsFrontmatter
|
|
118
|
+
? (await parseInstructionDocument(source, entry.path)).attributes
|
|
119
|
+
: null;
|
|
113
120
|
for (const target of dedupedTargets) {
|
|
114
121
|
validateAgentRuleMarkdownPath(entry.path, target.agentId);
|
|
115
|
-
if (!skipFrontmatterValidation) {
|
|
116
|
-
|
|
122
|
+
if (!skipFrontmatterValidation && parsedAttributes !== null) {
|
|
123
|
+
validateInstructionAgentRequirements(parsedAttributes, entry.path, target.agentId);
|
|
117
124
|
}
|
|
118
125
|
actions.push({
|
|
119
126
|
kind: "file-write",
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const DEFAULT_BOUNDED_CONCURRENCY: number;
|
|
2
|
+
export declare function mapConcurrentOrdered<T, R>(items: readonly T[], worker: (item: T, index: number) => Promise<R>, options?: {
|
|
3
|
+
concurrency?: number;
|
|
4
|
+
signal?: AbortSignal;
|
|
5
|
+
}): Promise<Array<R | undefined>>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
export const DEFAULT_BOUNDED_CONCURRENCY = Math.max(1, Math.min(8, os.availableParallelism()));
|
|
3
|
+
export async function mapConcurrentOrdered(items, worker, options = {}) {
|
|
4
|
+
const { signal } = options;
|
|
5
|
+
const concurrency = Math.max(1, Math.min(options.concurrency ?? DEFAULT_BOUNDED_CONCURRENCY, items.length));
|
|
6
|
+
const results = new Array(items.length);
|
|
7
|
+
let nextIndex = 0;
|
|
8
|
+
async function runWorker() {
|
|
9
|
+
while (nextIndex < items.length) {
|
|
10
|
+
if (signal?.aborted)
|
|
11
|
+
return;
|
|
12
|
+
const index = nextIndex;
|
|
13
|
+
nextIndex += 1;
|
|
14
|
+
results[index] = await worker(items[index], index);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
await Promise.all(Array.from({ length: concurrency }, () => runWorker()));
|
|
18
|
+
return results;
|
|
19
|
+
}
|