@oisincoveney/pipeline 2.7.0 → 2.8.1

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.
Files changed (60) hide show
  1. package/defaults/pipeline.yaml +25 -0
  2. package/dist/argo-graph.js +7 -7
  3. package/dist/argo-submit.d.ts +2 -4
  4. package/dist/argo-submit.js +80 -80
  5. package/dist/bench/eval-report.js +27 -0
  6. package/dist/cli/program.js +19 -3
  7. package/dist/cluster-doctor.js +89 -101
  8. package/dist/commands/bench-command.js +18 -0
  9. package/dist/config/defaults.js +9 -19
  10. package/dist/config/load.js +47 -37
  11. package/dist/config/schemas.d.ts +24 -7
  12. package/dist/config/schemas.js +20 -7
  13. package/dist/context/repo-map.js +203 -0
  14. package/dist/install-commands/opencode.js +10 -1
  15. package/dist/mcp/gateway-error.js +15 -0
  16. package/dist/mcp/gateway.js +119 -220
  17. package/dist/moka-global-config.js +20 -20
  18. package/dist/moka-submit.d.ts +6 -6
  19. package/dist/pipeline-init.js +18 -12
  20. package/dist/pipeline-runtime.js +592 -372
  21. package/dist/planning/compile.d.ts +8 -3
  22. package/dist/planning/compile.js +7 -7
  23. package/dist/planning/generate.d.ts +6 -1
  24. package/dist/planning/generate.js +29 -7
  25. package/dist/run-state/git-refs.js +124 -94
  26. package/dist/runner-command-contract.d.ts +6 -1
  27. package/dist/runner-command-contract.js +6 -5
  28. package/dist/runner-event-schema.d.ts +6 -6
  29. package/dist/runner-event-sink.js +37 -68
  30. package/dist/runner.d.ts +6 -1
  31. package/dist/runner.js +3 -3
  32. package/dist/runtime/agent-node/agent-node.js +218 -159
  33. package/dist/runtime/changed-files/changed-files.js +15 -27
  34. package/dist/runtime/changed-files/index.js +2 -0
  35. package/dist/runtime/drain-merge/drain-merge.js +124 -82
  36. package/dist/runtime/gates/gates.js +45 -27
  37. package/dist/runtime/hooks/hooks.js +74 -29
  38. package/dist/runtime/local-scheduler.js +45 -0
  39. package/dist/runtime/opencode-server.js +32 -23
  40. package/dist/runtime/opencode-session-executor.js +101 -44
  41. package/dist/runtime/parallel-node/parallel-node.js +93 -75
  42. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +49 -4
  43. package/dist/runtime/run-journal.js +21 -0
  44. package/dist/runtime/scheduler.js +122 -93
  45. package/dist/runtime/select-candidate/select-candidate.js +52 -24
  46. package/dist/runtime/services/agent-node-runtime-service.js +15 -0
  47. package/dist/runtime/services/command-executor-service.js +8 -0
  48. package/dist/runtime/services/config-io-service.js +42 -0
  49. package/dist/runtime/services/drain-merge-git-service.js +10 -0
  50. package/dist/runtime/services/git-porcelain-service.js +38 -0
  51. package/dist/runtime/services/kubernetes-argo-service.d.ts +13 -0
  52. package/dist/runtime/services/kubernetes-argo-service.js +81 -0
  53. package/dist/runtime/services/mcp-gateway-service.js +184 -0
  54. package/dist/runtime/services/opencode-sdk-service.js +27 -0
  55. package/dist/runtime/services/runner-event-sink-http-service.js +80 -0
  56. package/dist/runtime/services/select-candidate-service.js +13 -0
  57. package/dist/runtime/services/worktree-service.js +18 -0
  58. package/dist/schedule/passes/candidates.js +17 -8
  59. package/docs/config-architecture.md +105 -0
  60. package/package.json +7 -2
@@ -1,7 +1,6 @@
1
- import { PipelineConfigError, configIssuesFromZodError, validationError } from "./schemas.js";
2
- import { parseDocument } from "yaml";
1
+ import { ConfigIoService, parseConfigYamlAs, runConfigIoSync } from "../runtime/services/config-io-service.js";
3
2
  import { z } from "zod";
4
- import { readFileSync } from "node:fs";
3
+ import { Effect } from "effect";
5
4
  //#region src/config/defaults.ts
6
5
  const PIPELINE_CONFIG_PATH = ".pipeline/pipeline.yaml";
7
6
  const RUNNERS_CONFIG_PATH = ".pipeline/runners.yaml";
@@ -9,7 +8,9 @@ const PROFILES_CONFIG_PATH = ".pipeline/profiles.yaml";
9
8
  const OPENCODE_ECOSYSTEM_MANIFEST_PATH = "defaults/opencode-ecosystem.yaml";
10
9
  const DEFAULT_PACKAGE_DEFAULTS_ROOT = new URL("../../defaults/", import.meta.url);
11
10
  function loadDefaultYaml(filename) {
12
- return readFileSync(new URL(filename, DEFAULT_PACKAGE_DEFAULTS_ROOT), "utf8");
11
+ return runConfigIoSync(Effect.gen(function* () {
12
+ return yield* (yield* ConfigIoService).readText(new URL(filename, DEFAULT_PACKAGE_DEFAULTS_ROOT));
13
+ }));
13
14
  }
14
15
  const PACKAGE_DEFAULT_RUNNERS_YAML = loadDefaultYaml("runners.yaml");
15
16
  const PACKAGE_DEFAULT_PROFILES_YAML = loadDefaultYaml("profiles.yaml");
@@ -111,24 +112,13 @@ const openCodeEcosystemManifestSchema = z.object({
111
112
  version: z.literal(1)
112
113
  }).strict();
113
114
  function parseOpenCodeEcosystemManifest(source, sourcePath = OPENCODE_ECOSYSTEM_MANIFEST_PATH) {
114
- return parseYamlAs(source, sourcePath, openCodeEcosystemManifestSchema);
115
+ return runConfigIoSync(parseConfigYamlAs(source, sourcePath, openCodeEcosystemManifestSchema));
115
116
  }
116
117
  function loadDefaultOpenCodeEcosystemManifest() {
117
- return parseOpenCodeEcosystemManifest(readFileSync(DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST_URL, "utf8"));
118
+ return runConfigIoSync(Effect.gen(function* () {
119
+ return yield* parseConfigYamlAs(yield* (yield* ConfigIoService).readText(DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST_URL), OPENCODE_ECOSYSTEM_MANIFEST_PATH, openCodeEcosystemManifestSchema);
120
+ }));
118
121
  }
119
122
  const DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST = loadDefaultOpenCodeEcosystemManifest();
120
- function parseYamlAs(source, sourcePath, schema) {
121
- const document = parseDocument(source, {
122
- prettyErrors: false,
123
- uniqueKeys: true
124
- });
125
- if (document.errors.length > 0) throw new PipelineConfigError("PIPELINE_CONFIG_PARSE_ERROR", `Failed to parse ${sourcePath}`, document.errors.map((err) => ({
126
- message: err.message,
127
- path: sourcePath
128
- })));
129
- const parsed = schema.safeParse(document.toJS());
130
- if (!parsed.success) throw validationError(configIssuesFromZodError(parsed.error));
131
- return parsed.data;
132
- }
133
123
  //#endregion
134
124
  export { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST, OPENCODE_ECOSYSTEM_MANIFEST_PATH, PACKAGE_DEFAULT_PIPELINE_YAML, PACKAGE_DEFAULT_PROFILES_YAML, PACKAGE_DEFAULT_RUNNERS_YAML, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, RUNNERS_CONFIG_PATH, parseOpenCodeEcosystemManifest };
@@ -1,13 +1,14 @@
1
- import { PipelineConfigError, configIssuesFromZodError, pipelineFileSchema, profilesFileSchema, runnersFileSchema, validationError } from "./schemas.js";
1
+ import { pipelineFileSchema, profilesFileSchema, runnersFileSchema } from "./schemas.js";
2
+ import { parseConfigYamlAs, runConfigIoSync } from "../runtime/services/config-io-service.js";
2
3
  import { PACKAGE_DEFAULT_PIPELINE_YAML, PACKAGE_DEFAULT_PROFILES_YAML, PACKAGE_DEFAULT_RUNNERS_YAML, PIPELINE_CONFIG_PATH, PROFILES_CONFIG_PATH, RUNNERS_CONFIG_PATH } from "./defaults.js";
3
4
  import { validatePipelineConfig } from "./validate.js";
4
- import { parseDocument } from "yaml";
5
+ import { Effect } from "effect";
5
6
  //#region src/config/load.ts
6
7
  function loadPipelineConfig(projectRoot, options = {}) {
7
8
  return loadPackagePipelineConfig(projectRoot, options);
8
9
  }
9
10
  function loadPackagePipelineConfig(projectRoot, options = {}) {
10
- return parsePipelineConfigParts({
11
+ return runConfigIoSync(parsePipelineConfigPartsEffect({
11
12
  pipeline: PACKAGE_DEFAULT_PIPELINE_YAML,
12
13
  profiles: PACKAGE_DEFAULT_PROFILES_YAML,
13
14
  runners: PACKAGE_DEFAULT_RUNNERS_YAML
@@ -15,7 +16,7 @@ function loadPackagePipelineConfig(projectRoot, options = {}) {
15
16
  pipeline: "@oisincoveney/pipeline/defaults/pipeline.yaml",
16
17
  profiles: "@oisincoveney/pipeline/defaults/profiles.yaml",
17
18
  runners: "@oisincoveney/pipeline/defaults/runners.yaml"
18
- }, options);
19
+ }, options));
19
20
  }
20
21
  function parsePipelineConfigYaml(source, sourcePath = PIPELINE_CONFIG_PATH, projectRoot) {
21
22
  return parsePipelineConfigParts({
@@ -28,46 +29,55 @@ function parsePipelineConfigYaml(source, sourcePath = PIPELINE_CONFIG_PATH, proj
28
29
  runners: RUNNERS_CONFIG_PATH
29
30
  });
30
31
  }
32
+ function durabilityField(durability) {
33
+ return durability ? { durability } : {};
34
+ }
35
+ function pipe83Fields(pipeline) {
36
+ const keys = [
37
+ "best_of_n",
38
+ "context_handoff",
39
+ "parallel_worktrees",
40
+ "repo_map"
41
+ ];
42
+ const source = pipeline;
43
+ const out = {};
44
+ for (const key of keys) if (source[key] !== void 0) out[key] = source[key];
45
+ return out;
46
+ }
31
47
  function parsePipelineConfigParts(sources, projectRoot, sourcePaths = {
32
48
  pipeline: PIPELINE_CONFIG_PATH,
33
49
  profiles: PROFILES_CONFIG_PATH,
34
50
  runners: RUNNERS_CONFIG_PATH
35
51
  }, options = {}) {
36
- const runners = parseYamlAs(sources.runners, sourcePaths.runners, runnersFileSchema);
37
- const profiles = parseYamlAs(sources.profiles, sourcePaths.profiles, profilesFileSchema);
38
- const pipeline = parseYamlAs(sources.pipeline, sourcePaths.pipeline, pipelineFileSchema);
39
- return validatePipelineConfig({
40
- default_workflow: pipeline.default_workflow,
41
- entrypoints: pipeline.entrypoints,
42
- hooks: pipeline.hooks,
43
- ...profiles.mcp_gateway ? { mcp_gateway: profiles.mcp_gateway } : {},
44
- mcp_servers: profiles.mcp_servers,
45
- ...pipeline.orchestrator ? { orchestrator: pipeline.orchestrator } : {},
46
- profiles: profiles.profiles,
47
- runner_command: pipeline.runner_command,
48
- rules: profiles.rules,
49
- runners: runners.runners,
50
- scheduler: pipeline.scheduler,
51
- schedules: pipeline.schedules,
52
- skills: profiles.skills,
53
- ...pipeline.task_context ? { task_context: pipeline.task_context } : {},
54
- token_budget: pipeline.token_budget,
55
- version: 1,
56
- workflows: pipeline.workflows
57
- }, projectRoot, options);
52
+ return runConfigIoSync(parsePipelineConfigPartsEffect(sources, projectRoot, sourcePaths, options));
58
53
  }
59
- function parseYamlAs(source, sourcePath, schema) {
60
- const document = parseDocument(source, {
61
- prettyErrors: false,
62
- uniqueKeys: true
54
+ function parsePipelineConfigPartsEffect(sources, projectRoot, sourcePaths, options) {
55
+ return Effect.gen(function* () {
56
+ const runners = yield* parseConfigYamlAs(sources.runners, sourcePaths.runners, runnersFileSchema);
57
+ const profiles = yield* parseConfigYamlAs(sources.profiles, sourcePaths.profiles, profilesFileSchema);
58
+ const pipeline = yield* parseConfigYamlAs(sources.pipeline, sourcePaths.pipeline, pipelineFileSchema);
59
+ return validatePipelineConfig({
60
+ default_workflow: pipeline.default_workflow,
61
+ ...durabilityField(pipeline.durability),
62
+ ...pipe83Fields(pipeline),
63
+ entrypoints: pipeline.entrypoints,
64
+ hooks: pipeline.hooks,
65
+ ...profiles.mcp_gateway ? { mcp_gateway: profiles.mcp_gateway } : {},
66
+ mcp_servers: profiles.mcp_servers,
67
+ ...pipeline.orchestrator ? { orchestrator: pipeline.orchestrator } : {},
68
+ profiles: profiles.profiles,
69
+ runner_command: pipeline.runner_command,
70
+ rules: profiles.rules,
71
+ runners: runners.runners,
72
+ scheduler: pipeline.scheduler,
73
+ schedules: pipeline.schedules,
74
+ skills: profiles.skills,
75
+ ...pipeline.task_context ? { task_context: pipeline.task_context } : {},
76
+ token_budget: pipeline.token_budget,
77
+ version: 1,
78
+ workflows: pipeline.workflows
79
+ }, projectRoot, options);
63
80
  });
64
- if (document.errors.length > 0) throw new PipelineConfigError("PIPELINE_CONFIG_PARSE_ERROR", `Failed to parse ${sourcePath}`, document.errors.map((err) => ({
65
- message: err.message,
66
- path: sourcePath
67
- })));
68
- const parsed = schema.safeParse(document.toJS());
69
- if (!parsed.success) throw validationError(configIssuesFromZodError(parsed.error));
70
- return parsed.data;
71
81
  }
72
82
  //#endregion
73
83
  export { loadPackagePipelineConfig, loadPipelineConfig, parsePipelineConfigParts, parsePipelineConfigYaml };
@@ -14,9 +14,14 @@ interface PipelineConfigIssue {
14
14
  message: string;
15
15
  path?: string;
16
16
  }
17
- declare class PipelineConfigError extends Error {
18
- code: PipelineConfigErrorCode;
19
- issues: PipelineConfigIssue[];
17
+ declare const PipelineConfigError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => import("effect/Cause").YieldableError & {
18
+ readonly _tag: "PipelineConfigError";
19
+ } & Readonly<A>;
20
+ declare class PipelineConfigError extends PipelineConfigError_base<{
21
+ readonly code: PipelineConfigErrorCode;
22
+ readonly message: string;
23
+ readonly issues: PipelineConfigIssue[];
24
+ }> {
20
25
  constructor(code: PipelineConfigErrorCode, message: string, issues?: PipelineConfigIssue[]);
21
26
  }
22
27
  declare const workflowNodeBaseSchema: z.ZodObject<{
@@ -221,8 +226,8 @@ declare const configSchema: z.ZodObject<{
221
226
  policy: z.ZodOptional<z.ZodObject<{
222
227
  commands: z.ZodOptional<z.ZodEnum<{
223
228
  allow: "allow";
224
- deny: "deny";
225
229
  "trusted-only": "trusted-only";
230
+ deny: "deny";
226
231
  }>>;
227
232
  modules: z.ZodOptional<z.ZodEnum<{
228
233
  allow: "allow";
@@ -245,9 +250,13 @@ declare const configSchema: z.ZodObject<{
245
250
  }>>;
246
251
  }, z.core.$strict>>>;
247
252
  default_profile: z.ZodOptional<z.ZodString>;
253
+ host_scope: z.ZodDefault<z.ZodEnum<{
254
+ project: "project";
255
+ global: "global";
256
+ }>>;
248
257
  mode: z.ZodEnum<{
249
- local: "local";
250
258
  hosted: "hosted";
259
+ local: "local";
251
260
  }>;
252
261
  provider: z.ZodLiteral<"toolhive">;
253
262
  authorization_env: z.ZodDefault<z.ZodString>;
@@ -290,10 +299,10 @@ declare const configSchema: z.ZodObject<{
290
299
  }, z.core.$strict>>;
291
300
  output: z.ZodOptional<z.ZodObject<{
292
301
  format: z.ZodEnum<{
293
- json_schema: "json_schema";
294
302
  text: "text";
295
303
  json: "json";
296
304
  jsonl: "jsonl";
305
+ json_schema: "json_schema";
297
306
  }>;
298
307
  repair: z.ZodOptional<z.ZodObject<{
299
308
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -362,10 +371,10 @@ declare const configSchema: z.ZodObject<{
362
371
  disabled: "disabled";
363
372
  }>>>;
364
373
  output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
365
- json_schema: "json_schema";
366
374
  text: "text";
367
375
  json: "json";
368
376
  jsonl: "jsonl";
377
+ json_schema: "json_schema";
369
378
  }>>>;
370
379
  rules: z.ZodOptional<z.ZodBoolean>;
371
380
  skills: z.ZodOptional<z.ZodBoolean>;
@@ -502,9 +511,17 @@ declare const configSchema: z.ZodObject<{
502
511
  enabled: z.ZodDefault<z.ZodBoolean>;
503
512
  model: z.ZodOptional<z.ZodString>;
504
513
  }, z.core.$strict>>;
514
+ durability: z.ZodOptional<z.ZodObject<{
515
+ dir: z.ZodDefault<z.ZodString>;
516
+ enabled: z.ZodDefault<z.ZodBoolean>;
517
+ }, z.core.$strict>>;
505
518
  parallel_worktrees: z.ZodOptional<z.ZodObject<{
506
519
  enabled: z.ZodDefault<z.ZodBoolean>;
507
520
  }, z.core.$strict>>;
521
+ repo_map: z.ZodOptional<z.ZodObject<{
522
+ enabled: z.ZodDefault<z.ZodBoolean>;
523
+ token_budget: z.ZodDefault<z.ZodNumber>;
524
+ }, z.core.$strict>>;
508
525
  token_budget: z.ZodDefault<z.ZodObject<{
509
526
  default_context_window: z.ZodDefault<z.ZodNumber>;
510
527
  max_context_pct: z.ZodDefault<z.ZodNumber>;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { Data } from "effect";
2
3
  //#region src/config/schemas.ts
3
4
  const ID_RE = /^[a-z][a-z0-9-]*$/;
4
5
  const RUNNER_TYPES = ["opencode", "command"];
@@ -58,14 +59,13 @@ const DEFAULT_RUNNER_COMMAND_GIT_COMMITTER = {
58
59
  email: "git@oisin.ee",
59
60
  name: "oisin-bot"
60
61
  };
61
- var PipelineConfigError = class extends Error {
62
- code;
63
- issues;
62
+ var PipelineConfigError = class extends Data.TaggedError("PipelineConfigError") {
64
63
  constructor(code, message, issues = []) {
65
- super(message);
66
- this.name = "PipelineConfigError";
67
- this.code = code;
68
- this.issues = issues;
64
+ super({
65
+ code,
66
+ message,
67
+ issues
68
+ });
69
69
  }
70
70
  };
71
71
  const strictRecord = (valueSchema) => z.record(z.string(), valueSchema);
@@ -155,6 +155,7 @@ const mcpGatewayBackendSchema = z.object({
155
155
  const mcpGatewaySchema = z.object({
156
156
  backends: strictRecord(mcpGatewayBackendSchema).default({}),
157
157
  default_profile: z.string().min(1).optional(),
158
+ host_scope: z.enum(["project", "global"]).default("project"),
158
159
  mode: z.enum(["hosted", "local"]),
159
160
  provider: z.literal("toolhive"),
160
161
  authorization_env: z.string().min(1).default("PIPELINE_MCP_GATEWAY_AUTHORIZATION"),
@@ -466,12 +467,20 @@ const contextHandoffSchema = z.object({
466
467
  model: z.string().optional()
467
468
  }).strict();
468
469
  const parallelWorktreesSchema = z.object({ enabled: z.boolean().default(false) }).strict();
470
+ const durabilitySchema = z.object({
471
+ dir: z.string().min(1).default(".pipeline/journal"),
472
+ enabled: z.boolean().default(false)
473
+ }).strict();
469
474
  const bestOfNSchema = z.object({
470
475
  categories: z.array(z.string()).default(["green"]),
471
476
  enabled: z.boolean().default(false),
472
477
  judge_model: z.string().optional(),
473
478
  n: z.number().int().positive().default(1)
474
479
  }).strict();
480
+ const repoMapSchema = z.object({
481
+ enabled: z.boolean().default(false),
482
+ token_budget: z.number().int().positive().default(2e3)
483
+ }).strict();
475
484
  const pipelineFileSchema = z.object({
476
485
  default_workflow: z.string(),
477
486
  entrypoints: strictRecord(entrypointSchema).default({}),
@@ -495,7 +504,9 @@ const pipelineFileSchema = z.object({
495
504
  task_context: taskContextResolverSchema.optional(),
496
505
  best_of_n: bestOfNSchema.optional(),
497
506
  context_handoff: contextHandoffSchema.optional(),
507
+ durability: durabilitySchema.optional(),
498
508
  parallel_worktrees: parallelWorktreesSchema.optional(),
509
+ repo_map: repoMapSchema.optional(),
499
510
  token_budget: tokenBudgetSchema.default(DEFAULT_TOKEN_BUDGET),
500
511
  workflows: strictRecord(workflowSchema).default({}),
501
512
  version: z.literal(1)
@@ -529,7 +540,9 @@ const configSchema = z.object({
529
540
  task_context: taskContextResolverSchema.optional(),
530
541
  best_of_n: bestOfNSchema.optional(),
531
542
  context_handoff: contextHandoffSchema.optional(),
543
+ durability: durabilitySchema.optional(),
532
544
  parallel_worktrees: parallelWorktreesSchema.optional(),
545
+ repo_map: repoMapSchema.optional(),
533
546
  token_budget: tokenBudgetSchema.default(DEFAULT_TOKEN_BUDGET),
534
547
  version: z.literal(1),
535
548
  workflows: strictRecord(workflowSchema).default({})
@@ -0,0 +1,203 @@
1
+ import { estimateTokens } from "../token-estimator.js";
2
+ import { createRequire } from "node:module";
3
+ import { readFileSync, readdirSync, statSync } from "node:fs";
4
+ import { extname, join, relative } from "node:path";
5
+ import Graph from "graphology";
6
+ import pagerank from "graphology-metrics/centrality/pagerank.js";
7
+ import { Language, Parser, Query } from "web-tree-sitter";
8
+ //#region src/context/repo-map.ts
9
+ const SOURCE_EXTENSIONS = new Set([
10
+ ".cjs",
11
+ ".js",
12
+ ".jsx",
13
+ ".mjs",
14
+ ".ts",
15
+ ".tsx"
16
+ ]);
17
+ const SKIP_DIRS = new Set([
18
+ "node_modules",
19
+ ".git",
20
+ "dist",
21
+ ".pipeline"
22
+ ]);
23
+ const SEED_BONUS = 1;
24
+ const WORD_RE = /[a-z_][a-z0-9_]+/gi;
25
+ const require = createRequire(import.meta.url);
26
+ let parserPromise = null;
27
+ const languageCache = /* @__PURE__ */ new Map();
28
+ const queryCache = /* @__PURE__ */ new Map();
29
+ function getParser() {
30
+ parserPromise ??= Parser.init().then(() => new Parser());
31
+ return parserPromise;
32
+ }
33
+ function loadLanguage(grammar) {
34
+ const cached = languageCache.get(grammar);
35
+ if (cached) return cached;
36
+ const promise = Language.load(require.resolve(`tree-sitter-${grammar}/tree-sitter-${grammar}.wasm`));
37
+ languageCache.set(grammar, promise);
38
+ return promise;
39
+ }
40
+ function tagsQuery(language, grammar) {
41
+ const cached = queryCache.get(grammar);
42
+ if (cached) return cached;
43
+ const query = new Query(language, `${readFileSync(require.resolve("tree-sitter-javascript/queries/tags.scm"), "utf8")}\n${readFileSync(require.resolve("tree-sitter-typescript/queries/tags.scm"), "utf8")}`);
44
+ queryCache.set(grammar, query);
45
+ return query;
46
+ }
47
+ function grammarFor(file) {
48
+ const ext = extname(file);
49
+ return ext === ".ts" || ext === ".tsx" ? "typescript" : "javascript";
50
+ }
51
+ function discoverFiles(root) {
52
+ const found = [];
53
+ walkDir(root, found);
54
+ return found.sort();
55
+ }
56
+ function walkDir(dir, found) {
57
+ for (const entry of readdirSync(dir).sort()) handleEntry(join(dir, entry), entry, found);
58
+ }
59
+ function handleEntry(full, name, found) {
60
+ if (SKIP_DIRS.has(name)) return;
61
+ if (statSync(full).isDirectory()) {
62
+ walkDir(full, found);
63
+ return;
64
+ }
65
+ if (SOURCE_EXTENSIONS.has(extname(name))) found.push(full);
66
+ }
67
+ async function tagFile(root, file) {
68
+ const parser = await getParser();
69
+ const grammar = grammarFor(file);
70
+ const language = await loadLanguage(grammar);
71
+ parser.setLanguage(language);
72
+ const path = relative(root, file);
73
+ const tree = parser.parse(readFileSync(file, "utf8"));
74
+ const tags = {
75
+ definitions: [],
76
+ path,
77
+ references: []
78
+ };
79
+ if (!tree) return tags;
80
+ for (const match of tagsQuery(language, grammar).matches(tree.rootNode)) addMatch(tags, path, match);
81
+ return tags;
82
+ }
83
+ function addMatch(tags, path, match) {
84
+ const nameCapture = match.captures.find((c) => c.name === "name");
85
+ if (!nameCapture) return;
86
+ const name = nameCapture.node.text;
87
+ const def = match.captures.find((c) => c.name.startsWith("definition."));
88
+ if (def) {
89
+ tags.definitions.push({
90
+ endLine: def.node.endPosition.row + 1,
91
+ kind: def.name.slice(11),
92
+ name,
93
+ path,
94
+ startLine: def.node.startPosition.row + 1
95
+ });
96
+ return;
97
+ }
98
+ if (match.captures.some((c) => c.name.startsWith("reference."))) tags.references.push(name);
99
+ }
100
+ function definitionKey(def) {
101
+ return `def:${def.path}#${def.name}#${def.startLine}`;
102
+ }
103
+ function isSeeded(def, seedNames, artifacts) {
104
+ if (seedNames.has(def.name.toLowerCase())) return true;
105
+ return artifacts.some((artifact) => artifact.path === def.path && (!artifact.lineRange || def.startLine <= artifact.lineRange[1] && def.endLine >= artifact.lineRange[0]));
106
+ }
107
+ function buildGraph(fileTags, input) {
108
+ const seedNames = new Set(input.taskText.toLowerCase().match(WORD_RE) ?? []);
109
+ const graph = new Graph({
110
+ allowSelfLoops: false,
111
+ type: "directed"
112
+ });
113
+ const defsByName = /* @__PURE__ */ new Map();
114
+ for (const file of fileTags) for (const def of file.definitions) addDefNode(graph, defsByName, def, isSeeded(def, seedNames, input.artifacts));
115
+ linkReferences(graph, fileTags, defsByName);
116
+ return graph;
117
+ }
118
+ function addDefNode(graph, defsByName, def, matchedSeed) {
119
+ const key = definitionKey(def);
120
+ graph.mergeNode(key, {
121
+ def,
122
+ matchedSeed
123
+ });
124
+ defsByName.set(def.name, [...defsByName.get(def.name) ?? [], key]);
125
+ }
126
+ function linkReferences(graph, fileTags, defsByName) {
127
+ for (const file of fileTags) {
128
+ const fileKey = `file:${file.path}`;
129
+ graph.mergeNode(fileKey);
130
+ linkFileReferences(graph, fileKey, file.references, defsByName);
131
+ }
132
+ }
133
+ function linkFileReferences(graph, fileKey, references, defsByName) {
134
+ for (const name of references) for (const target of defsByName.get(name) ?? []) graph.mergeEdge(fileKey, target, { weight: 1 });
135
+ }
136
+ function pagerankScores(graph) {
137
+ try {
138
+ return pagerank(graph, {
139
+ getEdgeWeight: "weight",
140
+ maxIterations: 200,
141
+ tolerance: 1e-4
142
+ });
143
+ } catch {
144
+ return {};
145
+ }
146
+ }
147
+ function rankDefinitions(graph) {
148
+ const scores = pagerankScores(graph);
149
+ const ranked = [];
150
+ graph.forEachNode((key, attrs) => {
151
+ if (key.startsWith("def:")) ranked.push(toSymbol(attrs.def, Boolean(attrs.matchedSeed), scores[key] ?? 0));
152
+ });
153
+ return ranked.sort(compareSymbols);
154
+ }
155
+ function toSymbol(def, matchedSeed, pageRankScore) {
156
+ return {
157
+ kind: def.kind,
158
+ lineRange: [def.startLine, def.endLine],
159
+ matchedSeed,
160
+ name: def.name,
161
+ path: def.path,
162
+ score: pageRankScore + (matchedSeed ? SEED_BONUS : 0)
163
+ };
164
+ }
165
+ function compareSymbols(a, b) {
166
+ return b.score - a.score || a.path.localeCompare(b.path) || a.name.localeCompare(b.name) || a.lineRange[0] - b.lineRange[0];
167
+ }
168
+ function renderContext(selected) {
169
+ return ["Repo map context:", ...selected.map((s) => `## ${s.path}:${s.lineRange[0]}-${s.lineRange[1]}\n${s.kind} ${s.name}`)].join("\n");
170
+ }
171
+ function selectWithinBudget(ranked, budget, estimateTokens) {
172
+ let low = 0;
173
+ let high = ranked.length;
174
+ let best = 0;
175
+ while (low <= high) {
176
+ const mid = Math.floor((low + high) / 2);
177
+ if (estimateTokens(renderContext(ranked.slice(0, mid))) <= budget) {
178
+ best = mid;
179
+ low = mid + 1;
180
+ } else high = mid - 1;
181
+ }
182
+ const selected = ranked.slice(0, best);
183
+ const context = renderContext(selected);
184
+ return {
185
+ context,
186
+ estimatedTokens: estimateTokens(context),
187
+ selected
188
+ };
189
+ }
190
+ async function buildRepoMapContext(input) {
191
+ const estimateTokens$1 = input.estimateTokens ?? estimateTokens;
192
+ const ranked = rankDefinitions(buildGraph(await Promise.all(discoverFiles(input.worktreePath).map((file) => tagFile(input.worktreePath, file))), input));
193
+ const { context, estimatedTokens, selected } = selectWithinBudget(ranked, input.tokenBudget, estimateTokens$1);
194
+ return {
195
+ budget: input.tokenBudget,
196
+ context,
197
+ estimatedTokens,
198
+ selected,
199
+ totalRanked: ranked.length
200
+ };
201
+ }
202
+ //#endregion
203
+ export { buildRepoMapContext };
@@ -257,9 +257,18 @@ function namedOpencodePermissionMap(names) {
257
257
  function toolPermission(allowed, tool) {
258
258
  return allowed.has(tool) ? "allow" : "deny";
259
259
  }
260
+ /**
261
+ * PIPE-83.11: whether to synthesize the singleton pipeline gateway into this
262
+ * repo's `.opencode/opencode.json`. A "global"-scoped gateway is registered
263
+ * once in the global opencode config (via `moka gateway configure-host
264
+ * --scope global`) and inherited, so it is not embedded per project.
265
+ */
266
+ function shouldEmbedProjectGateway(config) {
267
+ return config.mcp_gateway !== void 0 && config.mcp_gateway.host_scope !== "global";
268
+ }
260
269
  function renderOpenCodeProjectConfig(config) {
261
270
  return formatOpenCodeProjectJson({
262
- ...config.mcp_gateway ? JSON.parse(renderOpenCodeGatewayConfig(config)) : { $schema: "https://opencode.ai/config.json" },
271
+ ...shouldEmbedProjectGateway(config) ? JSON.parse(renderOpenCodeGatewayConfig(config)) : { $schema: "https://opencode.ai/config.json" },
263
272
  lsp: true,
264
273
  ...opencodePluginConfig(),
265
274
  ...opencodeProviderConfig()
@@ -0,0 +1,15 @@
1
+ import { Data } from "effect";
2
+ //#region src/mcp/gateway-error.ts
3
+ /**
4
+ * Tagged error for the MCP (ToolHive) gateway subsystem. Lives in its own module
5
+ * so both the gateway facade (src/mcp/gateway.ts) and the Effect service
6
+ * (src/runtime/services/mcp-gateway-service.ts) can import it without forming a
7
+ * circular dependency between them.
8
+ */
9
+ var PipelineMcpGatewayError = class extends Data.TaggedError("PipelineMcpGatewayError") {
10
+ constructor(message) {
11
+ super({ message });
12
+ }
13
+ };
14
+ //#endregion
15
+ export { PipelineMcpGatewayError };