@oisincoveney/pipeline 1.20.0 → 1.21.0
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 +41 -43
- package/dist/commands/pipeline-command.js +0 -1
- package/dist/config.d.ts +19 -3
- package/dist/config.js +91 -10
- package/dist/gates.js +22 -6
- package/dist/hooks.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -22
- package/dist/install-commands.js +8 -2
- package/dist/mcp/gateway.js +101 -1
- package/dist/mcp/repo-local-backends.js +70 -0
- package/dist/mcp/toolhive-vmcp.js +57 -0
- package/dist/pipeline-init.js +10 -256
- package/dist/pipeline-runtime.d.ts +2 -2
- package/dist/pipeline-runtime.js +183 -4
- package/dist/runner-event-sink.js +27 -7
- package/dist/runner-job/delivery.js +64 -15
- package/dist/runner-job/pr-summary.js +117 -0
- package/dist/runner-job/run.js +63 -19
- package/dist/runner-job/workspace.js +39 -5
- package/dist/runner-output.js +21 -19
- package/dist/runner.d.ts +1 -1
- package/dist/runner.js +15 -3
- package/dist/runtime/agent-node/agent-node.js +31 -5
- package/dist/runtime/builtins/builtins.js +11 -4
- package/dist/runtime/context/context.js +1 -0
- package/dist/runtime/contracts/contracts.d.ts +17 -1
- package/dist/runtime/contracts/index.d.ts +1 -1
- package/dist/runtime/events/events.js +45 -1
- package/dist/runtime/json-validation/json-validation.js +8 -2
- package/dist/runtime/parallel-node/parallel-node.js +1 -0
- package/dist/schedule-planner.js +168 -5
- package/dist/standard-output-schemas.js +80 -0
- package/docs/config-architecture.md +13 -15
- package/docs/mcp-gateway.md +30 -9
- package/docs/mcp-host-isolation.md +2 -1
- package/docs/operator-guide.md +60 -18
- package/docs/pipeline-console-runner-contract.md +14 -14
- package/docs/pipeline-smoke-recovery-plan.md +7 -8
- package/docs/slash-command-adapter-contract.md +5 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
# @oisincoveney/pipeline
|
|
2
2
|
|
|
3
|
-
Config-driven multi-agent pipeline runner for repository work.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
artifacts.
|
|
3
|
+
Config-driven multi-agent pipeline runner for repository work. Runtime config is
|
|
4
|
+
owned by the installed `@oisincoveney/pipeline` package. Repo-local `.pipeline`
|
|
5
|
+
paths are runtime artifact locations only; they are not the runtime config
|
|
6
|
+
source.
|
|
8
7
|
|
|
9
8
|
## Requirements
|
|
10
9
|
|
|
11
10
|
- Bun 1.1 or newer
|
|
12
11
|
- Node.js 22.13 or newer
|
|
13
|
-
- `npx`, `backlog`, `uvx`, and Docker on `PATH` for default skills and MCP setup
|
|
12
|
+
- `npx`, `backlog`, `uvx`, and Docker on `PATH` for default skills and MCP gateway setup
|
|
14
13
|
- At least one configured runner CLI on `PATH`: `codex`, `opencode`, `kimi`,
|
|
15
14
|
`pi`, or a declared command runner
|
|
16
15
|
|
|
@@ -22,33 +21,31 @@ bun install --frozen-lockfile
|
|
|
22
21
|
|
|
23
22
|
## Start A Repository
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
Initialize package-owned pipeline support:
|
|
26
25
|
|
|
27
26
|
```shell
|
|
28
27
|
pipe init
|
|
29
28
|
```
|
|
30
29
|
|
|
31
30
|
`pipe init` installs default project skills with
|
|
32
|
-
`npx skills add oisincoveney/skills
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`
|
|
39
|
-
|
|
40
|
-
The default GitHub MCP registration uses GitHub's official container in
|
|
41
|
-
read-only mode and reads `GITHUB_PERSONAL_ACCESS_TOKEN` from the environment.
|
|
42
|
-
The Momokaya gateway endpoint is protected by Traefik HTTP Basic auth. Set
|
|
43
|
-
`PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP `Authorization` header
|
|
44
|
-
value before starting Codex or OpenCode:
|
|
31
|
+
`npx skills add oisincoveney/skills`, then writes generated Codex and OpenCode
|
|
32
|
+
command surfaces plus their singleton `pipeline-gateway` MCP entries. It does
|
|
33
|
+
not create repo-local `.pipeline` config files.
|
|
34
|
+
|
|
35
|
+
The default MCP gateway can run locally or point at the hosted Momokaya
|
|
36
|
+
gateway. Set `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP
|
|
37
|
+
`Authorization` header value before starting Codex or OpenCode when using a
|
|
38
|
+
protected gateway:
|
|
45
39
|
|
|
46
40
|
```shell
|
|
47
41
|
export PIPELINE_MCP_GATEWAY_AUTHORIZATION="Basic $(printf '%s' 'user:password' | base64)"
|
|
48
42
|
```
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
To refresh or check generated host files later, use:
|
|
45
|
+
|
|
46
|
+
```shell
|
|
47
|
+
pipe install-commands --host all --check
|
|
48
|
+
```
|
|
52
49
|
|
|
53
50
|
Check local prerequisites and config health:
|
|
54
51
|
|
|
@@ -136,9 +133,12 @@ validation, contract-version checks, and JSON Schema generation.
|
|
|
136
133
|
Use `PIPELINE_TARGET_PATH=/path/to/worktree` when the checked-out target repo is
|
|
137
134
|
mounted somewhere other than the process working directory.
|
|
138
135
|
|
|
139
|
-
##
|
|
136
|
+
## Custom YAML Parts
|
|
137
|
+
|
|
138
|
+
Runtime execution uses package-owned defaults. Tests and advanced embedding code
|
|
139
|
+
can still parse explicit YAML parts with `parsePipelineConfigParts()`.
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
`runners`:
|
|
142
142
|
|
|
143
143
|
```yaml
|
|
144
144
|
version: 1
|
|
@@ -156,7 +156,7 @@ runners:
|
|
|
156
156
|
output_formats: [text, json, jsonl, json_schema]
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
`profiles`:
|
|
160
160
|
|
|
161
161
|
```yaml
|
|
162
162
|
version: 1
|
|
@@ -182,7 +182,7 @@ profiles:
|
|
|
182
182
|
format: text
|
|
183
183
|
```
|
|
184
184
|
|
|
185
|
-
|
|
185
|
+
Example workflow shape:
|
|
186
186
|
|
|
187
187
|
```yaml
|
|
188
188
|
version: 1
|
|
@@ -215,14 +215,12 @@ workflows:
|
|
|
215
215
|
builtin: typecheck
|
|
216
216
|
```
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
when both are set.
|
|
218
|
+
Package-owned defaults declare `entrypoints` that expose stable app or CLI names
|
|
219
|
+
resolving to workflows or schedule policies. Direct `--workflow` selection
|
|
220
|
+
remains available and takes precedence over `--entrypoint` when both are set.
|
|
222
221
|
|
|
223
|
-
The
|
|
224
|
-
workflow. See `docs/config-architecture.md` for
|
|
225
|
-
support matrix.
|
|
222
|
+
The package defaults include a full research, red, green, verify, learn
|
|
223
|
+
workflow. See `docs/config-architecture.md` for the host support matrix.
|
|
226
224
|
|
|
227
225
|
### Structural Parallelism
|
|
228
226
|
|
|
@@ -292,22 +290,22 @@ branches share a base SHA, and merges passing branches into an integration
|
|
|
292
290
|
branch in declaration order. It reports merge conflicts; it does not resolve
|
|
293
291
|
them automatically.
|
|
294
292
|
|
|
295
|
-
Default profile skills
|
|
296
|
-
Runtime MCP projection and host-specific isolation policy live in
|
|
297
|
-
[`docs/mcp-host-isolation.md`](docs/mcp-host-isolation.md) and
|
|
293
|
+
Default profile skills and generated host resources are installed by
|
|
294
|
+
`pipe init`. Runtime MCP projection and host-specific isolation policy live in
|
|
295
|
+
`src/mcp`; see [`docs/mcp-host-isolation.md`](docs/mcp-host-isolation.md) and
|
|
298
296
|
[`docs/mcp-gateway.md`](docs/mcp-gateway.md).
|
|
299
297
|
|
|
300
298
|
## Generated Host Resources
|
|
301
299
|
|
|
302
|
-
Generate native host files
|
|
300
|
+
Generate native host files during setup:
|
|
303
301
|
|
|
304
302
|
```shell
|
|
305
|
-
pipe
|
|
303
|
+
pipe init
|
|
306
304
|
```
|
|
307
305
|
|
|
308
|
-
Generated resources are derived from
|
|
309
|
-
|
|
310
|
-
|
|
306
|
+
Generated resources are derived from package-owned config; they are not separate
|
|
307
|
+
sources of truth. Host resources use exact native agents when the node runner
|
|
308
|
+
matches the host. OpenCode also uses native subagents for cross-runner
|
|
311
309
|
model-backed nodes when the runner/profile provides an OpenCode-compatible
|
|
312
310
|
`model` or `host_models.opencode` value. Otherwise generated instructions
|
|
313
311
|
dispatch to that runner's CLI instead of inventing a host model.
|
|
@@ -336,8 +334,8 @@ runners:
|
|
|
336
334
|
|
|
337
335
|
## Runtime Guarantees
|
|
338
336
|
|
|
339
|
-
- `pipe run`
|
|
340
|
-
`.pipeline
|
|
337
|
+
- `pipe run` loads package-owned `@oisincoveney/pipeline` config even when the
|
|
338
|
+
repository has no repo-local `.pipeline` config files.
|
|
341
339
|
- Multi-agent workflows execute as separate agent boundaries; nodes are not
|
|
342
340
|
merged into one prompt.
|
|
343
341
|
- Native subagent strategy is preferred when the selected runner can represent
|
|
@@ -12,7 +12,6 @@ const BUILTIN_PIPE_COMMANDS = new Set([
|
|
|
12
12
|
]);
|
|
13
13
|
function registerConfiguredEntrypointCommands(program, config, runEntrypoint) {
|
|
14
14
|
const registered = /* @__PURE__ */ new Set();
|
|
15
|
-
if (!config) return registered;
|
|
16
15
|
const reservedCommands = new Set(program.commands.map((command) => command.name()));
|
|
17
16
|
for (const [id, entrypoint] of Object.entries(config.entrypoints)) {
|
|
18
17
|
if (reservedCommands.has(id)) continue;
|
package/dist/config.d.ts
CHANGED
|
@@ -10,11 +10,13 @@ declare const HOOK_EVENTS: readonly ["workflow.start", "workflow.success", "work
|
|
|
10
10
|
declare const GATE_KINDS: readonly ["acceptance", "artifact", "builtin", "changed_files", "command", "json_schema", "verdict"];
|
|
11
11
|
declare const SCHEDULE_BASELINES: readonly ["epic", "pipe"];
|
|
12
12
|
declare const SCHEDULING_ROLES: readonly ["coverage", "implementation"];
|
|
13
|
+
declare const MCP_GATEWAY_BACKEND_LOCALITIES: readonly ["repo-local", "repo-scoped-remote", "shared-remote"];
|
|
14
|
+
declare const MCP_GATEWAY_WORKSPACE_PATH_SOURCES: readonly ["PIPELINE_TARGET_PATH", "cwd"];
|
|
13
15
|
declare const DEFAULT_RUNNER_JOB_GIT_COMMITTER: {
|
|
14
16
|
readonly email: "git@oisin.ee";
|
|
15
17
|
readonly name: "oisin-bot";
|
|
16
18
|
};
|
|
17
|
-
type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "
|
|
19
|
+
type PipelineConfigErrorCode = "PIPELINE_CONFIG_LEGACY_UNSUPPORTED" | "PIPELINE_CONFIG_PARSE_ERROR" | "PIPELINE_CONFIG_VALIDATION_ERROR";
|
|
18
20
|
interface PipelineConfigIssue {
|
|
19
21
|
message: string;
|
|
20
22
|
path?: string;
|
|
@@ -237,6 +239,19 @@ declare const configSchema: z.ZodObject<{
|
|
|
237
239
|
}, z.core.$strict>>;
|
|
238
240
|
}, z.core.$strict>>;
|
|
239
241
|
mcp_gateway: z.ZodOptional<z.ZodObject<{
|
|
242
|
+
backends: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
243
|
+
locality: z.ZodEnum<{
|
|
244
|
+
"repo-local": "repo-local";
|
|
245
|
+
"repo-scoped-remote": "repo-scoped-remote";
|
|
246
|
+
"shared-remote": "shared-remote";
|
|
247
|
+
}>;
|
|
248
|
+
required: z.ZodDefault<z.ZodBoolean>;
|
|
249
|
+
tool_prefixes: z.ZodArray<z.ZodString>;
|
|
250
|
+
workspace_path_source: z.ZodOptional<z.ZodEnum<{
|
|
251
|
+
PIPELINE_TARGET_PATH: "PIPELINE_TARGET_PATH";
|
|
252
|
+
cwd: "cwd";
|
|
253
|
+
}>>;
|
|
254
|
+
}, z.core.$strict>>>;
|
|
240
255
|
default_profile: z.ZodOptional<z.ZodString>;
|
|
241
256
|
mode: z.ZodEnum<{
|
|
242
257
|
hosted: "hosted";
|
|
@@ -411,6 +426,8 @@ type HookEvent = (typeof HOOK_EVENTS)[number];
|
|
|
411
426
|
type GateKind = (typeof GATE_KINDS)[number];
|
|
412
427
|
type ScheduleBaseline = (typeof SCHEDULE_BASELINES)[number];
|
|
413
428
|
type SchedulingRole = (typeof SCHEDULING_ROLES)[number];
|
|
429
|
+
type McpGatewayBackendLocality = (typeof MCP_GATEWAY_BACKEND_LOCALITIES)[number];
|
|
430
|
+
type McpGatewayWorkspacePathSource = (typeof MCP_GATEWAY_WORKSPACE_PATH_SOURCES)[number];
|
|
414
431
|
interface PipelineConfigParts {
|
|
415
432
|
pipeline: string;
|
|
416
433
|
profiles: string;
|
|
@@ -421,9 +438,8 @@ interface PipelineConfigValidationOptions {
|
|
|
421
438
|
}
|
|
422
439
|
declare function loadPipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
423
440
|
declare function loadPackagePipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
424
|
-
declare function tryLoadPipelineConfig(projectRoot: string, options?: PipelineConfigValidationOptions): PipelineConfig | null;
|
|
425
441
|
declare function parsePipelineConfigYaml(source: string, sourcePath?: string, projectRoot?: string): PipelineConfig;
|
|
426
442
|
declare function parsePipelineConfigParts(sources: PipelineConfigParts, projectRoot?: string, sourcePaths?: PipelineConfigParts, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
427
443
|
declare function validatePipelineConfig(rawConfig: PipelineConfig, projectRoot?: string, options?: PipelineConfigValidationOptions): PipelineConfig;
|
|
428
444
|
//#endregion
|
|
429
|
-
export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, GateKind, HookEvent, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, SchedulingRole, WorkflowNodeKind, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml,
|
|
445
|
+
export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, GateKind, HookEvent, McpGatewayBackendLocality, McpGatewayWorkspacePathSource, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfig, PipelineConfigError, PipelineConfigErrorCode, PipelineConfigIssue, PipelineConfigParts, PipelineConfigValidationOptions, RUNNERS_CONFIG_PATH, RunnerType, ScheduleBaseline, SchedulingRole, WorkflowNodeKind, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, validatePipelineConfig, workflowSchema };
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveFileReference } from "./path-refs.js";
|
|
2
|
+
import { standardOutputSchemaNameFromPath } from "./standard-output-schemas.js";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { parseDocument } from "yaml";
|
|
4
5
|
import { z } from "zod";
|
|
@@ -54,6 +55,12 @@ const RETRY_REASONS = [
|
|
|
54
55
|
];
|
|
55
56
|
const SCHEDULE_BASELINES = ["epic", "pipe"];
|
|
56
57
|
const SCHEDULING_ROLES = ["coverage", "implementation"];
|
|
58
|
+
const MCP_GATEWAY_BACKEND_LOCALITIES = [
|
|
59
|
+
"repo-local",
|
|
60
|
+
"repo-scoped-remote",
|
|
61
|
+
"shared-remote"
|
|
62
|
+
];
|
|
63
|
+
const MCP_GATEWAY_WORKSPACE_PATH_SOURCES = ["PIPELINE_TARGET_PATH", "cwd"];
|
|
57
64
|
const PIPELINE_GATEWAY_SERVER_ID = "pipeline-gateway";
|
|
58
65
|
const DEFAULT_RUNNER_JOB_GIT_COMMITTER = {
|
|
59
66
|
email: "git@oisin.ee",
|
|
@@ -104,6 +111,29 @@ mcp_gateway:
|
|
|
104
111
|
url_env: PIPELINE_MCP_GATEWAY_URL
|
|
105
112
|
authorization_env: PIPELINE_MCP_GATEWAY_AUTHORIZATION
|
|
106
113
|
default_profile: default
|
|
114
|
+
backends:
|
|
115
|
+
context7:
|
|
116
|
+
locality: shared-remote
|
|
117
|
+
tool_prefixes: [context7]
|
|
118
|
+
uidotsh:
|
|
119
|
+
locality: shared-remote
|
|
120
|
+
tool_prefixes: [uidotsh]
|
|
121
|
+
qdrant:
|
|
122
|
+
locality: repo-scoped-remote
|
|
123
|
+
tool_prefixes: [qdrant]
|
|
124
|
+
fallow:
|
|
125
|
+
locality: repo-local
|
|
126
|
+
workspace_path_source: PIPELINE_TARGET_PATH
|
|
127
|
+
required: false
|
|
128
|
+
tool_prefixes: [fallow]
|
|
129
|
+
serena:
|
|
130
|
+
locality: repo-local
|
|
131
|
+
workspace_path_source: PIPELINE_TARGET_PATH
|
|
132
|
+
tool_prefixes: [serena]
|
|
133
|
+
backlog:
|
|
134
|
+
locality: repo-local
|
|
135
|
+
workspace_path_source: PIPELINE_TARGET_PATH
|
|
136
|
+
tool_prefixes: [backlog]
|
|
107
137
|
profiles:
|
|
108
138
|
orchestrator:
|
|
109
139
|
runner: codex
|
|
@@ -121,6 +151,10 @@ profiles:
|
|
|
121
151
|
tools: [read, list, grep, glob, bash]
|
|
122
152
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
123
153
|
network: { mode: inherit }
|
|
154
|
+
output:
|
|
155
|
+
format: json_schema
|
|
156
|
+
schema_path: .pipeline/schemas/research.schema.json
|
|
157
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
124
158
|
pipeline-inspector:
|
|
125
159
|
runner: codex
|
|
126
160
|
description: Inspect the repository without modifying files.
|
|
@@ -139,12 +173,17 @@ profiles:
|
|
|
139
173
|
network: { mode: inherit }
|
|
140
174
|
pipeline-test-writer:
|
|
141
175
|
runner: codex
|
|
176
|
+
scheduling_roles: [implementation]
|
|
142
177
|
description: Add focused failing tests for the requested behavior.
|
|
143
|
-
instructions: { inline: "Add focused failing tests for the requested behavior only. Do not change production code." }
|
|
178
|
+
instructions: { inline: "Add focused failing tests for the requested behavior only. Do not change production code. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
|
|
144
179
|
mcp_servers: [pipeline-gateway]
|
|
145
180
|
tools: [read, list, grep, glob, bash, edit, write]
|
|
146
181
|
filesystem: { mode: workspace-write, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
147
182
|
network: { mode: inherit }
|
|
183
|
+
output:
|
|
184
|
+
format: json_schema
|
|
185
|
+
schema_path: .pipeline/schemas/implementation.schema.json
|
|
186
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
148
187
|
pipeline-epic-router:
|
|
149
188
|
runner: codex
|
|
150
189
|
description: Route epic sub-tickets into fixed implementation tracks.
|
|
@@ -157,11 +196,15 @@ profiles:
|
|
|
157
196
|
runner: codex
|
|
158
197
|
scheduling_roles: [implementation]
|
|
159
198
|
description: Implement production code until the failing tests pass.
|
|
160
|
-
instructions: { inline: "Implement the smallest production change that satisfies the failing tests." }
|
|
199
|
+
instructions: { inline: "Implement the smallest production change that satisfies the failing tests. Return only valid JSON with top-level changes and verification. Every changes entry must include summary, why, and files. Include risks, followups, and lessons when present. Do not use Markdown fences or prose outside the JSON object." }
|
|
161
200
|
mcp_servers: [pipeline-gateway]
|
|
162
201
|
tools: [read, list, grep, glob, bash, edit, write]
|
|
163
202
|
filesystem: { mode: workspace-write, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
164
203
|
network: { mode: inherit }
|
|
204
|
+
output:
|
|
205
|
+
format: json_schema
|
|
206
|
+
schema_path: .pipeline/schemas/implementation.schema.json
|
|
207
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
165
208
|
pipeline-acceptance-reviewer:
|
|
166
209
|
runner: codex
|
|
167
210
|
scheduling_roles: [coverage]
|
|
@@ -171,6 +214,10 @@ profiles:
|
|
|
171
214
|
tools: [read, list, grep, glob, bash]
|
|
172
215
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
173
216
|
network: { mode: inherit }
|
|
217
|
+
output:
|
|
218
|
+
format: json_schema
|
|
219
|
+
schema_path: .pipeline/schemas/acceptance.schema.json
|
|
220
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
174
221
|
pipeline-thermo-nuclear-reviewer:
|
|
175
222
|
runner: codex
|
|
176
223
|
scheduling_roles: [coverage]
|
|
@@ -180,6 +227,10 @@ profiles:
|
|
|
180
227
|
tools: [read, list, grep, glob, bash]
|
|
181
228
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
182
229
|
network: { mode: inherit }
|
|
230
|
+
output:
|
|
231
|
+
format: json_schema
|
|
232
|
+
schema_path: .pipeline/schemas/review.schema.json
|
|
233
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
183
234
|
pipeline-verifier:
|
|
184
235
|
runner: codex
|
|
185
236
|
scheduling_roles: [coverage]
|
|
@@ -189,6 +240,10 @@ profiles:
|
|
|
189
240
|
tools: [read, list, grep, glob, bash]
|
|
190
241
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
191
242
|
network: { mode: inherit }
|
|
243
|
+
output:
|
|
244
|
+
format: json_schema
|
|
245
|
+
schema_path: .pipeline/schemas/verify.schema.json
|
|
246
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
192
247
|
pipeline-learner:
|
|
193
248
|
runner: codex
|
|
194
249
|
description: Store durable lessons from the completed run.
|
|
@@ -197,6 +252,10 @@ profiles:
|
|
|
197
252
|
tools: [read, list, grep, glob, bash]
|
|
198
253
|
filesystem: { mode: read-only, allow: ["**/*"], deny: ["node_modules/**", "dist/**", ".git/**"] }
|
|
199
254
|
network: { mode: inherit }
|
|
255
|
+
output:
|
|
256
|
+
format: json_schema
|
|
257
|
+
schema_path: .pipeline/schemas/learn.schema.json
|
|
258
|
+
repair: { enabled: true, max_attempts: 1 }
|
|
200
259
|
`;
|
|
201
260
|
const PACKAGE_DEFAULT_PIPELINE_YAML = `version: 1
|
|
202
261
|
default_workflow: default
|
|
@@ -419,7 +478,28 @@ const mcpServerSchema = z.object({
|
|
|
419
478
|
path: ["bearer_token_env_var"]
|
|
420
479
|
});
|
|
421
480
|
});
|
|
481
|
+
const mcpGatewayBackendSchema = z.object({
|
|
482
|
+
locality: z.enum(MCP_GATEWAY_BACKEND_LOCALITIES),
|
|
483
|
+
required: z.boolean().default(true),
|
|
484
|
+
tool_prefixes: z.array(z.string().min(1)).min(1),
|
|
485
|
+
workspace_path_source: z.enum(MCP_GATEWAY_WORKSPACE_PATH_SOURCES).optional()
|
|
486
|
+
}).strict().superRefine((backend, ctx) => {
|
|
487
|
+
if (backend.locality === "repo-local") {
|
|
488
|
+
if (!backend.workspace_path_source) ctx.addIssue({
|
|
489
|
+
code: "custom",
|
|
490
|
+
message: "repo-local gateway backend must declare workspace_path_source as PIPELINE_TARGET_PATH or cwd",
|
|
491
|
+
path: ["workspace_path_source"]
|
|
492
|
+
});
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
if (backend.workspace_path_source) ctx.addIssue({
|
|
496
|
+
code: "custom",
|
|
497
|
+
message: "workspace_path_source is only valid for repo-local gateway backends",
|
|
498
|
+
path: ["workspace_path_source"]
|
|
499
|
+
});
|
|
500
|
+
});
|
|
422
501
|
const mcpGatewaySchema = z.object({
|
|
502
|
+
backends: strictRecord(mcpGatewayBackendSchema).default({}),
|
|
423
503
|
default_profile: z.string().min(1).optional(),
|
|
424
504
|
mode: z.enum(["hosted", "local"]),
|
|
425
505
|
provider: z.literal("toolhive"),
|
|
@@ -804,9 +884,6 @@ function loadPackagePipelineConfig(projectRoot, options = {}) {
|
|
|
804
884
|
runners: "@oisincoveney/pipeline/defaults/runners.yaml"
|
|
805
885
|
}, options);
|
|
806
886
|
}
|
|
807
|
-
function tryLoadPipelineConfig(projectRoot, options = {}) {
|
|
808
|
-
return loadPipelineConfig(projectRoot, options);
|
|
809
|
-
}
|
|
810
887
|
function parsePipelineConfigYaml(source, sourcePath = PIPELINE_CONFIG_PATH, projectRoot) {
|
|
811
888
|
return parsePipelineConfigParts({
|
|
812
889
|
pipeline: source,
|
|
@@ -950,10 +1027,13 @@ function validateActor(label, path, actor, runner, config, issues, projectRoot,
|
|
|
950
1027
|
validatePath(`${path}.instructions.path`, actor.instructions.path, projectRoot, issues, options);
|
|
951
1028
|
validateReferences(`${path}.rules`, actor.rules, config.rules, "rule", issues);
|
|
952
1029
|
validateReferences(`${path}.skills`, actor.skills, config.skills, "skill", issues);
|
|
953
|
-
validateReferences(`${path}.mcp_servers`, actor.mcp_servers, config.mcp_gateway ? {
|
|
954
|
-
|
|
955
|
-
[
|
|
956
|
-
|
|
1030
|
+
validateReferences(`${path}.mcp_servers`, actor.mcp_servers, config.mcp_gateway ? { [PIPELINE_GATEWAY_SERVER_ID]: {} } : config.mcp_servers, "MCP server", issues);
|
|
1031
|
+
if (config.mcp_gateway) {
|
|
1032
|
+
for (const serverId of actor.mcp_servers ?? []) if (serverId !== PIPELINE_GATEWAY_SERVER_ID) issues.push({
|
|
1033
|
+
path: `${path}.mcp_servers`,
|
|
1034
|
+
message: `${path}.mcp_servers must only reference ${PIPELINE_GATEWAY_SERVER_ID} when mcp_gateway is configured`
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
957
1037
|
validateBooleanCapability(`${path}.rules`, actor.rules, runner.capabilities.rules, "rules", issues);
|
|
958
1038
|
validateBooleanCapability(`${path}.skills`, actor.skills, runner.capabilities.skills, "skills", issues);
|
|
959
1039
|
validateBooleanCapability(`${path}.mcp_servers`, actor.mcp_servers, runner.capabilities.mcp_servers, "MCP servers", issues);
|
|
@@ -1052,6 +1132,7 @@ function validateListCapability(path, requested, supported, label, issues) {
|
|
|
1052
1132
|
}
|
|
1053
1133
|
function validatePath(path, value, projectRoot, issues, options = {}) {
|
|
1054
1134
|
if (!(value && projectRoot)) return;
|
|
1135
|
+
if (standardOutputSchemaNameFromPath(value)) return;
|
|
1055
1136
|
if (!existsSync(resolveFileReference(projectRoot, value))) {
|
|
1056
1137
|
if (options.allowMissingLintFileReferences && isLintableMissingFileReferencePath(path)) return;
|
|
1057
1138
|
issues.push({
|
|
@@ -1070,4 +1151,4 @@ function validationError(issues) {
|
|
|
1070
1151
|
return new PipelineConfigError("PIPELINE_CONFIG_VALIDATION_ERROR", ["Invalid pipeline config:", ...issues.map((issue) => issue.path ? `- ${issue.path}: ${issue.message}` : `- ${issue.message}`)].join("\n"), issues);
|
|
1071
1152
|
}
|
|
1072
1153
|
//#endregion
|
|
1073
|
-
export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfigError, RUNNERS_CONFIG_PATH, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml,
|
|
1154
|
+
export { DEFAULT_RUNNER_JOB_GIT_COMMITTER, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, PipelineConfigError, RUNNERS_CONFIG_PATH, loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml, validatePipelineConfig, workflowSchema };
|
package/dist/gates.js
CHANGED
|
@@ -12,6 +12,9 @@ function parseFailingTests(output) {
|
|
|
12
12
|
return m ? [m[1].trim()] : [];
|
|
13
13
|
});
|
|
14
14
|
}
|
|
15
|
+
function displayCommand(command) {
|
|
16
|
+
return [command.command, ...command.args].join(" ");
|
|
17
|
+
}
|
|
15
18
|
function readPackageScripts(worktreePath) {
|
|
16
19
|
try {
|
|
17
20
|
return parseJson(readFileSync(join(worktreePath, "package.json"), "utf-8"), "package.json").scripts ?? {};
|
|
@@ -55,14 +58,16 @@ async function runTests(worktreePath, signal) {
|
|
|
55
58
|
});
|
|
56
59
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
57
60
|
return {
|
|
61
|
+
command: displayCommand(projectCommand),
|
|
58
62
|
exitCode: result.exitCode ?? 0,
|
|
59
63
|
output,
|
|
60
64
|
failingTests: []
|
|
61
65
|
};
|
|
62
66
|
} catch (err) {
|
|
63
67
|
const e = err;
|
|
64
|
-
const output = [e.stdout, e.stderr].filter(Boolean).join("\n");
|
|
68
|
+
const output = [e.stdout, e.stderr].filter(Boolean).join("\n") || [e.shortMessage, e.message].filter(Boolean).join("\n");
|
|
65
69
|
return {
|
|
70
|
+
command: displayCommand(projectCommand),
|
|
66
71
|
exitCode: e.exitCode ?? 1,
|
|
67
72
|
output,
|
|
68
73
|
failingTests: parseFailingTests(output)
|
|
@@ -83,26 +88,35 @@ async function runTypecheck(worktreePath, signal) {
|
|
|
83
88
|
});
|
|
84
89
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
85
90
|
return {
|
|
91
|
+
command: displayCommand(projectCommand),
|
|
86
92
|
exitCode: result.exitCode ?? 0,
|
|
87
93
|
output
|
|
88
94
|
};
|
|
89
95
|
} catch (err) {
|
|
90
96
|
const e = err;
|
|
91
|
-
const output = [e.stdout, e.stderr].filter(Boolean).join("\n");
|
|
97
|
+
const output = [e.stdout, e.stderr].filter(Boolean).join("\n") || [e.shortMessage, e.message].filter(Boolean).join("\n");
|
|
92
98
|
return {
|
|
99
|
+
command: displayCommand(projectCommand),
|
|
93
100
|
exitCode: e.exitCode ?? 1,
|
|
94
101
|
output
|
|
95
102
|
};
|
|
96
103
|
}
|
|
97
104
|
}
|
|
98
|
-
async function runSemgrep(worktreePath, signal) {
|
|
99
|
-
const
|
|
105
|
+
async function runSemgrep(worktreePath, signal, changedFiles) {
|
|
106
|
+
const overrideCommand = envCommand("PIPELINE_SEMGREP_COMMAND");
|
|
107
|
+
const targets = changedFiles ? [...new Set(changedFiles)].filter((file) => existsSync(join(worktreePath, file))) : void 0;
|
|
108
|
+
if (!overrideCommand && targets && targets.length === 0) return {
|
|
109
|
+
command: "uvx semgrep scan --config=p/ci --error",
|
|
110
|
+
exitCode: 0,
|
|
111
|
+
output: "skipped: no changed files to scan"
|
|
112
|
+
};
|
|
113
|
+
const projectCommand = overrideCommand ?? {
|
|
100
114
|
args: [
|
|
101
115
|
"semgrep",
|
|
102
116
|
"scan",
|
|
103
117
|
"--config=p/ci",
|
|
104
118
|
"--error",
|
|
105
|
-
"."
|
|
119
|
+
...targets ? ["--", ...targets] : ["."]
|
|
106
120
|
],
|
|
107
121
|
command: "uvx"
|
|
108
122
|
};
|
|
@@ -114,13 +128,15 @@ async function runSemgrep(worktreePath, signal) {
|
|
|
114
128
|
});
|
|
115
129
|
const output = [result.stdout, result.stderr].filter(Boolean).join("\n");
|
|
116
130
|
return {
|
|
131
|
+
command: displayCommand(projectCommand),
|
|
117
132
|
exitCode: result.exitCode ?? 0,
|
|
118
133
|
output
|
|
119
134
|
};
|
|
120
135
|
} catch (err) {
|
|
121
136
|
const e = err;
|
|
122
|
-
const output = [e.stdout, e.stderr].filter(Boolean).join("\n");
|
|
137
|
+
const output = [e.stdout, e.stderr].filter(Boolean).join("\n") || [e.shortMessage, e.message].filter(Boolean).join("\n");
|
|
123
138
|
return {
|
|
139
|
+
command: displayCommand(projectCommand),
|
|
124
140
|
exitCode: e.exitCode ?? 1,
|
|
125
141
|
output
|
|
126
142
|
};
|
package/dist/hooks.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ declare const hookResultSchema: z.ZodObject<{
|
|
|
13
13
|
taskContext: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
14
|
}, z.core.$strict>>;
|
|
15
15
|
status: z.ZodEnum<{
|
|
16
|
-
pass: "pass";
|
|
17
16
|
fail: "fail";
|
|
17
|
+
pass: "pass";
|
|
18
18
|
skip: "skip";
|
|
19
19
|
}>;
|
|
20
20
|
summary: z.ZodOptional<z.ZodString>;
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ interface PipeOptions {
|
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
12
|
* Config-driven `pipe` entrypoint. Package-owned defaults are the source of
|
|
13
|
-
* truth; repo-local pipeline files are
|
|
13
|
+
* truth; repo-local pipeline files are ignored by runtime loading.
|
|
14
14
|
*/
|
|
15
15
|
declare function pipe(description: string, options?: PipeOptions): Promise<void>;
|
|
16
16
|
interface DoctorCheck {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { standardOutputSchemaNameFromPath } from "./standard-output-schemas.js";
|
|
3
|
+
import { PipelineConfigError, loadPipelineConfig } from "./config.js";
|
|
3
4
|
import { BUILTIN_PIPE_COMMANDS, registerConfiguredEntrypointCommands } from "./commands/pipeline-command.js";
|
|
4
|
-
import { configureGatewayHosts, localGatewayStatus, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "./mcp/gateway.js";
|
|
5
|
+
import { configureGatewayHosts, localGatewayStatus, reconcileGateway, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "./mcp/gateway.js";
|
|
5
6
|
import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "./runner.js";
|
|
6
7
|
import { compileWorkflowPlan } from "./workflow-planner.js";
|
|
7
8
|
import { formatConfigError, runPipelineFromConfig } from "./pipeline-runtime.js";
|
|
@@ -19,7 +20,7 @@ const PATH_SEPARATOR_RE = /[\\/]/;
|
|
|
19
20
|
const LINE_RE = /\r?\n/;
|
|
20
21
|
/**
|
|
21
22
|
* Config-driven `pipe` entrypoint. Package-owned defaults are the source of
|
|
22
|
-
* truth; repo-local pipeline files are
|
|
23
|
+
* truth; repo-local pipeline files are ignored by runtime loading.
|
|
23
24
|
*/
|
|
24
25
|
function pipe(description, options = {}) {
|
|
25
26
|
try {
|
|
@@ -192,9 +193,9 @@ function truncateMiddle(text, maxLength) {
|
|
|
192
193
|
return `${text.slice(0, keep)}\n... truncated ...\n${text.slice(-keep)}`;
|
|
193
194
|
}
|
|
194
195
|
function createCliProgram() {
|
|
195
|
-
const configuredPipeline =
|
|
196
|
+
const configuredPipeline = loadConfiguredEntrypoints(process.env.PIPELINE_TARGET_PATH ?? process.cwd());
|
|
196
197
|
const program = new Command();
|
|
197
|
-
program.name("@oisincoveney/pipeline").description("Run
|
|
198
|
+
program.name("@oisincoveney/pipeline").description("Run package-owned @oisincoveney/pipeline config").exitOverride();
|
|
198
199
|
const runAction = async (descriptionParts, flags) => {
|
|
199
200
|
await pipe(descriptionParts.join(" "), {
|
|
200
201
|
entrypoint: flags.entrypoint,
|
|
@@ -202,9 +203,9 @@ function createCliProgram() {
|
|
|
202
203
|
workflow: flags.workflow
|
|
203
204
|
});
|
|
204
205
|
};
|
|
205
|
-
program.command("run").description("Run a workflow from
|
|
206
|
-
program.command("pipe").description("Alias for run").argument("<description...>", "task description").option("--entrypoint <entrypoint>", "entrypoint alias from
|
|
207
|
-
program.command("validate").description("Validate
|
|
206
|
+
program.command("run").description("Run a workflow from package-owned @oisincoveney/pipeline config").argument("<description...>", "task description").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to execute").option("--workflow <workflow>", "workflow id from package config").action(runAction);
|
|
207
|
+
program.command("pipe").description("Alias for run").argument("<description...>", "task description").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to execute").option("--workflow <workflow>", "workflow id from package config").action(runAction);
|
|
208
|
+
program.command("validate").description("Validate package-owned @oisincoveney/pipeline config and compile the workflow plan").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to validate").option("--strict", "fail when validation lint warnings are emitted").option("--no-lint", "skip validation lint warnings").option("--workflow <workflow>", "workflow id from package config").action((flags) => {
|
|
208
209
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
209
210
|
const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
|
|
210
211
|
const plan = flags.schedule ? compileScheduleArtifact(config, parseScheduleArtifact(readFileSync(flags.schedule, "utf8"), flags.schedule), cwd).plan : compileWorkflowPlan(config, resolveWorkflowSelection(config, flags.workflow, flags.entrypoint));
|
|
@@ -213,7 +214,7 @@ function createCliProgram() {
|
|
|
213
214
|
if (flags.strict && warnings.length > 0) throw new Error(`Validation failed with ${warnings.length} warning${warnings.length === 1 ? "" : "s"}.`);
|
|
214
215
|
console.log(`OK: ${plan.workflowId} (${plan.topologicalOrder.length} nodes)`);
|
|
215
216
|
});
|
|
216
|
-
program.command("explain-plan").description("Explain workflow nodes, runners, gates, hooks, and artifacts").option("--entrypoint <entrypoint>", "entrypoint alias from
|
|
217
|
+
program.command("explain-plan").description("Explain workflow nodes, runners, gates, hooks, and artifacts").option("--entrypoint <entrypoint>", "entrypoint alias from package config").option("--schedule <schedule>", "approved schedule YAML to explain").option("--workflow <workflow>", "workflow id from package config").action((flags) => {
|
|
217
218
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
218
219
|
const config = loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
|
|
219
220
|
console.log(formatSelectedWorkflowPlan(config, cwd, flags));
|
|
@@ -247,6 +248,16 @@ function createCliProgram() {
|
|
|
247
248
|
});
|
|
248
249
|
console.log(result.map((item) => [`${item.host}: ${item.path}`, item.backupPath ? `backup=${item.backupPath}` : "backup=none"].join(" ")).join("\n"));
|
|
249
250
|
});
|
|
251
|
+
gatewayCommand.command("reconcile").description("Apply the current workspace gateway backend inventory").action(async () => {
|
|
252
|
+
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
253
|
+
const result = await reconcileGateway(loadPipelineConfig(cwd, { allowMissingLintFileReferences: true }), cwd);
|
|
254
|
+
console.log([
|
|
255
|
+
`workspace=${result.workspacePath}`,
|
|
256
|
+
`config=${result.configPath}`,
|
|
257
|
+
`backends=${result.backendCount}`,
|
|
258
|
+
result.readinessFailures.length > 0 ? `readiness_failures=${result.readinessFailures.join("; ")}` : "readiness_failures=none"
|
|
259
|
+
].join("\n"));
|
|
260
|
+
});
|
|
250
261
|
gatewayCommand.command("local-start").description("Start a local ToolHive vMCP gateway for local mode").option("--detach", "reserved for future background startup", false).action(async (flags) => {
|
|
251
262
|
if (flags.detach) throw new Error("Detached local gateway startup is not implemented.");
|
|
252
263
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
@@ -256,11 +267,8 @@ function createCliProgram() {
|
|
|
256
267
|
const cwd = process.env.PIPELINE_TARGET_PATH ?? process.cwd();
|
|
257
268
|
console.log(await localGatewayStatus(cwd));
|
|
258
269
|
});
|
|
259
|
-
program.command("init").description("Initialize package-owned pipeline support without repo-local config").
|
|
260
|
-
const result = await initPipelineProject({
|
|
261
|
-
cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd(),
|
|
262
|
-
overwrite: flags.overwrite ?? false
|
|
263
|
-
});
|
|
270
|
+
program.command("init").description("Initialize package-owned pipeline support without repo-local config").action(async () => {
|
|
271
|
+
const result = await initPipelineProject({ cwd: process.env.PIPELINE_TARGET_PATH ?? process.cwd() });
|
|
264
272
|
console.log(formatPipelineInitResult(result));
|
|
265
273
|
});
|
|
266
274
|
program.command("install-commands").description("Install generated slash-command adapters into this repository").addOption(new Option("--host <host>", "host command set to install").choices([
|
|
@@ -282,13 +290,8 @@ function createCliProgram() {
|
|
|
282
290
|
} });
|
|
283
291
|
return program;
|
|
284
292
|
}
|
|
285
|
-
function
|
|
286
|
-
|
|
287
|
-
return tryLoadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
|
|
288
|
-
} catch (err) {
|
|
289
|
-
if (err instanceof PipelineConfigError) return null;
|
|
290
|
-
throw err;
|
|
291
|
-
}
|
|
293
|
+
function loadConfiguredEntrypoints(cwd) {
|
|
294
|
+
return loadPipelineConfig(cwd, { allowMissingLintFileReferences: true });
|
|
292
295
|
}
|
|
293
296
|
function parseGatewayHostScope(value) {
|
|
294
297
|
if (value === "project" || value === "global") return value;
|
|
@@ -324,7 +327,7 @@ function lintMissingFileReferences(config, projectRoot) {
|
|
|
324
327
|
});
|
|
325
328
|
}
|
|
326
329
|
return refs.flatMap((ref) => {
|
|
327
|
-
if (!ref.value || existsSync(resolve(projectRoot, ref.value))) return [];
|
|
330
|
+
if (!ref.value || standardOutputSchemaNameFromPath(ref.value) || existsSync(resolve(projectRoot, ref.value))) return [];
|
|
328
331
|
return [{
|
|
329
332
|
ruleId: "missing-file-reference",
|
|
330
333
|
message: `${ref.path} references missing file '${ref.value}'`
|