@oisincoveney/pipeline 1.11.1 → 1.11.3
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 -3
- package/dist/config.d.ts +11 -2
- package/dist/config.js +10 -0
- package/dist/install-commands.js +54 -5
- package/dist/pipeline-init.js +75 -5
- package/dist/pipeline-runtime.js +10 -6
- package/dist/schedule-planner.d.ts +216 -0
- package/dist/schedule-planner.js +393 -103
- package/dist/task-ref.js +19 -0
- package/dist/workflow-planner.d.ts +11 -1
- package/dist/workflow-planner.js +159 -18
- package/docs/config-architecture.md +3 -0
- package/docs/operator-guide.md +17 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,8 +11,8 @@ artifacts.
|
|
|
11
11
|
- Bun 1.1 or newer
|
|
12
12
|
- Node.js 22.13 or newer
|
|
13
13
|
- `npx`, `backlog`, `uvx`, and Docker on `PATH` for default skills and MCP setup
|
|
14
|
-
- At least one configured runner CLI on `PATH`: `codex`, `
|
|
15
|
-
`
|
|
14
|
+
- At least one configured runner CLI on `PATH`: `codex`, `opencode`, `kimi`,
|
|
15
|
+
`pi`, or a declared command runner
|
|
16
16
|
|
|
17
17
|
Install dependencies:
|
|
18
18
|
|
|
@@ -307,7 +307,6 @@ The installer creates one command surface per configured entrypoint.
|
|
|
307
307
|
|
|
308
308
|
| Host | Generated files | Invocation |
|
|
309
309
|
| ----------- | ----------------------------------------------------------------- | ---------------------------------- |
|
|
310
|
-
| Claude Code | `.claude/commands/<entrypoint>.md`, `.claude/agents/*.md` | `/pipe <task>`, `/inspect <task>`, `/epic <task>` |
|
|
311
310
|
| Codex | `.agents/skills/<entrypoint>/SKILL.md`, `.agents/plugins/oisin-pipeline/commands/<entrypoint>.md`, `.agents/plugins/oisin-pipeline/agents/*.md`, `.codex/config.toml` | `$pipe <task>`, `$inspect <task>`, `$epic <task>`, `/pipe <task>`, `/inspect <task>`, `/epic <task>` |
|
|
312
311
|
| OpenCode | `.opencode/commands/<entrypoint>.md`, `.opencode/agents/*.md` | `/pipe <task>`, `/inspect <task>`, `/epic <task>` |
|
|
313
312
|
| Kimi | `.kimi/commands/<entrypoint>.md`, `.kimi/agents/*.yaml` | `/pipe <task>`, `/inspect <task>`, `/epic <task>` |
|
package/dist/config.d.ts
CHANGED
|
@@ -101,6 +101,15 @@ declare const workflowNodeBaseSchema: z.ZodObject<{
|
|
|
101
101
|
timeout: "timeout";
|
|
102
102
|
}>>>;
|
|
103
103
|
}, z.core.$strict>>;
|
|
104
|
+
task_context: z.ZodOptional<z.ZodObject<{
|
|
105
|
+
acceptance_criteria: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
106
|
+
id: z.ZodString;
|
|
107
|
+
text: z.ZodString;
|
|
108
|
+
}, z.core.$strict>>>;
|
|
109
|
+
description: z.ZodOptional<z.ZodString>;
|
|
110
|
+
id: z.ZodOptional<z.ZodString>;
|
|
111
|
+
title: z.ZodOptional<z.ZodString>;
|
|
112
|
+
}, z.core.$strict>>;
|
|
104
113
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
105
114
|
}, z.core.$strip>;
|
|
106
115
|
type WorkflowNodeBase = z.infer<typeof workflowNodeBaseSchema>;
|
|
@@ -222,10 +231,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
222
231
|
}, z.core.$strict>>;
|
|
223
232
|
output: z.ZodOptional<z.ZodObject<{
|
|
224
233
|
format: z.ZodEnum<{
|
|
234
|
+
json_schema: "json_schema";
|
|
225
235
|
text: "text";
|
|
226
236
|
json: "json";
|
|
227
237
|
jsonl: "jsonl";
|
|
228
|
-
json_schema: "json_schema";
|
|
229
238
|
}>;
|
|
230
239
|
repair: z.ZodOptional<z.ZodObject<{
|
|
231
240
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -265,10 +274,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
265
274
|
disabled: "disabled";
|
|
266
275
|
}>>>;
|
|
267
276
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
277
|
+
json_schema: "json_schema";
|
|
268
278
|
text: "text";
|
|
269
279
|
json: "json";
|
|
270
280
|
jsonl: "jsonl";
|
|
271
|
-
json_schema: "json_schema";
|
|
272
281
|
}>>>;
|
|
273
282
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
274
283
|
skills: z.ZodOptional<z.ZodBoolean>;
|
package/dist/config.js
CHANGED
|
@@ -259,6 +259,15 @@ const hookSchema = z.object({
|
|
|
259
259
|
trusted: z.boolean().optional()
|
|
260
260
|
}).strict();
|
|
261
261
|
const taskContextResolverSchema = z.object({ type: z.string().min(1) }).passthrough();
|
|
262
|
+
const nodeTaskContextSchema = z.object({
|
|
263
|
+
acceptance_criteria: z.array(z.object({
|
|
264
|
+
id: z.string().min(1),
|
|
265
|
+
text: z.string().min(1)
|
|
266
|
+
}).strict()).optional(),
|
|
267
|
+
description: z.string().optional(),
|
|
268
|
+
id: z.string().min(1).optional(),
|
|
269
|
+
title: z.string().optional()
|
|
270
|
+
}).strict();
|
|
262
271
|
const entrypointBaseSchema = z.object({
|
|
263
272
|
description: z.string().optional(),
|
|
264
273
|
task_context: taskContextResolverSchema.optional()
|
|
@@ -277,6 +286,7 @@ const workflowNodeBaseSchema = z.object({
|
|
|
277
286
|
id: z.string(),
|
|
278
287
|
needs: z.array(z.string()).optional(),
|
|
279
288
|
retries: retriesSchema.optional(),
|
|
289
|
+
task_context: nodeTaskContextSchema.optional(),
|
|
280
290
|
timeout_ms: z.number().int().positive().optional()
|
|
281
291
|
});
|
|
282
292
|
const workflowNodeSchema = z.lazy(() => z.discriminatedUnion("kind", [
|
package/dist/install-commands.js
CHANGED
|
@@ -3,7 +3,7 @@ import { loadPipelineConfig } from "./config.js";
|
|
|
3
3
|
import { codexNativeMcpConfig } from "./mcp/native-config.js";
|
|
4
4
|
import { compileWorkflowPlan } from "./workflow-planner.js";
|
|
5
5
|
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
-
import { dirname, join, relative } from "node:path";
|
|
6
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
7
7
|
import { mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
8
8
|
import matter from "gray-matter";
|
|
9
9
|
import { stringify } from "smol-toml";
|
|
@@ -17,6 +17,8 @@ const OWNER_YAML_MARKER_PREFIX = "# @oisincoveney/pipeline:";
|
|
|
17
17
|
const CODEX_PLUGIN_COMMAND_ROOT = ".agents/plugins/oisin-pipeline/commands";
|
|
18
18
|
const CODEX_AGENT_CONFIG_START = "# @oisincoveney/pipeline:codex-agents:start";
|
|
19
19
|
const CODEX_AGENT_CONFIG_END = "# @oisincoveney/pipeline:codex-agents:end";
|
|
20
|
+
const CODEX_AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
|
|
21
|
+
const CODEX_AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
|
|
20
22
|
const ENTRYPOINT_PATH_PATTERNS = {
|
|
21
23
|
codex: [/^\.agents\/skills\/([^/]+)\/SKILL\.md$/, /^\.agents\/plugins\/oisin-pipeline\/commands\/([^/]+)\.md$/],
|
|
22
24
|
opencode: [/^\.opencode\/commands\/([^/]+)\.md$/]
|
|
@@ -261,7 +263,7 @@ function opencodePermission(actor, options = {}) {
|
|
|
261
263
|
write: allowed.has("write") ? "allow" : "deny"
|
|
262
264
|
};
|
|
263
265
|
}
|
|
264
|
-
function opencodeDefinitions(config) {
|
|
266
|
+
function opencodeDefinitions(config, cwd) {
|
|
265
267
|
return [
|
|
266
268
|
...entrypointCommandDefinitions("opencode", config, (id, entrypoint) => ({
|
|
267
269
|
content: markdown({
|
|
@@ -315,7 +317,8 @@ function opencodeDefinitions(config) {
|
|
|
315
317
|
host: "opencode",
|
|
316
318
|
invocation: invocationForHost("opencode"),
|
|
317
319
|
path: `.opencode/agents/${id}.md`
|
|
318
|
-
}))
|
|
320
|
+
})),
|
|
321
|
+
projectAgentsMdDefinition(cwd, "opencode")
|
|
319
322
|
];
|
|
320
323
|
}
|
|
321
324
|
function codexDefinitions(config, cwd) {
|
|
@@ -357,10 +360,49 @@ function codexDefinitions(config, cwd) {
|
|
|
357
360
|
invocation: codexPluginInvocation(id),
|
|
358
361
|
path: `${CODEX_PLUGIN_COMMAND_ROOT}/${id}.md`
|
|
359
362
|
})),
|
|
363
|
+
projectAgentsMdDefinition(cwd, "codex"),
|
|
360
364
|
...nativeCodexProfiles.map(([id, profile]) => codexTomlAgentDefinition(config, cwd, id, profile)),
|
|
361
365
|
codexProjectConfigAgentDefinition()
|
|
362
366
|
];
|
|
363
367
|
}
|
|
368
|
+
function projectAgentsMdDefinition(cwd, host) {
|
|
369
|
+
const repoName = basename(cwd);
|
|
370
|
+
return {
|
|
371
|
+
block: {
|
|
372
|
+
end: CODEX_AGENTS_MD_END,
|
|
373
|
+
start: CODEX_AGENTS_MD_START
|
|
374
|
+
},
|
|
375
|
+
content: [
|
|
376
|
+
CODEX_AGENTS_MD_START,
|
|
377
|
+
GENERATED_MARKER,
|
|
378
|
+
`${OWNER_MARKER_PREFIX}host=codex -->`,
|
|
379
|
+
`${OWNER_MARKER_PREFIX}host=opencode -->`,
|
|
380
|
+
"",
|
|
381
|
+
"## Pipeline Guidance",
|
|
382
|
+
"",
|
|
383
|
+
"This repository is configured with `@oisincoveney/pipeline`.",
|
|
384
|
+
"",
|
|
385
|
+
"- Use `$pipe`, `$inspect`, or `$epic` for Codex skill entrypoints when the user asks for pipeline workflows.",
|
|
386
|
+
"- Use `/pipe`, `/inspect`, or `/epic` for Codex or OpenCode slash-command entrypoints when available.",
|
|
387
|
+
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
388
|
+
"- Prefer the generated pipeline profiles and command surfaces over ad hoc subagent prompts.",
|
|
389
|
+
"",
|
|
390
|
+
"## Pipeline Memory",
|
|
391
|
+
"",
|
|
392
|
+
`Use Qdrant collection \`${repoName}\` for this repository.`,
|
|
393
|
+
"",
|
|
394
|
+
`- Call \`qdrant-find\` before research with \`collection_name: ${repoName}\` unless the user explicitly disables memory.`,
|
|
395
|
+
`- Call \`qdrant-store\` during LEARN with \`collection_name: ${repoName}\` for durable lessons worth reusing.`,
|
|
396
|
+
"- Include metadata with at least `repo`, `phase`, `workflow` or `entrypoint`, `task`, and `outcome` when storing lessons.",
|
|
397
|
+
"",
|
|
398
|
+
CODEX_AGENTS_MD_END,
|
|
399
|
+
""
|
|
400
|
+
].join("\n"),
|
|
401
|
+
host,
|
|
402
|
+
invocation: invocationForHost(host),
|
|
403
|
+
path: "AGENTS.md"
|
|
404
|
+
};
|
|
405
|
+
}
|
|
364
406
|
function codexTomlAgentDefinition(config, cwd, id, profile) {
|
|
365
407
|
const profileWithResolvedModel = {
|
|
366
408
|
...profile,
|
|
@@ -462,9 +504,16 @@ function instructionsPointer(actor) {
|
|
|
462
504
|
function definitionsFor(host, config, cwd) {
|
|
463
505
|
const definitions = {
|
|
464
506
|
codex: () => codexDefinitions(config, cwd),
|
|
465
|
-
opencode: () => opencodeDefinitions(config)
|
|
507
|
+
opencode: () => opencodeDefinitions(config, cwd)
|
|
466
508
|
};
|
|
467
|
-
return (host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => definitions[name]());
|
|
509
|
+
return dedupeDefinitionsByPath((host === "all" ? COMMAND_HOSTS : [host]).flatMap((name) => definitions[name]()));
|
|
510
|
+
}
|
|
511
|
+
function dedupeDefinitionsByPath(definitions) {
|
|
512
|
+
const lastIndexes = /* @__PURE__ */ new Map();
|
|
513
|
+
definitions.forEach((definition, index) => {
|
|
514
|
+
lastIndexes.set(definition.path, index);
|
|
515
|
+
});
|
|
516
|
+
return definitions.filter((definition, index) => lastIndexes.get(definition.path) === index);
|
|
468
517
|
}
|
|
469
518
|
function selectedHosts(host) {
|
|
470
519
|
return host === "all" ? [...COMMAND_HOSTS] : [host];
|
package/dist/pipeline-init.js
CHANGED
|
@@ -313,6 +313,8 @@ rules:
|
|
|
313
313
|
path: .pipeline/rules/verification.md
|
|
314
314
|
|
|
315
315
|
skills:
|
|
316
|
+
schedule-graph-shaping:
|
|
317
|
+
path: .pipeline/skills/schedule-graph-shaping/SKILL.md
|
|
316
318
|
critique:
|
|
317
319
|
path: .agents/skills/critique/SKILL.md
|
|
318
320
|
diagnose:
|
|
@@ -423,7 +425,7 @@ profiles:
|
|
|
423
425
|
description: Refine a baseline schedule into a specialized approved-plan artifact.
|
|
424
426
|
instructions:
|
|
425
427
|
path: .pipeline/prompts/schedule-planner.md
|
|
426
|
-
skills: [
|
|
428
|
+
skills: [schedule-graph-shaping]
|
|
427
429
|
mcp_servers: [serena, context7, backlog, qdrant, github-readonly]
|
|
428
430
|
tools: [read, list, grep, glob, bash]
|
|
429
431
|
filesystem:
|
|
@@ -636,6 +638,62 @@ const SCAFFOLD_FILES = {
|
|
|
636
638
|
[PIPELINE_CONFIG_PATH]: DEFAULT_PIPELINE_YAML,
|
|
637
639
|
[PROFILES_CONFIG_PATH]: DEFAULT_PROFILES_YAML,
|
|
638
640
|
[RUNNERS_CONFIG_PATH]: DEFAULT_RUNNERS_YAML,
|
|
641
|
+
".pipeline/skills/schedule-graph-shaping/SKILL.md": [
|
|
642
|
+
"---",
|
|
643
|
+
"name: schedule-graph-shaping",
|
|
644
|
+
"description: Use when generating or reviewing pipeline schedule graphs for a task or epic. Shapes explicit root DAGs by grouping related tickets and verification work by goal, dependency, and evidence instead of defaulting to one full RED/GREEN/VERIFY chain per ticket.",
|
|
645
|
+
"---",
|
|
646
|
+
"",
|
|
647
|
+
"# Schedule Graph Shaping",
|
|
648
|
+
"",
|
|
649
|
+
"Use this when producing a `pipeline-schedule` YAML artifact.",
|
|
650
|
+
"",
|
|
651
|
+
"## Contract",
|
|
652
|
+
"",
|
|
653
|
+
"- Return only the artifact requested by the schedule planner. Do not add prose.",
|
|
654
|
+
"- Generate exactly one workflow named `root`.",
|
|
655
|
+
"- Do not use `kind: workflow` or embed configured workflow copies such as `default`, `infra`, `track`, or `epic-drain`.",
|
|
656
|
+
"- Every generated agent node must declare a configured `profile`.",
|
|
657
|
+
"- Node IDs must be stable lowercase kebab-case and match `^[a-z][a-z0-9-]*$`.",
|
|
658
|
+
"- Do not invent profiles, node-level skills, or unconfigured gates.",
|
|
659
|
+
"",
|
|
660
|
+
"## Shaping Procedure",
|
|
661
|
+
"",
|
|
662
|
+
"1. Cluster work units by intent before drawing nodes.",
|
|
663
|
+
" Group tickets that validate the same behavior, touch the same subsystem, share acceptance evidence, or must land in a fixed order.",
|
|
664
|
+
"",
|
|
665
|
+
"2. Use RED nodes for test strategy, not ticket counting.",
|
|
666
|
+
" One RED node can cover several GREEN tickets when they share the same failing test suite or behavior contract. Split RED nodes only when the tests are meaningfully independent or different profiles are needed.",
|
|
667
|
+
"",
|
|
668
|
+
"3. Use GREEN nodes for independently implementable slices.",
|
|
669
|
+
" A GREEN node may cover one ticket or a coherent group of tickets. Split GREEN nodes when the work can run in parallel, has different dependencies, has materially different ownership/risk, or would make one node too broad to review.",
|
|
670
|
+
"",
|
|
671
|
+
"4. Use acceptance nodes for user-visible outcomes.",
|
|
672
|
+
" One acceptance node can cover multiple implementation nodes when they produce the same visible outcome or acceptance checklist.",
|
|
673
|
+
"",
|
|
674
|
+
"5. Use verifier nodes for shared evidence.",
|
|
675
|
+
" One verifier can validate multiple tickets when the same real repository commands and checks prove them. Split verifiers only when evidence differs, one area needs specialized inspection, or a dependency boundary requires earlier proof.",
|
|
676
|
+
"",
|
|
677
|
+
"6. Preserve necessary serial order.",
|
|
678
|
+
" Dependencies from the backlog, shared migrations/schema changes, public API changes, and foundational refactors should gate downstream implementation. Independent clusters should fan out and then fan in to shared acceptance, verifier, merge, or review nodes.",
|
|
679
|
+
"",
|
|
680
|
+
"## Task Context",
|
|
681
|
+
"",
|
|
682
|
+
"- Assign every backlog work unit to at least one explicit generated agent node with `task_context.id`.",
|
|
683
|
+
"- Prefer assigning ticket-specific context to GREEN nodes.",
|
|
684
|
+
"- RED, acceptance, and verifier nodes may omit `task_context` when they cover a group; include it only when the node is genuinely ticket-specific.",
|
|
685
|
+
"",
|
|
686
|
+
"## Efficiency Checks",
|
|
687
|
+
"",
|
|
688
|
+
"Before returning the graph, ask:",
|
|
689
|
+
"",
|
|
690
|
+
"- Did I create a RED/GREEN/VERIFY chain just because a ticket exists?",
|
|
691
|
+
"- Can several GREEN nodes share one RED node without losing test-first behavior?",
|
|
692
|
+
"- Can several GREEN nodes share one verifier because the same commands prove them?",
|
|
693
|
+
"- Are independent implementation slices parallelized?",
|
|
694
|
+
"- Are serial edges based on real dependencies rather than habit?",
|
|
695
|
+
""
|
|
696
|
+
].join("\n"),
|
|
639
697
|
".pipeline/prompts/orchestrator.md": [
|
|
640
698
|
"You are the orchestrator for the pipeline.",
|
|
641
699
|
"Use `.pipeline/pipeline.yaml` as the source of truth for workflow order, profiles, gates, hooks, and artifacts.",
|
|
@@ -646,6 +704,9 @@ const SCAFFOLD_FILES = {
|
|
|
646
704
|
].join("\n"),
|
|
647
705
|
".pipeline/prompts/researcher.md": [
|
|
648
706
|
"You are the research phase for the pipeline.",
|
|
707
|
+
"Call `qdrant-find` before local inspection when the qdrant MCP server is available.",
|
|
708
|
+
"Use collection_name equal to the repository directory basename, and skip this only when the user explicitly disables memory.",
|
|
709
|
+
"Surface relevant prior lessons briefly before continuing.",
|
|
649
710
|
"Inspect first-party source, tests, docs, and task context before proposing changes.",
|
|
650
711
|
"Write structured findings that identify relevant files, existing patterns, acceptance criteria, and risks.",
|
|
651
712
|
"Return only valid JSON matching `.pipeline/schemas/research.schema.json`: an object with `findings` and `ac` arrays, plus optional `files`, `risks`, and `target`.",
|
|
@@ -665,13 +726,20 @@ const SCAFFOLD_FILES = {
|
|
|
665
726
|
".pipeline/prompts/schedule-planner.md": [
|
|
666
727
|
"# Schedule planner",
|
|
667
728
|
"",
|
|
668
|
-
"
|
|
729
|
+
"Generate a constrained agent graph as a specialized `pipeline-schedule` YAML artifact for the user task.",
|
|
730
|
+
"",
|
|
731
|
+
"Keep the graph auditable: execution must include research, implementation, and verification.",
|
|
732
|
+
"",
|
|
733
|
+
"Generate exactly one workflow named `root`. Do not embed `default`, `epic-drain`, `infra`, `track`, or other configured workflow copies. Use explicit generated agent, builtin, command, parallel, or group nodes. Do not use `kind: workflow`.",
|
|
734
|
+
"",
|
|
735
|
+
"Use the provided backlog work units as the source of truth when present. Assign each work unit to explicit generated agent nodes with only its `task_context.id`, use only allowed configured profiles, and ensure implementation work has downstream acceptance, verification, or review coverage. Do not invent profiles or node-level skill overrides.",
|
|
736
|
+
"Do not copy backlog descriptions or acceptance criteria into output; the scheduler hydrates them from the assigned `task_context.id` after parsing.",
|
|
669
737
|
"",
|
|
670
|
-
"
|
|
738
|
+
"Shape the graph by intent, not by ticket count. Do not create a full RED/GREEN/ACCEPTANCE/VERIFY chain for each backlog ticket unless each step needs ticket-specific evidence. Use one RED node for a group of tickets when they share a test strategy, fan out to parallel GREEN implementation nodes where the work can be implemented independently, and fan back in to shared acceptance or verifier nodes when the same acceptance checklist or real repository commands prove the group. Only serialize ticket nodes when the backlog, a shared migration/schema/API dependency, or implementation risk requires it.",
|
|
671
739
|
"",
|
|
672
|
-
"
|
|
740
|
+
"Return exactly one YAML document and nothing else. Do not wrap it in Markdown fences. Do not include commentary, plans, task lists, or explanations. Do not modify files. Do not invoke other agents.",
|
|
673
741
|
"",
|
|
674
|
-
"
|
|
742
|
+
"Use block-style YAML for objects and arrays. Do not use compact inline mappings like `{ id: PIPE-41.1, title: ... }`. Quote scalar strings that contain punctuation such as `:`, `#`, `[`, `]`, `{`, or `}`.",
|
|
675
743
|
""
|
|
676
744
|
].join("\n"),
|
|
677
745
|
".pipeline/prompts/test-writer.md": [
|
|
@@ -744,6 +812,8 @@ const SCAFFOLD_FILES = {
|
|
|
744
812
|
".pipeline/prompts/learner.md": [
|
|
745
813
|
"You are the LEARN phase for the pipeline.",
|
|
746
814
|
"Store durable lessons from the run when useful and report qdrant-store evidence.",
|
|
815
|
+
"Call `qdrant-store` with collection_name equal to the repository directory basename.",
|
|
816
|
+
"Include metadata with at least repo, phase, workflow or entrypoint, task, and outcome.",
|
|
747
817
|
"Do not write local markdown knowledge as the durable sink.",
|
|
748
818
|
"Return only valid JSON matching `.pipeline/schemas/learn.schema.json`: an object with `qdrant` and `evidence`.",
|
|
749
819
|
"Do not wrap the JSON in Markdown fences or add prose outside the JSON object.",
|
package/dist/pipeline-runtime.js
CHANGED
|
@@ -870,7 +870,7 @@ async function executeWorkflowNode(node, context) {
|
|
|
870
870
|
runId: context.runId,
|
|
871
871
|
signal: context.signal,
|
|
872
872
|
task: context.task,
|
|
873
|
-
taskContext: context
|
|
873
|
+
taskContext: effectiveTaskContext(node, context),
|
|
874
874
|
workflowId: node.workflow,
|
|
875
875
|
worktreePath: worktree.worktreePath ?? context.worktreePath
|
|
876
876
|
});
|
|
@@ -1155,7 +1155,7 @@ function renderAgentPrompt(node, context) {
|
|
|
1155
1155
|
`Workflow: ${context.workflowId}`,
|
|
1156
1156
|
`Node: ${node.id}`,
|
|
1157
1157
|
node.profile ? `Profile: ${node.profile}` : "",
|
|
1158
|
-
renderTaskContext(context
|
|
1158
|
+
renderTaskContext(effectiveTaskContext(node, context)),
|
|
1159
1159
|
"",
|
|
1160
1160
|
"Declared grants:",
|
|
1161
1161
|
`- tools: ${(profile?.tools ?? []).join(", ") || "none"}`,
|
|
@@ -1171,6 +1171,9 @@ function renderAgentPrompt(node, context) {
|
|
|
1171
1171
|
...node.needs.map((need) => `## ${need}\n${context.lastOutputByNode.get(need) ?? ""}`)
|
|
1172
1172
|
].filter(Boolean).join("\n");
|
|
1173
1173
|
}
|
|
1174
|
+
function effectiveTaskContext(node, context) {
|
|
1175
|
+
return node.taskContext ?? context.taskContext;
|
|
1176
|
+
}
|
|
1174
1177
|
function inheritedOutputSections(node, context) {
|
|
1175
1178
|
const ownNeeds = new Set(node.needs);
|
|
1176
1179
|
const inherited = [...context.inheritedOutputNodeIds].filter((id) => !ownNeeds.has(id) && context.lastOutputByNode.has(id));
|
|
@@ -1692,12 +1695,13 @@ function emitAgentFinish(context, plan, attempt, result) {
|
|
|
1692
1695
|
}
|
|
1693
1696
|
function evaluateGate(gate, nodeId, context, attempt) {
|
|
1694
1697
|
const gateId = gate.id ?? `${gate.kind}:${nodeId}`;
|
|
1698
|
+
const node = context.plan.graph.node(nodeId);
|
|
1695
1699
|
switch (gate.kind) {
|
|
1696
1700
|
case "command": return evaluateCommandGate(gate, gateId, nodeId, context);
|
|
1697
1701
|
case "artifact": return evaluateArtifactGate(gate, gateId, nodeId, context);
|
|
1698
1702
|
case "builtin": return evaluateBuiltinGate(gate, gateId, nodeId, context);
|
|
1699
1703
|
case "verdict": return evaluateVerdictGate(gate, gateId, nodeId, context, attempt);
|
|
1700
|
-
case "acceptance": return evaluateAcceptanceGate(gate, gateId, nodeId, context, attempt);
|
|
1704
|
+
case "acceptance": return evaluateAcceptanceGate(gate, gateId, nodeId, context, attempt, node);
|
|
1701
1705
|
case "changed_files": return evaluateChangedFilesGate(gate, gateId, nodeId, context);
|
|
1702
1706
|
case "json_schema": return evaluateJsonSchemaGate(gate, gateId, nodeId, context, attempt);
|
|
1703
1707
|
default: return assertNever(gate);
|
|
@@ -1781,8 +1785,8 @@ function evaluateVerdictGate(gate, gateId, nodeId, context, attempt) {
|
|
|
1781
1785
|
reason: passed ? void 0 : "verdict requirement failed"
|
|
1782
1786
|
};
|
|
1783
1787
|
}
|
|
1784
|
-
function evaluateAcceptanceGate(gate, gateId, nodeId, context, attempt) {
|
|
1785
|
-
const expected = context.taskContext?.acceptanceCriteria ?? [];
|
|
1788
|
+
function evaluateAcceptanceGate(gate, gateId, nodeId, context, attempt, node) {
|
|
1789
|
+
const expected = (node ? effectiveTaskContext(node, context) : context.taskContext)?.acceptanceCriteria ?? [];
|
|
1786
1790
|
if (expected.length === 0) return {
|
|
1787
1791
|
evidence: ["no acceptance criteria in task context"],
|
|
1788
1792
|
gateId,
|
|
@@ -2076,7 +2080,7 @@ function hookPayload(context, failure, node, gateId) {
|
|
|
2076
2080
|
},
|
|
2077
2081
|
failure,
|
|
2078
2082
|
task: context.task,
|
|
2079
|
-
taskContext: context.taskContext
|
|
2083
|
+
taskContext: node ? effectiveTaskContext(node, context) : context.taskContext
|
|
2080
2084
|
};
|
|
2081
2085
|
}
|
|
2082
2086
|
function renderTemplate(value, context, failure, node, gateId) {
|