@femtomc/mu-orchestrator 26.2.31 → 26.2.33

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @femtomc/mu-orchestrator
2
2
 
3
- Node DAG runner that drives `@femtomc/mu-issue` and `@femtomc/mu-forum` to execute ready leaf issues and log outcomes.
3
+ Node DAG runner that drives `@femtomc/mu-issue` and `@femtomc/mu-forum` to execute ready leaf issues and log outcomes. Agent runtime primitives (role prompts, pi backends, prompt helpers) live in `@femtomc/mu-agent`.
4
4
 
5
5
  ## Install
6
6
 
@@ -50,5 +50,5 @@ bun run typecheck
50
50
 
51
51
  ## Runtime
52
52
 
53
- - **Node-only** (uses `node:child_process` + filesystem).
54
- - Default backend is the `pi` CLI (`pi --mode json ...`). If you don't have `pi`, pass a custom `BackendRunner`.
53
+ - **Node-only** (uses filesystem + pi SDK in-process).
54
+ - Default backend is `PiSdkBackend` (from `@femtomc/mu-agent`). Pass a custom `BackendRunner` to override.
@@ -2,7 +2,7 @@ import { type EventLog } from "@femtomc/mu-core/node";
2
2
  import type { ForumStore } from "@femtomc/mu-forum";
3
3
  import type { IssueStore } from "@femtomc/mu-issue";
4
4
  import type { ModelOverrides } from "./model_resolution.js";
5
- import type { BackendRunner } from "./pi_backend.js";
5
+ import { type BackendRunner } from "@femtomc/mu-agent";
6
6
  export type DagResult = {
7
7
  status: "root_final" | "no_executable_leaf" | "max_steps_exhausted" | "error";
8
8
  steps: number;
@@ -1 +1 @@
1
- {"version":3,"file":"dag_runner.d.ts","sourceRoot":"","sources":["../src/dag_runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAGjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGrD,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,qBAAqB,GAAG,OAAO,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAkBF,qBAAa,SAAS;;gBAYpB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAC,cAAc,CAAC,EAAE,cAAc,CAAA;KAAO;IAkLrF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC;CAiNjG"}
1
+ {"version":3,"file":"dag_runner.d.ts","sourceRoot":"","sources":["../src/dag_runner.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,KAAK,QAAQ,EAKb,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,uBAAuB,CAAC;AAEjF,OAAO,EAAE,KAAK,aAAa,EAAgE,MAAM,mBAAmB,CAAC;AAErH,MAAM,MAAM,SAAS,GAAG;IACvB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,qBAAqB,GAAG,OAAO,CAAC;IAC9E,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC5B,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,qBAAqB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,CAAC,EAAE,EAAE,yBAAyB,KAAK,IAAI,CAAC;CACxD,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC9B,KAAK,CAAC,EAAE,cAAc,CAAC;CACvB,CAAC;AAkBF,qBAAa,SAAS;;gBAYpB,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,OAAO,CAAC,EAAE,aAAa,CAAC;QAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;QAAC,cAAc,CAAC,EAAE,cAAc,CAAA;KAAO;IAkLrF,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAW,EAAE,IAAI,GAAE,gBAAqB,GAAG,OAAO,CAAC,SAAS,CAAC;CAiNjG"}
@@ -2,8 +2,7 @@ import { mkdir } from "node:fs/promises";
2
2
  import { join, relative } from "node:path";
3
3
  import { currentRunId, fsEventLogFromRepoRoot, getStorePaths, newRunId, runContext, } from "@femtomc/mu-core/node";
4
4
  import { resolveModelConfig } from "./model_resolution.js";
5
- import { roleFromTags, systemPromptForRole } from "./mu_roles.js";
6
- import { PiSdkBackend } from "./pi_sdk_backend.js";
5
+ import { PiSdkBackend, roleFromTags, systemPromptForRole } from "@femtomc/mu-agent";
7
6
  function roundTo(n, digits) {
8
7
  const f = 10 ** digits;
9
8
  return Math.round(n * f) / f;
package/dist/index.d.ts CHANGED
@@ -2,13 +2,6 @@ export type { DagResult, DagRunnerBackendLineEvent, DagRunnerHooks, DagRunnerRun
2
2
  export { DagRunner } from "./dag_runner.js";
3
3
  export type { ModelOverrides, ResolvedModelConfig } from "./model_resolution.js";
4
4
  export { resolveModelConfig } from "./model_resolution.js";
5
- export type { BackendRunner, BackendRunOpts } from "./pi_backend.js";
6
- export { PiCliBackend, piStreamHasError } from "./pi_backend.js";
7
- export { createMuResourceLoader, PiSdkBackend } from "./pi_sdk_backend.js";
8
5
  export type { PiStreamRendererOpts } from "./pi_stream_renderer.js";
9
6
  export { PiStreamRenderer } from "./pi_stream_renderer.js";
10
- export type { PromptMeta } from "./prompt.js";
11
- export { buildRoleCatalog, extractDescription, readPromptMeta, renderPromptTemplate, splitFrontmatter, } from "./prompt.js";
12
- export { DEFAULT_ORCHESTRATOR_PROMPT, DEFAULT_WORKER_PROMPT } from "./mu_roles.js";
13
- export declare function orchestratorHello(): string;
14
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,SAAS,EACT,yBAAyB,EACzB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC3E,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EACN,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,2BAA2B,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGnF,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACX,SAAS,EACT,yBAAyB,EACzB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -1,11 +1,3 @@
1
1
  export { DagRunner } from "./dag_runner.js";
2
2
  export { resolveModelConfig } from "./model_resolution.js";
3
- export { PiCliBackend, piStreamHasError } from "./pi_backend.js";
4
- export { createMuResourceLoader, PiSdkBackend } from "./pi_sdk_backend.js";
5
3
  export { PiStreamRenderer } from "./pi_stream_renderer.js";
6
- export { buildRoleCatalog, extractDescription, readPromptMeta, renderPromptTemplate, splitFrontmatter, } from "./prompt.js";
7
- export { DEFAULT_ORCHESTRATOR_PROMPT, DEFAULT_WORKER_PROMPT } from "./mu_roles.js";
8
- // Back-compat placeholder API used by other packages/tests.
9
- export function orchestratorHello() {
10
- return "orchestrator(forum,issue)";
11
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@femtomc/mu-orchestrator",
3
- "version": "26.2.31",
3
+ "version": "26.2.33",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -14,10 +14,10 @@
14
14
  "dist/**"
15
15
  ],
16
16
  "dependencies": {
17
- "@femtomc/mu-core": "26.2.31",
18
- "@femtomc/mu-forum": "26.2.31",
19
- "@femtomc/mu-issue": "26.2.31",
20
- "@mariozechner/pi-agent-core": "^0.52.12",
17
+ "@femtomc/mu-agent": "26.2.33",
18
+ "@femtomc/mu-core": "26.2.33",
19
+ "@femtomc/mu-forum": "26.2.33",
20
+ "@femtomc/mu-issue": "26.2.33",
21
21
  "@mariozechner/pi-coding-agent": "^0.52.12",
22
22
  "@mariozechner/pi-ai": "^0.52.12"
23
23
  }
@@ -1,15 +0,0 @@
1
- export type MuRole = "orchestrator" | "worker";
2
- /** Determine role from tags. Defaults to orchestrator if no role tag present. */
3
- export declare function roleFromTags(tags: readonly string[]): MuRole;
4
- export declare const DEFAULT_ORCHESTRATOR_PROMPT: string;
5
- export declare const DEFAULT_WORKER_PROMPT: string;
6
- /**
7
- * Load the system prompt for a role.
8
- *
9
- * When `repoRoot` is provided, tries `.mu/roles/${role}.md` first.
10
- * The file body IS the entire system prompt — no auto-appending.
11
- * Frontmatter is stripped via `splitFrontmatter`.
12
- * Falls back to the hardcoded default on any error.
13
- */
14
- export declare function systemPromptForRole(role: MuRole, repoRoot?: string): Promise<string>;
15
- //# sourceMappingURL=mu_roles.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mu_roles.d.ts","sourceRoot":"","sources":["../src/mu_roles.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAE/C,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAM5D;AAgED,eAAO,MAAM,2BAA2B,QAwC5B,CAAC;AAEb,eAAO,MAAM,qBAAqB,QA0BtB,CAAC;AAMb;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAY1F"}
package/dist/mu_roles.js DELETED
@@ -1,165 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
3
- import { splitFrontmatter } from "./prompt.js";
4
- /** Determine role from tags. Defaults to orchestrator if no role tag present. */
5
- export function roleFromTags(tags) {
6
- for (const tag of tags) {
7
- if (tag === "role:worker")
8
- return "worker";
9
- if (tag === "role:orchestrator")
10
- return "orchestrator";
11
- }
12
- return "orchestrator";
13
- }
14
- /* ------------------------------------------------------------------ */
15
- /* mu CLI reference */
16
- /* ------------------------------------------------------------------ */
17
- const MU_CLI_REFERENCE = `
18
- ## mu CLI
19
-
20
- You are running inside **mu**, an issue-driven orchestration system.
21
- You have four tools: bash, read, write, edit.
22
-
23
- - Orchestrator: use bash to run \`mu\` commands; do NOT use write/edit (and avoid read).
24
- - Worker: use tools as needed to implement your assigned issue.
25
-
26
- Tip: run \`mu <command> --help\` for details.
27
-
28
- ### Issues
29
-
30
- \`\`\`bash
31
- # Create a child issue (always set --parent and --role)
32
- mu issues create "<title>" --parent <parent-id> --role worker [--body "<text>"] [--priority N] [--tag TAG]
33
-
34
- # Inspect
35
- mu issues get <id> # full issue detail
36
- mu issues list --root <root-id> [--status open|in_progress|closed]
37
- mu issues children <id> # direct children
38
- mu issues ready --root <root-id> # executable leaves
39
-
40
- # Status transitions
41
- mu issues claim <id> # open → in_progress
42
- mu issues close <id> --outcome <outcome> # close with outcome
43
-
44
- # Dependencies
45
- mu issues dep <src> blocks <dst> # src must close before dst starts
46
- mu issues dep <child> parent <parent> # set parent-child edge
47
- mu issues undep <src> blocks <dst> # remove blocking edge
48
-
49
- # Update fields
50
- mu issues update <id> [--title "..."] [--body "..."] [--role worker|orchestrator] [--priority N] [--add-tag TAG]
51
- \`\`\`
52
-
53
- ### Outcomes
54
-
55
- | Outcome | Meaning |
56
- |--------------|-----------------------------------------------------|
57
- | \`success\` | Work completed successfully (terminal) |
58
- | \`failure\` | Work failed — triggers re-orchestration |
59
- | \`needs_work\` | Partial — triggers re-orchestration |
60
- | \`expanded\` | Decomposed into children (orchestrator closes self) |
61
- | \`skipped\` | Not applicable (terminal) |
62
-
63
- ### Forum (logging & coordination)
64
-
65
- \`\`\`bash
66
- mu forum post issue:<id> -m "<message>" --author <role>
67
- mu forum read issue:<id> [--limit N]
68
- \`\`\`
69
- `.trim();
70
- /* ------------------------------------------------------------------ */
71
- /* Default role prompts (exported for mu init + tests) */
72
- /* ------------------------------------------------------------------ */
73
- export const DEFAULT_ORCHESTRATOR_PROMPT = [
74
- "# Mu Orchestrator",
75
- "",
76
- "You are mu's orchestrator: the hierarchical planner for the issue DAG.",
77
- "",
78
- "## Non-Negotiable Constraints",
79
- "",
80
- "1. You MUST NOT execute work directly. No code changes, no file edits, no git commits.",
81
- "2. You MUST decompose the assigned issue into worker child issues, then close the assigned issue with `--outcome expanded`.",
82
- "3. Decomposition MUST be deterministic and minimal. Use `blocks` edges for sequencing.",
83
- "",
84
- "Even if the task looks atomic: create exactly one worker child issue rather than doing the work yourself.",
85
- "If you catch yourself about to implement: STOP and create/refine worker issues instead.",
86
- "",
87
- "Your only job is to create child issues, add any required `blocks` dependencies, and then close yourself with outcome=expanded.",
88
- "",
89
- "## Workflow",
90
- "",
91
- "1. Investigate: `mu issues get <id>`, `mu forum read issue:<id> --limit 20`, `mu issues children <id>`.",
92
- "2. Decompose: create child issues with `mu issues create` (always set `--parent` and `--role worker`).",
93
- "3. Order: add `blocks` edges between children where sequencing matters.",
94
- "4. Close: `mu issues close <id> --outcome expanded`.",
95
- "",
96
- "The ONLY valid outcome for you is `expanded`.",
97
- "Never close with `success`, `failure`, `needs_work`, or `skipped` — those are for workers.",
98
- "",
99
- "## Rules",
100
- "",
101
- "- Use only roles: orchestrator, worker.",
102
- "- Every executable leaf MUST be `--role worker`.",
103
- "- Never create a child without an explicit role.",
104
- "",
105
- "## Strategies For Good Plans",
106
- "",
107
- "- Include feedback loops in worker issues: tests, typecheck, build, lint, repro steps.",
108
- "- Prefer small issues with crisp acceptance criteria over large ambiguous ones.",
109
- "- If the work needs verification, add a worker review issue blocked by implementation.",
110
- " If review fails, that worker should close with outcome=needs_work and describe what failed.",
111
- "",
112
- MU_CLI_REFERENCE,
113
- ].join("\n");
114
- export const DEFAULT_WORKER_PROMPT = [
115
- "# Mu Worker",
116
- "",
117
- "You are mu's worker. You execute exactly one atomic issue end-to-end.",
118
- "",
119
- "## Responsibilities",
120
- "",
121
- "- Implement the work described in your assigned issue.",
122
- "- Keep scope tight to the issue specification.",
123
- "- Verify results (tests, typecheck, build, lint, etc.) and report what changed.",
124
- "- Close your issue with a terminal outcome when done.",
125
- "",
126
- "## Workflow",
127
- "",
128
- "1. Inspect: `mu issues get <id>` and `mu forum read issue:<id> --limit 20`.",
129
- "2. Implement: edit files, run commands, and keep changes scoped to the issue.",
130
- "3. Verify: run tests/build/typecheck/lint as appropriate. Prefer hard feedback loops.",
131
- "4. Close: `mu issues close <id> --outcome success` (or `failure`/`skipped`).",
132
- "5. Log key notes: `mu forum post issue:<id> -m '...' --author worker`.",
133
- "",
134
- "## Rules",
135
- "",
136
- "- Do NOT create child issues — that is the orchestrator's job.",
137
- "- If the issue is too large/unclear, close with `--outcome needs_work` and explain what is missing.",
138
- "",
139
- MU_CLI_REFERENCE,
140
- ].join("\n");
141
- /* ------------------------------------------------------------------ */
142
- /* Role-specific system prompts */
143
- /* ------------------------------------------------------------------ */
144
- /**
145
- * Load the system prompt for a role.
146
- *
147
- * When `repoRoot` is provided, tries `.mu/roles/${role}.md` first.
148
- * The file body IS the entire system prompt — no auto-appending.
149
- * Frontmatter is stripped via `splitFrontmatter`.
150
- * Falls back to the hardcoded default on any error.
151
- */
152
- export async function systemPromptForRole(role, repoRoot) {
153
- if (repoRoot) {
154
- try {
155
- const filePath = join(repoRoot, ".mu", "roles", `${role}.md`);
156
- const raw = await readFile(filePath, "utf8");
157
- const { body } = splitFrontmatter(raw);
158
- return body;
159
- }
160
- catch {
161
- // File missing or unreadable — fall through to default.
162
- }
163
- }
164
- return role === "orchestrator" ? DEFAULT_ORCHESTRATOR_PROMPT : DEFAULT_WORKER_PROMPT;
165
- }
@@ -1,26 +0,0 @@
1
- import type { MuRole } from "./mu_roles.js";
2
- export type BackendRunOpts = {
3
- issueId: string;
4
- role: MuRole;
5
- systemPrompt: string;
6
- prompt: string;
7
- provider: string;
8
- model: string;
9
- thinking: string;
10
- cwd: string;
11
- cli: string;
12
- logSuffix: string;
13
- onLine?: (line: string) => void;
14
- teePath?: string;
15
- };
16
- export interface BackendRunner {
17
- run(opts: BackendRunOpts): Promise<number>;
18
- }
19
- export type PiCliArgvOpts = Pick<BackendRunOpts, "prompt" | "systemPrompt" | "provider" | "model" | "thinking">;
20
- /** Build argv for the `pi` CLI. Exported for regression testing. */
21
- export declare function buildPiCliArgv(opts: PiCliArgvOpts): string[];
22
- export declare function piStreamHasError(line: string): boolean;
23
- export declare class PiCliBackend implements BackendRunner {
24
- run(opts: BackendRunOpts): Promise<number>;
25
- }
26
- //# sourceMappingURL=pi_backend.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pi_backend.d.ts","sourceRoot":"","sources":["../src/pi_backend.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,WAAW,aAAa;IAC7B,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC3C;AAED,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,cAAc,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,CAAC,CAAC;AAEhH,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,EAAE,CAgB5D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CA4BtD;AAED,qBAAa,YAAa,YAAW,aAAa;IACpC,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAuEvD"}
@@ -1,116 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import { mkdir, open } from "node:fs/promises";
3
- import { dirname } from "node:path";
4
- import { createInterface } from "node:readline";
5
- import { PassThrough } from "node:stream";
6
- /** Build argv for the `pi` CLI. Exported for regression testing. */
7
- export function buildPiCliArgv(opts) {
8
- return [
9
- "pi",
10
- "--mode",
11
- "json",
12
- "--no-session",
13
- "--provider",
14
- opts.provider,
15
- "--model",
16
- opts.model,
17
- "--thinking",
18
- opts.thinking,
19
- "--system-prompt",
20
- opts.systemPrompt,
21
- opts.prompt,
22
- ];
23
- }
24
- export function piStreamHasError(line) {
25
- let event;
26
- try {
27
- event = JSON.parse(line);
28
- }
29
- catch {
30
- return false;
31
- }
32
- const etype = event?.type;
33
- if (etype === "message_update") {
34
- const assistantEvent = event?.assistantMessageEvent;
35
- if (assistantEvent && typeof assistantEvent === "object" && assistantEvent.type === "error") {
36
- return true;
37
- }
38
- }
39
- if (etype === "message_end") {
40
- const message = event?.message;
41
- if (!message || typeof message !== "object") {
42
- return false;
43
- }
44
- if (message.role !== "assistant") {
45
- return false;
46
- }
47
- return message.stopReason === "error" || message.stopReason === "aborted";
48
- }
49
- return false;
50
- }
51
- export class PiCliBackend {
52
- async run(opts) {
53
- if (opts.cli !== "pi") {
54
- throw new Error(`unsupported backend cli=${JSON.stringify(opts.cli)} (only "pi" is supported)`);
55
- }
56
- const argv = buildPiCliArgv(opts);
57
- let teeFh = null;
58
- try {
59
- if (opts.teePath) {
60
- await mkdir(dirname(opts.teePath), { recursive: true });
61
- teeFh = await open(opts.teePath, "w");
62
- }
63
- const proc = spawn(argv[0], argv.slice(1), {
64
- cwd: opts.cwd,
65
- stdio: ["ignore", "pipe", "pipe"],
66
- });
67
- const merged = new PassThrough();
68
- proc.stdout?.pipe(merged);
69
- proc.stderr?.pipe(merged);
70
- let sawAssistantError = false;
71
- const DELTA_TYPES = /^(?:thinking_delta|toolcall_delta|text_delta)$/;
72
- const rl = createInterface({ input: merged, crlfDelay: Number.POSITIVE_INFINITY });
73
- const readLoop = (async () => {
74
- for await (const line of rl) {
75
- const trimmed = String(line);
76
- if (piStreamHasError(trimmed)) {
77
- sawAssistantError = true;
78
- }
79
- opts.onLine?.(trimmed);
80
- if (teeFh) {
81
- // Skip streaming deltas from the log — they carry the full
82
- // accumulated message state on every token, causing quadratic
83
- // log growth. Structural events are preserved.
84
- let skip = false;
85
- try {
86
- const parsed = JSON.parse(trimmed);
87
- const aType = parsed?.assistantMessageEvent?.type;
88
- if (typeof aType === "string" && DELTA_TYPES.test(aType)) {
89
- skip = true;
90
- }
91
- }
92
- catch { }
93
- if (!skip) {
94
- await teeFh.write(`${trimmed}\n`);
95
- }
96
- }
97
- }
98
- })();
99
- const exitCode = await new Promise((resolve, reject) => {
100
- proc.once("error", (err) => reject(err));
101
- proc.once("close", (code) => resolve(code ?? 0));
102
- });
103
- // Ensure the reader finishes (and flushes tee writes).
104
- await readLoop;
105
- if (exitCode === 0 && sawAssistantError) {
106
- return 1;
107
- }
108
- return exitCode;
109
- }
110
- finally {
111
- if (teeFh) {
112
- await teeFh.close();
113
- }
114
- }
115
- }
116
- }
@@ -1,20 +0,0 @@
1
- import { DefaultResourceLoader, SettingsManager } from "@mariozechner/pi-coding-agent";
2
- import type { BackendRunner, BackendRunOpts } from "./pi_backend.js";
3
- /**
4
- * In-process backend using the pi SDK.
5
- *
6
- * Replaces subprocess spawning of the `pi` CLI with direct use of
7
- * `createAgentSession` from `@mariozechner/pi-coding-agent`.
8
- */
9
- export declare class PiSdkBackend implements BackendRunner {
10
- run(opts: BackendRunOpts): Promise<number>;
11
- }
12
- export type CreateMuResourceLoaderOpts = {
13
- cwd: string;
14
- systemPrompt: string;
15
- agentDir?: string;
16
- settingsManager?: SettingsManager;
17
- additionalSkillPaths?: string[];
18
- };
19
- export declare function createMuResourceLoader(opts: CreateMuResourceLoaderOpts): DefaultResourceLoader;
20
- //# sourceMappingURL=pi_sdk_backend.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pi_sdk_backend.d.ts","sourceRoot":"","sources":["../src/pi_sdk_backend.ts"],"names":[],"mappings":"AAMA,OAAO,EAQN,qBAAqB,EAErB,eAAe,EACf,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAwCrE;;;;;GAKG;AACH,qBAAa,YAAa,YAAW,aAAa;IAC3C,GAAG,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;CAiGhD;AAED,MAAM,MAAM,0BAA0B,GAAG;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC,CAAC;AAEF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,0BAA0B,GAAG,qBAAqB,CAsB9F"}
@@ -1,152 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { mkdir, open } from "node:fs/promises";
3
- import { basename, dirname, join } from "node:path";
4
- import { getModels, getProviders } from "@mariozechner/pi-ai";
5
- import { AuthStorage, createAgentSession, createBashTool, createEditTool, createReadTool, createWriteTool, DefaultResourceLoader, SessionManager, SettingsManager, } from "@mariozechner/pi-coding-agent";
6
- import { piStreamHasError } from "./pi_backend.js";
7
- /**
8
- * Resolve a bare model ID (e.g. "gpt-5.3-codex") to a pi-ai Model object.
9
- *
10
- * When multiple providers offer the same model ID, prefer providers that
11
- * have auth configured (env var, OAuth, or stored API key).
12
- */
13
- function resolveModel(modelId, authStorage, providerConstraint) {
14
- if (providerConstraint) {
15
- const providers = getProviders();
16
- if (!providers.includes(providerConstraint)) {
17
- throw new Error(`Unknown provider "${providerConstraint}". Available: ${providers.join(", ")}`);
18
- }
19
- const models = getModels(providerConstraint);
20
- return models.find((m) => m.id === modelId);
21
- }
22
- let fallback;
23
- for (const provider of getProviders()) {
24
- const models = getModels(provider);
25
- const match = models.find((m) => m.id === modelId);
26
- if (!match)
27
- continue;
28
- // Prefer providers that have auth configured.
29
- if (authStorage.hasAuth(provider)) {
30
- return match;
31
- }
32
- // Keep first match as fallback.
33
- if (!fallback) {
34
- fallback = match;
35
- }
36
- }
37
- return fallback;
38
- }
39
- /**
40
- * In-process backend using the pi SDK.
41
- *
42
- * Replaces subprocess spawning of the `pi` CLI with direct use of
43
- * `createAgentSession` from `@mariozechner/pi-coding-agent`.
44
- */
45
- export class PiSdkBackend {
46
- async run(opts) {
47
- const authStorage = new AuthStorage();
48
- const model = resolveModel(opts.model, authStorage, opts.provider);
49
- if (!model) {
50
- const scope = opts.provider ? ` in provider "${opts.provider}"` : "";
51
- throw new Error(`Model "${opts.model}" not found${scope} in pi-ai registry.`);
52
- }
53
- const settingsManager = SettingsManager.inMemory();
54
- const resourceLoader = createMuResourceLoader({
55
- cwd: opts.cwd,
56
- systemPrompt: opts.systemPrompt,
57
- settingsManager,
58
- });
59
- await resourceLoader.reload();
60
- const tools = [
61
- createBashTool(opts.cwd),
62
- createReadTool(opts.cwd),
63
- createWriteTool(opts.cwd),
64
- createEditTool(opts.cwd),
65
- ];
66
- const sessionOpts = {
67
- cwd: opts.cwd,
68
- model,
69
- thinkingLevel: opts.thinking,
70
- tools,
71
- sessionManager: SessionManager.inMemory(opts.cwd),
72
- settingsManager,
73
- resourceLoader,
74
- authStorage,
75
- };
76
- const { session } = await createAgentSession(sessionOpts);
77
- let teeFh = null;
78
- try {
79
- if (opts.teePath) {
80
- await mkdir(dirname(opts.teePath), { recursive: true });
81
- teeFh = await open(opts.teePath, "w");
82
- }
83
- // Bind extensions (required for tools to work in print mode).
84
- await session.bindExtensions({
85
- commandContextActions: {
86
- waitForIdle: () => session.agent.waitForIdle(),
87
- newSession: async () => ({ cancelled: true }),
88
- fork: async () => ({ cancelled: true }),
89
- navigateTree: async () => ({ cancelled: true }),
90
- switchSession: async () => ({ cancelled: true }),
91
- reload: async () => { },
92
- },
93
- onError: () => { },
94
- });
95
- let sawError = false;
96
- const DELTA_TYPES = new Set(["thinking_delta", "toolcall_delta", "text_delta"]);
97
- // Subscribe to events — serialize to JSONL for tee and error detection.
98
- const unsub = session.subscribe((event) => {
99
- const line = JSON.stringify(event);
100
- if (piStreamHasError(line)) {
101
- sawError = true;
102
- }
103
- // onLine gets everything (CLI needs deltas for live rendering).
104
- opts.onLine?.(line);
105
- // Tee file: skip streaming deltas (they carry the full accumulated
106
- // message state on every token, causing quadratic log growth).
107
- // Structural events (message_start/end, turn_start/end, tool_execution_*,
108
- // thinking_start/end, toolcall_start/end) are kept.
109
- if (teeFh) {
110
- const aType = event?.assistantMessageEvent?.type;
111
- if (!DELTA_TYPES.has(aType)) {
112
- teeFh.write(`${line}\n`).catch(() => { });
113
- }
114
- }
115
- });
116
- try {
117
- await session.prompt(opts.prompt, { expandPromptTemplates: false });
118
- }
119
- finally {
120
- unsub();
121
- }
122
- return sawError ? 1 : 0;
123
- }
124
- finally {
125
- session.dispose();
126
- if (teeFh) {
127
- await teeFh.close();
128
- }
129
- }
130
- }
131
- }
132
- export function createMuResourceLoader(opts) {
133
- const skillPaths = new Set();
134
- for (const p of opts.additionalSkillPaths ?? []) {
135
- skillPaths.add(p);
136
- }
137
- // If a repo has a top-level `skills/` dir (like workshop/), load it.
138
- const repoSkills = join(opts.cwd, "skills");
139
- if (existsSync(repoSkills)) {
140
- skillPaths.add(repoSkills);
141
- }
142
- return new DefaultResourceLoader({
143
- cwd: opts.cwd,
144
- agentDir: opts.agentDir,
145
- settingsManager: opts.settingsManager ?? SettingsManager.inMemory(),
146
- additionalSkillPaths: [...skillPaths],
147
- systemPromptOverride: (_base) => opts.systemPrompt,
148
- agentsFilesOverride: (base) => ({
149
- agentsFiles: base.agentsFiles.filter((f) => basename(f.path) === "AGENTS.md"),
150
- }),
151
- });
152
- }
package/dist/prompt.d.ts DELETED
@@ -1,17 +0,0 @@
1
- import type { Issue } from "@femtomc/mu-core";
2
- export type PromptMeta = Record<string, unknown>;
3
- export declare function splitFrontmatter(text: string): {
4
- meta: PromptMeta;
5
- body: string;
6
- };
7
- export declare function extractDescription(meta: PromptMeta, body: string): {
8
- description: string;
9
- source: string;
10
- };
11
- export declare function readPromptMeta(path: string): Promise<PromptMeta>;
12
- export declare function buildRoleCatalog(repoRoot: string): Promise<string>;
13
- export declare function renderPromptTemplate(path: string, issue: Pick<Issue, "id" | "title" | "body">, opts?: {
14
- repoRoot?: string;
15
- }): Promise<string>;
16
- export declare function resolvePromptPath(repoRoot: string, promptPath: string): string;
17
- //# sourceMappingURL=prompt.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAyCjD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA6BjF;AAYD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAW1G;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAItE;AAMD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwCxE;AAED,wBAAsB,oBAAoB,CACzC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,GAAG,OAAO,GAAG,MAAM,CAAC,EAC3C,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAK9E"}
package/dist/prompt.js DELETED
@@ -1,153 +0,0 @@
1
- import { readdir, readFile } from "node:fs/promises";
2
- import { isAbsolute, join, relative } from "node:path";
3
- function stripQuotes(s) {
4
- const trimmed = s.trim();
5
- if (trimmed.length >= 2) {
6
- const first = trimmed[0];
7
- const last = trimmed[trimmed.length - 1];
8
- if ((first === `"` && last === `"`) || (first === `'` && last === `'`)) {
9
- return trimmed.slice(1, -1);
10
- }
11
- }
12
- return trimmed;
13
- }
14
- function parseSimpleYamlFrontmatter(text) {
15
- // We only need a small subset: flat `key: value` mappings.
16
- // If parsing fails, we return {} (mirrors Python behavior).
17
- const out = {};
18
- const lines = text.split(/\r?\n/);
19
- for (const rawLine of lines) {
20
- const line = rawLine.trim();
21
- if (line.length === 0) {
22
- continue;
23
- }
24
- if (line.startsWith("#")) {
25
- continue;
26
- }
27
- const idx = line.indexOf(":");
28
- if (idx <= 0) {
29
- continue;
30
- }
31
- const key = line.slice(0, idx).trim();
32
- if (!key) {
33
- continue;
34
- }
35
- const value = stripQuotes(line.slice(idx + 1));
36
- out[key] = value;
37
- }
38
- return out;
39
- }
40
- export function splitFrontmatter(text) {
41
- const lines = text.split(/\r?\n/);
42
- if (lines.length === 0 || lines[0]?.trim() !== "---") {
43
- return { meta: {}, body: text };
44
- }
45
- // Find the terminating `---` line.
46
- let endIdx = -1;
47
- for (let i = 1; i < lines.length; i++) {
48
- if (lines[i]?.trim() === "---") {
49
- endIdx = i;
50
- break;
51
- }
52
- }
53
- if (endIdx < 0) {
54
- return { meta: {}, body: text };
55
- }
56
- try {
57
- const metaText = lines.slice(1, endIdx).join("\n");
58
- const body = lines
59
- .slice(endIdx + 1)
60
- .join("\n")
61
- .replace(/^\n+/, "");
62
- const meta = parseSimpleYamlFrontmatter(metaText);
63
- return { meta, body };
64
- }
65
- catch {
66
- return { meta: {}, body: text };
67
- }
68
- }
69
- function firstNonEmptyLine(text) {
70
- for (const line of text.split(/\r?\n/)) {
71
- const stripped = line.trim();
72
- if (stripped) {
73
- return stripped;
74
- }
75
- }
76
- return "";
77
- }
78
- export function extractDescription(meta, body) {
79
- const raw = meta.description;
80
- const desc = typeof raw === "string" ? raw.trim() : "";
81
- if (desc) {
82
- return { description: desc, source: "frontmatter" };
83
- }
84
- const bodyDesc = firstNonEmptyLine(body);
85
- if (bodyDesc) {
86
- return { description: bodyDesc, source: "body" };
87
- }
88
- return { description: "", source: "none" };
89
- }
90
- export async function readPromptMeta(path) {
91
- const text = await readFile(path, "utf8");
92
- const { meta } = splitFrontmatter(text);
93
- return meta;
94
- }
95
- function toPosixPath(path) {
96
- return path.replaceAll("\\", "/");
97
- }
98
- export async function buildRoleCatalog(repoRoot) {
99
- const rolesDir = join(repoRoot, ".mu", "roles");
100
- let entries;
101
- try {
102
- entries = await readdir(rolesDir);
103
- }
104
- catch {
105
- return "";
106
- }
107
- const roleFiles = entries.filter((e) => e.endsWith(".md")).sort();
108
- const sections = [];
109
- for (const file of roleFiles) {
110
- const abs = join(rolesDir, file);
111
- const text = await readFile(abs, "utf8");
112
- const { meta, body } = splitFrontmatter(text);
113
- const name = file.replace(/\.md$/, "");
114
- const promptPath = toPosixPath(relative(repoRoot, abs));
115
- const { description, source } = extractDescription(meta, body);
116
- const parts = [];
117
- for (const key of ["cli", "model", "reasoning"]) {
118
- if (key in meta) {
119
- parts.push(`${key}: ${String(meta[key])}`);
120
- }
121
- }
122
- const configLine = parts.length > 0 ? parts.join(" | ") : "default config";
123
- const catalogDesc = description || "No description provided.";
124
- sections.push(`### ${name}\n` +
125
- `description: ${catalogDesc}\n` +
126
- `description_source: ${source}\n` +
127
- `prompt: ${promptPath}\n` +
128
- `config: ${configLine}`);
129
- }
130
- return sections.join("\n\n");
131
- }
132
- export async function renderPromptTemplate(path, issue, opts = {}) {
133
- const text = await readFile(path, "utf8");
134
- const { body } = splitFrontmatter(text);
135
- let promptText = issue.title ?? "";
136
- if (issue.body) {
137
- promptText += `\n\n${issue.body}`;
138
- }
139
- let rendered = body;
140
- rendered = rendered.replaceAll("{{PROMPT}}", promptText);
141
- rendered = rendered.replaceAll("{{ISSUE_ID}}", issue.id ?? "");
142
- if (rendered.includes("{{ROLES}}")) {
143
- const catalog = opts.repoRoot ? await buildRoleCatalog(opts.repoRoot) : "";
144
- rendered = rendered.replaceAll("{{ROLES}}", catalog);
145
- }
146
- return rendered;
147
- }
148
- export function resolvePromptPath(repoRoot, promptPath) {
149
- if (isAbsolute(promptPath)) {
150
- return promptPath;
151
- }
152
- return join(repoRoot, promptPath);
153
- }