@oisincoveney/pipeline 1.8.0 → 1.9.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 +18 -13
- package/defaults/install-manifest.json +6 -4
- package/dist/pipeline-init.js +348 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,11 +37,17 @@ Momokaya remote endpoint
|
|
|
37
37
|
|
|
38
38
|
The default GitHub MCP registration uses GitHub's official container in
|
|
39
39
|
read-only mode and reads `GITHUB_PERSONAL_ACCESS_TOKEN` from the environment.
|
|
40
|
-
The Momokaya Qdrant endpoint is protected by Traefik HTTP Basic auth. Set
|
|
41
|
-
`
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
The Momokaya Qdrant endpoint is protected by Traefik HTTP Basic auth. Set
|
|
41
|
+
`MEMORY_MCP_BASIC_AUTH` to the base64 `user:password` payload before running
|
|
42
|
+
`pipe init` if you want init to register that remote server with MCPM:
|
|
43
|
+
|
|
44
|
+
```shell
|
|
45
|
+
export MEMORY_MCP_BASIC_AUTH="$(printf '%s' 'user:password' | base64)"
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
When `MEMORY_MCP_BASIC_AUTH` is not set, `pipe init` still writes the default
|
|
49
|
+
scaffold and keeps the generated `qdrant` MCP entry, but skips immediate MCPM
|
|
50
|
+
registration for that private endpoint.
|
|
45
51
|
|
|
46
52
|
Check local prerequisites and config health:
|
|
47
53
|
|
|
@@ -90,7 +96,7 @@ pipe epic PIPE-31
|
|
|
90
96
|
|
|
91
97
|
The `epic` entrypoint routes an epic's child tickets into fixed specialist
|
|
92
98
|
tracks, runs those tracks in parallel, merges passing branches, and then runs a
|
|
93
|
-
|
|
99
|
+
thermo-nuclear code quality review.
|
|
94
100
|
|
|
95
101
|
The `pipe` binary also accepts the task directly:
|
|
96
102
|
|
|
@@ -201,11 +207,11 @@ The built-in `epic` entrypoint uses those primitives:
|
|
|
201
207
|
entrypoints:
|
|
202
208
|
epic:
|
|
203
209
|
workflow: epic-drain
|
|
204
|
-
description: Route an epic's tickets into specialist tracks, run them in parallel, then
|
|
210
|
+
description: Route an epic's tickets into specialist tracks, run them in parallel, then thermo-nuclear review.
|
|
205
211
|
|
|
206
212
|
workflows:
|
|
207
213
|
epic-drain:
|
|
208
|
-
description: Research, route, parallel-implement tracks in isolated worktrees, integrate,
|
|
214
|
+
description: Research, route, parallel-implement tracks in isolated worktrees, integrate, thermo-nuclear review.
|
|
209
215
|
nodes:
|
|
210
216
|
- id: research
|
|
211
217
|
kind: agent
|
|
@@ -240,7 +246,7 @@ workflows:
|
|
|
240
246
|
needs: [implement]
|
|
241
247
|
- id: review
|
|
242
248
|
kind: agent
|
|
243
|
-
profile: pipeline-
|
|
249
|
+
profile: pipeline-thermo-nuclear-reviewer
|
|
244
250
|
needs: [merge]
|
|
245
251
|
gates:
|
|
246
252
|
- { id: review-verdict, kind: verdict, target: stdout }
|
|
@@ -253,10 +259,9 @@ branches share a base SHA, and merges passing branches into an integration
|
|
|
253
259
|
branch in declaration order. It reports merge conflicts; it does not resolve
|
|
254
260
|
them automatically.
|
|
255
261
|
|
|
256
|
-
The `
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
promotes that warning to a failure.
|
|
262
|
+
The `thermo-nuclear-code-quality-review` skill is installed from
|
|
263
|
+
`cursor/plugins` and registered at
|
|
264
|
+
`.agents/skills/thermo-nuclear-code-quality-review/SKILL.md`.
|
|
260
265
|
|
|
261
266
|
## Generated Host Resources
|
|
262
267
|
|
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
"deprecation-and-migration"
|
|
31
31
|
]
|
|
32
32
|
},
|
|
33
|
+
{
|
|
34
|
+
"source": "cursor/plugins",
|
|
35
|
+
"skills": ["thermo-nuclear-code-quality-review"]
|
|
36
|
+
},
|
|
33
37
|
{
|
|
34
38
|
"source": "trailofbits/skills",
|
|
35
39
|
"skills": ["semgrep", "supply-chain-risk-auditor"]
|
|
@@ -78,12 +82,10 @@
|
|
|
78
82
|
"name": "oisin-pipeline-qdrant",
|
|
79
83
|
"transport": "remote",
|
|
80
84
|
"url": "https://memory-mcp.momokaya.ee/mcp/",
|
|
85
|
+
"optionalRegistration": true,
|
|
81
86
|
"headers": {
|
|
82
87
|
"Authorization": {
|
|
83
|
-
"sources": [
|
|
84
|
-
{ "env": "MOMOKAYA_MCP_AUTHORIZATION" },
|
|
85
|
-
{ "env": "MEMORY_MCP_BASIC_AUTH", "prefix": "Basic " }
|
|
86
|
-
]
|
|
88
|
+
"sources": [{ "env": "MEMORY_MCP_BASIC_AUTH", "prefix": "Basic " }]
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
},
|
package/dist/pipeline-init.js
CHANGED
|
@@ -29,6 +29,18 @@ var PipelineMcpInstallError = class extends Error {
|
|
|
29
29
|
this.name = "PipelineMcpInstallError";
|
|
30
30
|
}
|
|
31
31
|
};
|
|
32
|
+
var PipelineMcpMissingCredentialError = class extends PipelineMcpInstallError {
|
|
33
|
+
headerName;
|
|
34
|
+
missingEnv;
|
|
35
|
+
serverName;
|
|
36
|
+
constructor(serverName, headerName, missingEnv) {
|
|
37
|
+
super([`MCP server ${serverName} requires ${headerName} credentials before it can be registered.`, `Set ${missingEnv.join(" or ")} and re-run pipeline init.`].join("\n"));
|
|
38
|
+
this.name = "PipelineMcpMissingCredentialError";
|
|
39
|
+
this.serverName = serverName;
|
|
40
|
+
this.headerName = headerName;
|
|
41
|
+
this.missingEnv = missingEnv;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
32
44
|
var PipelineDefaultManifestError = class extends Error {
|
|
33
45
|
constructor(message) {
|
|
34
46
|
super(message);
|
|
@@ -58,6 +70,7 @@ const pipelineMcpInstallSpecSchema = z.object({
|
|
|
58
70
|
env: z.record(z.string(), z.string()).optional(),
|
|
59
71
|
headers: z.record(z.string(), pipelineMcpHeaderValueSchema).optional(),
|
|
60
72
|
name: z.string().min(1),
|
|
73
|
+
optionalRegistration: z.boolean().optional(),
|
|
61
74
|
transport: z.enum(["remote", "stdio"]),
|
|
62
75
|
url: z.string().url().optional()
|
|
63
76
|
}).strict().superRefine((spec, ctx) => {
|
|
@@ -125,6 +138,9 @@ entrypoints:
|
|
|
125
138
|
inspect:
|
|
126
139
|
workflow: inspect
|
|
127
140
|
description: Read-only repository inspection
|
|
141
|
+
epic:
|
|
142
|
+
workflow: epic-drain
|
|
143
|
+
description: Route an epic's tickets into specialist tracks, run them in parallel, then thermo-nuclear review.
|
|
128
144
|
|
|
129
145
|
orchestrator:
|
|
130
146
|
profile: orchestrator
|
|
@@ -234,6 +250,121 @@ workflows:
|
|
|
234
250
|
kind: agent
|
|
235
251
|
profile: pipeline-learner
|
|
236
252
|
needs: [verify]
|
|
253
|
+
infra:
|
|
254
|
+
description: Default-shaped stub workflow for infrastructure specialization.
|
|
255
|
+
nodes:
|
|
256
|
+
- id: research
|
|
257
|
+
kind: agent
|
|
258
|
+
profile: pipeline-researcher
|
|
259
|
+
- id: red
|
|
260
|
+
kind: agent
|
|
261
|
+
profile: pipeline-test-writer
|
|
262
|
+
needs: [research]
|
|
263
|
+
gates:
|
|
264
|
+
- id: red-test-file-policy
|
|
265
|
+
kind: changed_files
|
|
266
|
+
changed_files:
|
|
267
|
+
allow:
|
|
268
|
+
[
|
|
269
|
+
"**/*.test.*",
|
|
270
|
+
"**/*.spec.*",
|
|
271
|
+
"**/*_test.*",
|
|
272
|
+
"**/__tests__/**",
|
|
273
|
+
"test/**",
|
|
274
|
+
"tests/**",
|
|
275
|
+
"**/*.snap",
|
|
276
|
+
]
|
|
277
|
+
require_any:
|
|
278
|
+
[
|
|
279
|
+
"**/*.test.*",
|
|
280
|
+
"**/*.spec.*",
|
|
281
|
+
"**/*_test.*",
|
|
282
|
+
"**/__tests__/**",
|
|
283
|
+
"test/**",
|
|
284
|
+
"tests/**",
|
|
285
|
+
]
|
|
286
|
+
- id: green
|
|
287
|
+
kind: agent
|
|
288
|
+
profile: pipeline-code-writer
|
|
289
|
+
needs: [red]
|
|
290
|
+
- id: acceptance
|
|
291
|
+
kind: agent
|
|
292
|
+
profile: pipeline-acceptance-reviewer
|
|
293
|
+
needs: [green]
|
|
294
|
+
gates:
|
|
295
|
+
- id: acceptance-coverage
|
|
296
|
+
kind: acceptance
|
|
297
|
+
target: stdout
|
|
298
|
+
required: false
|
|
299
|
+
- id: acceptance-verdict
|
|
300
|
+
kind: verdict
|
|
301
|
+
target: stdout
|
|
302
|
+
- id: verify
|
|
303
|
+
kind: agent
|
|
304
|
+
profile: pipeline-verifier
|
|
305
|
+
needs: [acceptance]
|
|
306
|
+
gates:
|
|
307
|
+
- id: verify-typecheck
|
|
308
|
+
kind: builtin
|
|
309
|
+
builtin: typecheck
|
|
310
|
+
- id: verify-tests
|
|
311
|
+
kind: builtin
|
|
312
|
+
builtin: test
|
|
313
|
+
- id: verify-semgrep
|
|
314
|
+
kind: builtin
|
|
315
|
+
builtin: semgrep
|
|
316
|
+
- id: verify-duplication
|
|
317
|
+
kind: builtin
|
|
318
|
+
builtin: duplication
|
|
319
|
+
- id: verify-verdict
|
|
320
|
+
kind: verdict
|
|
321
|
+
target: stdout
|
|
322
|
+
- id: learn
|
|
323
|
+
kind: agent
|
|
324
|
+
profile: pipeline-learner
|
|
325
|
+
needs: [verify]
|
|
326
|
+
epic-drain:
|
|
327
|
+
description: Research, route, parallel-implement tracks in isolated worktrees, integrate, thermo-nuclear review.
|
|
328
|
+
nodes:
|
|
329
|
+
- id: research
|
|
330
|
+
kind: agent
|
|
331
|
+
profile: pipeline-researcher
|
|
332
|
+
- id: plan
|
|
333
|
+
kind: agent
|
|
334
|
+
profile: pipeline-epic-router
|
|
335
|
+
needs: [research]
|
|
336
|
+
- id: implement
|
|
337
|
+
kind: parallel
|
|
338
|
+
needs: [plan]
|
|
339
|
+
nodes:
|
|
340
|
+
- id: test
|
|
341
|
+
kind: workflow
|
|
342
|
+
workflow: default
|
|
343
|
+
worktree_root: .pipeline/runs/\${runId}/test
|
|
344
|
+
- id: frontend
|
|
345
|
+
kind: workflow
|
|
346
|
+
workflow: default
|
|
347
|
+
worktree_root: .pipeline/runs/\${runId}/frontend
|
|
348
|
+
- id: backend
|
|
349
|
+
kind: workflow
|
|
350
|
+
workflow: default
|
|
351
|
+
worktree_root: .pipeline/runs/\${runId}/backend
|
|
352
|
+
- id: k8s
|
|
353
|
+
kind: workflow
|
|
354
|
+
workflow: infra
|
|
355
|
+
worktree_root: .pipeline/runs/\${runId}/k8s
|
|
356
|
+
- id: merge
|
|
357
|
+
kind: builtin
|
|
358
|
+
builtin: drain-merge
|
|
359
|
+
needs: [implement]
|
|
360
|
+
- id: review
|
|
361
|
+
kind: agent
|
|
362
|
+
profile: pipeline-thermo-nuclear-reviewer
|
|
363
|
+
needs: [merge]
|
|
364
|
+
gates:
|
|
365
|
+
- id: review-verdict
|
|
366
|
+
kind: verdict
|
|
367
|
+
target: stdout
|
|
237
368
|
`;
|
|
238
369
|
const DEFAULT_RUNNERS_YAML = `version: 1
|
|
239
370
|
|
|
@@ -364,6 +495,8 @@ skills:
|
|
|
364
495
|
path: .agents/skills/incremental-implementation/SKILL.md
|
|
365
496
|
debugging-and-error-recovery:
|
|
366
497
|
path: .agents/skills/debugging-and-error-recovery/SKILL.md
|
|
498
|
+
thermo-nuclear-code-quality-review:
|
|
499
|
+
path: .agents/skills/thermo-nuclear-code-quality-review/SKILL.md
|
|
367
500
|
code-review-and-quality:
|
|
368
501
|
path: .agents/skills/code-review-and-quality/SKILL.md
|
|
369
502
|
doubt-driven-development:
|
|
@@ -489,6 +622,25 @@ profiles:
|
|
|
489
622
|
mode: inherit
|
|
490
623
|
output:
|
|
491
624
|
format: text
|
|
625
|
+
pipeline-epic-router:
|
|
626
|
+
runner: codex
|
|
627
|
+
description: Route epic sub-tickets into fixed implementation tracks.
|
|
628
|
+
instructions:
|
|
629
|
+
path: .pipeline/prompts/epic-router.md
|
|
630
|
+
mcp_servers: [backlog, github-readonly]
|
|
631
|
+
tools: [read, list, grep, glob, bash]
|
|
632
|
+
filesystem:
|
|
633
|
+
mode: read-only
|
|
634
|
+
allow: ["**/*"]
|
|
635
|
+
deny: ["node_modules/**", "dist/**", ".git/**"]
|
|
636
|
+
network:
|
|
637
|
+
mode: inherit
|
|
638
|
+
output:
|
|
639
|
+
format: json_schema
|
|
640
|
+
schema_path: .pipeline/schemas/epic-plan.schema.json
|
|
641
|
+
repair:
|
|
642
|
+
enabled: true
|
|
643
|
+
max_attempts: 1
|
|
492
644
|
pipeline-code-writer:
|
|
493
645
|
runner: codex
|
|
494
646
|
description: Implement production code until the failing tests pass.
|
|
@@ -539,6 +691,26 @@ profiles:
|
|
|
539
691
|
repair:
|
|
540
692
|
enabled: true
|
|
541
693
|
max_attempts: 1
|
|
694
|
+
pipeline-thermo-nuclear-reviewer:
|
|
695
|
+
runner: codex
|
|
696
|
+
description: Perform the final thermo-nuclear code quality review of the integration branch.
|
|
697
|
+
instructions:
|
|
698
|
+
path: .agents/skills/thermo-nuclear-code-quality-review/SKILL.md
|
|
699
|
+
skills: [thermo-nuclear-code-quality-review]
|
|
700
|
+
mcp_servers: [serena, semgrep, github-readonly]
|
|
701
|
+
tools: [read, list, grep, glob, bash]
|
|
702
|
+
filesystem:
|
|
703
|
+
mode: read-only
|
|
704
|
+
allow: ["**/*"]
|
|
705
|
+
deny: ["node_modules/**", "dist/**", ".git/**"]
|
|
706
|
+
network:
|
|
707
|
+
mode: inherit
|
|
708
|
+
output:
|
|
709
|
+
format: json_schema
|
|
710
|
+
schema_path: .pipeline/schemas/review.schema.json
|
|
711
|
+
repair:
|
|
712
|
+
enabled: true
|
|
713
|
+
max_attempts: 1
|
|
542
714
|
pipeline-verifier:
|
|
543
715
|
runner: codex
|
|
544
716
|
description: Verify checks, implementation fit, and final evidence.
|
|
@@ -696,6 +868,109 @@ const ACCEPTANCE_SCHEMA = JSON.stringify({
|
|
|
696
868
|
],
|
|
697
869
|
type: "object"
|
|
698
870
|
}, null, 2);
|
|
871
|
+
const EPIC_PLAN_SCHEMA = JSON.stringify({
|
|
872
|
+
additionalProperties: false,
|
|
873
|
+
properties: {
|
|
874
|
+
backend: {
|
|
875
|
+
items: {
|
|
876
|
+
additionalProperties: false,
|
|
877
|
+
properties: {
|
|
878
|
+
id: { type: "string" },
|
|
879
|
+
rationale: { type: "string" },
|
|
880
|
+
title: { type: "string" }
|
|
881
|
+
},
|
|
882
|
+
required: ["id"],
|
|
883
|
+
type: "object"
|
|
884
|
+
},
|
|
885
|
+
type: "array"
|
|
886
|
+
},
|
|
887
|
+
frontend: {
|
|
888
|
+
items: {
|
|
889
|
+
additionalProperties: false,
|
|
890
|
+
properties: {
|
|
891
|
+
id: { type: "string" },
|
|
892
|
+
rationale: { type: "string" },
|
|
893
|
+
title: { type: "string" }
|
|
894
|
+
},
|
|
895
|
+
required: ["id"],
|
|
896
|
+
type: "object"
|
|
897
|
+
},
|
|
898
|
+
type: "array"
|
|
899
|
+
},
|
|
900
|
+
k8s: {
|
|
901
|
+
items: {
|
|
902
|
+
additionalProperties: false,
|
|
903
|
+
properties: {
|
|
904
|
+
id: { type: "string" },
|
|
905
|
+
rationale: { type: "string" },
|
|
906
|
+
title: { type: "string" }
|
|
907
|
+
},
|
|
908
|
+
required: ["id"],
|
|
909
|
+
type: "object"
|
|
910
|
+
},
|
|
911
|
+
type: "array"
|
|
912
|
+
},
|
|
913
|
+
rationale: { type: "string" },
|
|
914
|
+
test: {
|
|
915
|
+
items: {
|
|
916
|
+
additionalProperties: false,
|
|
917
|
+
properties: {
|
|
918
|
+
id: { type: "string" },
|
|
919
|
+
rationale: { type: "string" },
|
|
920
|
+
title: { type: "string" }
|
|
921
|
+
},
|
|
922
|
+
required: ["id"],
|
|
923
|
+
type: "object"
|
|
924
|
+
},
|
|
925
|
+
type: "array"
|
|
926
|
+
}
|
|
927
|
+
},
|
|
928
|
+
required: [
|
|
929
|
+
"test",
|
|
930
|
+
"frontend",
|
|
931
|
+
"backend",
|
|
932
|
+
"k8s"
|
|
933
|
+
],
|
|
934
|
+
type: "object"
|
|
935
|
+
}, null, 2);
|
|
936
|
+
const REVIEW_SCHEMA = JSON.stringify({
|
|
937
|
+
additionalProperties: false,
|
|
938
|
+
properties: {
|
|
939
|
+
findings: {
|
|
940
|
+
items: {
|
|
941
|
+
additionalProperties: false,
|
|
942
|
+
properties: {
|
|
943
|
+
file: { type: "string" },
|
|
944
|
+
line: {
|
|
945
|
+
minimum: 1,
|
|
946
|
+
type: "integer"
|
|
947
|
+
},
|
|
948
|
+
message: { type: "string" },
|
|
949
|
+
rule: { type: "string" },
|
|
950
|
+
severity: {
|
|
951
|
+
enum: [
|
|
952
|
+
"info",
|
|
953
|
+
"warn",
|
|
954
|
+
"error",
|
|
955
|
+
"critical"
|
|
956
|
+
],
|
|
957
|
+
type: "string"
|
|
958
|
+
}
|
|
959
|
+
},
|
|
960
|
+
required: ["severity", "message"],
|
|
961
|
+
type: "object"
|
|
962
|
+
},
|
|
963
|
+
type: "array"
|
|
964
|
+
},
|
|
965
|
+
summary: { type: "string" },
|
|
966
|
+
verdict: {
|
|
967
|
+
enum: ["PASS", "FAIL"],
|
|
968
|
+
type: "string"
|
|
969
|
+
}
|
|
970
|
+
},
|
|
971
|
+
required: ["verdict", "findings"],
|
|
972
|
+
type: "object"
|
|
973
|
+
}, null, 2);
|
|
699
974
|
const SCAFFOLD_FILES = {
|
|
700
975
|
[PIPELINE_CONFIG_PATH]: DEFAULT_PIPELINE_YAML,
|
|
701
976
|
[PROFILES_CONFIG_PATH]: DEFAULT_PROFILES_YAML,
|
|
@@ -733,6 +1008,36 @@ const SCAFFOLD_FILES = {
|
|
|
733
1008
|
"Return concrete failing-test evidence.",
|
|
734
1009
|
""
|
|
735
1010
|
].join("\n"),
|
|
1011
|
+
".pipeline/prompts/epic-router.md": [
|
|
1012
|
+
"# Epic router",
|
|
1013
|
+
"",
|
|
1014
|
+
"You read an epic ticket and its sub-tickets via the Backlog MCP server, then route each sub-ticket into exactly one of four named tracks: test, frontend, backend, k8s. You output a JSON document matching `.pipeline/schemas/epic-plan.schema.json`.",
|
|
1015
|
+
"",
|
|
1016
|
+
"## Inputs",
|
|
1017
|
+
"",
|
|
1018
|
+
"- The user's task is an epic id (or a description that names one). Use the Backlog MCP `task_view` and `task_search` tools to find the epic and enumerate its sub-tickets.",
|
|
1019
|
+
"- For each sub-ticket, read its title, description, labels, and any referenced files.",
|
|
1020
|
+
"",
|
|
1021
|
+
"## Routing rules",
|
|
1022
|
+
"",
|
|
1023
|
+
"Pick the single best-fit track per ticket. Heuristics, in priority order:",
|
|
1024
|
+
"",
|
|
1025
|
+
"1. **k8s** - anything touching deployment, Kubernetes manifests, Helm charts, infra YAML, CI/CD pipelines, Docker, ingress, RBAC, cluster config.",
|
|
1026
|
+
"2. **backend** - server-side APIs, services, database schema, server-side data flows, MCP servers, non-UI integrations.",
|
|
1027
|
+
"3. **frontend** - UI components, client-side state, styling, browser interactions, accessibility, Figma-referenced work.",
|
|
1028
|
+
"4. **test** - work that is *primarily* writing or restructuring tests (e.g. coverage uplift, harness changes). Don't route a feature ticket here just because it mentions tests - features go to their domain track and write their own tests there.",
|
|
1029
|
+
"",
|
|
1030
|
+
"Ties: prefer **backend > frontend > test > k8s** unless a strong signal flips it.",
|
|
1031
|
+
"",
|
|
1032
|
+
"A track may be empty (`[]`).",
|
|
1033
|
+
"",
|
|
1034
|
+
"## Output",
|
|
1035
|
+
"",
|
|
1036
|
+
"Emit a single JSON document conforming to the schema. Include a short `rationale` string explaining notable routing decisions.",
|
|
1037
|
+
"",
|
|
1038
|
+
"Do not modify any files. Do not invoke other agents.",
|
|
1039
|
+
""
|
|
1040
|
+
].join("\n"),
|
|
736
1041
|
".pipeline/prompts/code-writer.md": [
|
|
737
1042
|
"You are the GREEN/code-write phase for the pipeline.",
|
|
738
1043
|
"Implement the smallest production change that satisfies the failing tests.",
|
|
@@ -785,7 +1090,9 @@ const SCAFFOLD_FILES = {
|
|
|
785
1090
|
].join("\n"),
|
|
786
1091
|
".pipeline/schemas/research.schema.json": `${RESEARCH_SCHEMA}\n`,
|
|
787
1092
|
".pipeline/schemas/acceptance.schema.json": `${ACCEPTANCE_SCHEMA}\n`,
|
|
1093
|
+
".pipeline/schemas/epic-plan.schema.json": `${EPIC_PLAN_SCHEMA}\n`,
|
|
788
1094
|
".pipeline/schemas/verify.schema.json": `${VERIFY_SCHEMA}\n`,
|
|
1095
|
+
".pipeline/schemas/review.schema.json": `${REVIEW_SCHEMA}\n`,
|
|
789
1096
|
".pipeline/schemas/learn.schema.json": `${LEARN_SCHEMA}\n`,
|
|
790
1097
|
".pipeline/host-resources/claude.md": hostResourceInput("Claude Code"),
|
|
791
1098
|
".pipeline/host-resources/codex.md": hostResourceInput("Codex"),
|
|
@@ -824,8 +1131,13 @@ async function installDefaultSkillsWithCli(specs, cwd) {
|
|
|
824
1131
|
}
|
|
825
1132
|
}
|
|
826
1133
|
async function installDefaultMcpsWithCli(specs, cwd) {
|
|
1134
|
+
const skipped = [];
|
|
827
1135
|
for (const spec of specs) {
|
|
828
1136
|
const install = mcpInstallArgs(spec);
|
|
1137
|
+
if ("skipped" in install) {
|
|
1138
|
+
skipped.push(install.skipped);
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
829
1141
|
try {
|
|
830
1142
|
await execa("uvx", [...DEFAULT_MCPM_ARGS, ...install.args], {
|
|
831
1143
|
cwd,
|
|
@@ -848,6 +1160,7 @@ async function installDefaultMcpsWithCli(specs, cwd) {
|
|
|
848
1160
|
].filter(Boolean).join("\n"));
|
|
849
1161
|
}
|
|
850
1162
|
}
|
|
1163
|
+
return { skipped };
|
|
851
1164
|
}
|
|
852
1165
|
function mcpInstallArgs(spec) {
|
|
853
1166
|
if (spec.catalog) return {
|
|
@@ -870,19 +1183,29 @@ function mcpInstallArgs(spec) {
|
|
|
870
1183
|
if (spec.transport === "remote") {
|
|
871
1184
|
if (!spec.url) throw new PipelineMcpInstallError(`MCP server ${spec.name} is remote but has no url.`);
|
|
872
1185
|
const redactions = [];
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1186
|
+
try {
|
|
1187
|
+
const headers = Object.entries(spec.headers ?? {}).flatMap(([key, value]) => {
|
|
1188
|
+
const headerValue = resolveMcpHeaderValue(spec.name, key, value);
|
|
1189
|
+
redactions.push(headerValue);
|
|
1190
|
+
return ["--headers", `${key}=${headerValue}`];
|
|
1191
|
+
});
|
|
1192
|
+
return {
|
|
1193
|
+
args: [
|
|
1194
|
+
...args,
|
|
1195
|
+
"--url",
|
|
1196
|
+
spec.url,
|
|
1197
|
+
...headers
|
|
1198
|
+
],
|
|
1199
|
+
redactions
|
|
1200
|
+
};
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
if (spec.optionalRegistration && err instanceof PipelineMcpMissingCredentialError) return { skipped: {
|
|
1203
|
+
missingEnv: err.missingEnv,
|
|
1204
|
+
name: spec.name,
|
|
1205
|
+
reason: `missing ${err.headerName} credentials`
|
|
1206
|
+
} };
|
|
1207
|
+
throw err;
|
|
1208
|
+
}
|
|
886
1209
|
}
|
|
887
1210
|
if (!spec.command) throw new PipelineMcpInstallError(`MCP server ${spec.name} is stdio but has no command.`);
|
|
888
1211
|
return {
|
|
@@ -914,8 +1237,7 @@ function resolveMcpHeaderValue(serverName, headerName, header) {
|
|
|
914
1237
|
const rawValue = process.env[source.env];
|
|
915
1238
|
if (rawValue && rawValue.trim().length > 0) return `${source.prefix ?? ""}${rawValue}${source.suffix ?? ""}`;
|
|
916
1239
|
}
|
|
917
|
-
|
|
918
|
-
throw new PipelineMcpInstallError([`MCP server ${serverName} requires ${headerName} credentials before it can be registered.`, `Set ${envNames} and re-run pipeline init.`].join("\n"));
|
|
1240
|
+
throw new PipelineMcpMissingCredentialError(serverName, headerName, header.sources.map((source) => source.env));
|
|
919
1241
|
}
|
|
920
1242
|
function assertDefaultSkillsInstalled(cwd) {
|
|
921
1243
|
const paths = defaultSkillPaths();
|
|
@@ -935,7 +1257,7 @@ async function initPipelineProject(options = {}) {
|
|
|
935
1257
|
const paths = Object.keys(files);
|
|
936
1258
|
const conflicts = paths.filter((path) => existsSync(join(cwd, path)));
|
|
937
1259
|
if (conflicts.length > 0 && !options.overwrite) throw new PipelineInitError(conflicts);
|
|
938
|
-
await (options.mcpInstaller ?? installDefaultMcpsWithCli)(DEFAULT_MCP_INSTALLS, cwd);
|
|
1260
|
+
const mcpInstallResult = await (options.mcpInstaller ?? installDefaultMcpsWithCli)(DEFAULT_MCP_INSTALLS, cwd);
|
|
939
1261
|
await (options.skillInstaller ?? installDefaultSkillsWithCli)(DEFAULT_SKILL_INSTALLS, cwd);
|
|
940
1262
|
const skillPaths = assertDefaultSkillsInstalled(cwd);
|
|
941
1263
|
for (const [path, content] of Object.entries(files)) {
|
|
@@ -949,10 +1271,18 @@ async function initPipelineProject(options = {}) {
|
|
|
949
1271
|
...existsSync(join(cwd, "skills-lock.json")) ? ["skills-lock.json"] : []
|
|
950
1272
|
];
|
|
951
1273
|
loadPipelineConfig(cwd);
|
|
952
|
-
return {
|
|
1274
|
+
return {
|
|
1275
|
+
files: generatedPaths,
|
|
1276
|
+
skippedMcps: mcpInstallResult?.skipped ?? []
|
|
1277
|
+
};
|
|
953
1278
|
}
|
|
954
1279
|
function formatPipelineInitResult(result) {
|
|
955
|
-
|
|
1280
|
+
const skippedMcps = result.skippedMcps ?? [];
|
|
1281
|
+
return [
|
|
1282
|
+
"Initialized pipeline scaffold:",
|
|
1283
|
+
...result.files.map((path) => `create ${path}`),
|
|
1284
|
+
...skippedMcps.flatMap((skip) => [`Skipped MCPM registration for ${skip.name}: ${skip.reason}.`, `Set ${skip.missingEnv.join(" or ")} before retrying MCPM registration. The generated MCP entry remains in .mcp.json.`])
|
|
1285
|
+
].join("\n");
|
|
956
1286
|
}
|
|
957
1287
|
function hostResourceInput(host) {
|
|
958
1288
|
return [
|
package/package.json
CHANGED