@llblab/pi-actors 0.45.1 → 0.46.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.
Files changed (212) hide show
  1. package/AGENTS.md +9 -7
  2. package/CHANGELOG.md +8 -1
  3. package/README.md +26 -12
  4. package/dist/fixtures/protocol/recipe-summary.json +1 -1
  5. package/dist/index.js +1 -1
  6. package/dist/lib/async-runs.d.ts +4 -1
  7. package/dist/lib/async-runs.js +22 -23
  8. package/dist/lib/automatic-review-runtime.d.ts +2 -0
  9. package/dist/lib/automatic-review-runtime.js +12 -5
  10. package/dist/lib/draft-sleep.js +0 -1
  11. package/dist/lib/extension-runtime.d.ts +1 -1
  12. package/dist/lib/extension-runtime.js +8 -2
  13. package/dist/lib/inspector.js +45 -7
  14. package/dist/lib/paths.d.ts +0 -1
  15. package/dist/lib/paths.js +0 -7
  16. package/dist/lib/prompts.d.ts +1 -1
  17. package/dist/lib/prompts.js +2 -2
  18. package/dist/lib/recipes-context.js +2 -2
  19. package/dist/lib/recipes-references.d.ts +24 -9
  20. package/dist/lib/recipes-references.js +152 -126
  21. package/dist/lib/runtime.d.ts +0 -1
  22. package/dist/lib/runtime.js +1 -3
  23. package/dist/lib/tools-inspect.d.ts +0 -1
  24. package/dist/lib/tools-inspect.js +35 -8
  25. package/dist/lib/tools-local.d.ts +2 -0
  26. package/dist/lib/tools-local.js +1 -1
  27. package/dist/lib/tools-spawn.d.ts +2 -0
  28. package/dist/lib/tools-spawn.js +24 -52
  29. package/dist/scripts/build-dist.mjs +13 -4
  30. package/dist/skills/actors/SKILL.md +6 -6
  31. package/dist/skills/actors/recipes/recipe-validate.json +11 -0
  32. package/{recipes/utility-coordinator-lock-snapshot.json → dist/skills/actors/recipes/resource-locker-snapshot.json} +1 -3
  33. package/dist/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
  34. package/dist/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
  35. package/dist/skills/actors/recipes/run-summary.json +9 -0
  36. package/dist/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -2
  37. package/dist/skills/actors/scripts/run-utils.mjs +164 -0
  38. package/{scripts → dist/skills/actors/scripts}/validate-recipe.mjs +134 -17
  39. package/dist/skills/artifacts/SKILL.md +16 -0
  40. package/{recipes/pipeline-artifact-bundle.json → dist/skills/artifacts/recipes/bundle.json} +4 -4
  41. package/dist/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
  42. package/dist/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
  43. package/{recipes/pipeline-artifact-report.json → dist/skills/artifacts/recipes/report.json} +2 -2
  44. package/dist/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
  45. package/dist/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  46. package/dist/skills/media/SKILL.md +16 -0
  47. package/dist/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
  48. package/{recipes/music-player.json → dist/skills/media/recipes/player.json} +1 -3
  49. package/dist/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
  50. package/dist/skills/media/scripts/media-utils.mjs +47 -0
  51. package/dist/{scripts → skills/media/scripts}/music-player.mjs +9 -3
  52. package/dist/skills/project-work/SKILL.md +16 -0
  53. package/dist/skills/project-work/recipes/changelog-section.json +11 -0
  54. package/{recipes/pipeline-docs-maintenance.json → dist/skills/project-work/recipes/docs-maintenance.json} +4 -4
  55. package/dist/skills/project-work/recipes/package-summary.json +9 -0
  56. package/{recipes/pipeline-release-readiness.json → dist/skills/project-work/recipes/release-readiness.json} +6 -6
  57. package/dist/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
  58. package/{recipes/pipeline-repo-health.json → dist/skills/project-work/recipes/repo-health.json} +6 -6
  59. package/{recipes/pipeline-async-run-ops.json → dist/skills/project-work/recipes/run-ops.json} +3 -3
  60. package/dist/skills/project-work/recipes/skill-summary.json +11 -0
  61. package/dist/skills/project-work/scripts/project-utils.mjs +89 -0
  62. package/dist/skills/recipe-memory/SKILL.md +16 -0
  63. package/dist/skills/swarm/SKILL.md +4 -4
  64. package/{recipes/pipeline-architect-coordinator.json → dist/skills/swarm/recipes/architect.json} +5 -5
  65. package/{recipes/pipeline-checkpoint-continuation.json → dist/skills/swarm/recipes/checkpoint-continuation.json} +3 -3
  66. package/dist/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
  67. package/dist/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
  68. package/dist/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
  69. package/{recipes/pipeline-research-synthesis.json → dist/skills/swarm/recipes/research-synthesis.json} +6 -6
  70. package/{recipes/pipeline-review-readiness.json → dist/skills/swarm/recipes/review-readiness.json} +1 -1
  71. package/{recipes/subagents-prompts.json → dist/skills/swarm/recipes/subagent-prompts.json} +1 -1
  72. package/{recipes → dist/skills/swarm/recipes}/subagent-quorum.json +1 -1
  73. package/dist/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
  74. package/docs/actor-inspector.md +4 -3
  75. package/docs/async-runs.md +2 -2
  76. package/docs/recipe-library.md +23 -23
  77. package/docs/releasing.md +2 -2
  78. package/docs/template-recipes.md +30 -14
  79. package/docs/tool-registry.md +2 -2
  80. package/fixtures/protocol/recipe-summary.json +1 -1
  81. package/index.ts +2 -1
  82. package/lib/async-runs.ts +47 -24
  83. package/lib/automatic-review-runtime.ts +16 -3
  84. package/lib/draft-sleep.ts +0 -1
  85. package/lib/extension-runtime.ts +21 -2
  86. package/lib/inspector.ts +56 -11
  87. package/lib/paths.ts +0 -7
  88. package/lib/prompts.ts +2 -2
  89. package/lib/recipes-context.ts +2 -2
  90. package/lib/recipes-references.ts +223 -134
  91. package/lib/runtime.ts +1 -5
  92. package/lib/tools-inspect.ts +42 -9
  93. package/lib/tools-local.ts +2 -0
  94. package/lib/tools-spawn.ts +37 -74
  95. package/package.json +2 -4
  96. package/scripts/build-dist.mjs +13 -4
  97. package/skills/actors/SKILL.md +6 -6
  98. package/skills/actors/recipes/recipe-validate.json +11 -0
  99. package/{dist/recipes/utility-coordinator-lock-snapshot.json → skills/actors/recipes/resource-locker-snapshot.json} +1 -3
  100. package/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
  101. package/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
  102. package/skills/actors/recipes/run-summary.json +9 -0
  103. package/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -2
  104. package/skills/actors/scripts/run-utils.mjs +164 -0
  105. package/{dist → skills/actors}/scripts/validate-recipe.mjs +134 -17
  106. package/skills/artifacts/SKILL.md +16 -0
  107. package/{dist/recipes/pipeline-artifact-bundle.json → skills/artifacts/recipes/bundle.json} +4 -4
  108. package/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
  109. package/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
  110. package/{dist/recipes/pipeline-artifact-report.json → skills/artifacts/recipes/report.json} +2 -2
  111. package/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
  112. package/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  113. package/skills/media/SKILL.md +16 -0
  114. package/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
  115. package/{dist/recipes/music-player.json → skills/media/recipes/player.json} +1 -3
  116. package/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
  117. package/skills/media/scripts/media-utils.mjs +47 -0
  118. package/{scripts → skills/media/scripts}/music-player.mjs +9 -3
  119. package/skills/project-work/SKILL.md +16 -0
  120. package/skills/project-work/recipes/changelog-section.json +11 -0
  121. package/{dist/recipes/pipeline-docs-maintenance.json → skills/project-work/recipes/docs-maintenance.json} +4 -4
  122. package/skills/project-work/recipes/package-summary.json +9 -0
  123. package/{dist/recipes/pipeline-release-readiness.json → skills/project-work/recipes/release-readiness.json} +6 -6
  124. package/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
  125. package/{dist/recipes/pipeline-repo-health.json → skills/project-work/recipes/repo-health.json} +6 -6
  126. package/{dist/recipes/pipeline-async-run-ops.json → skills/project-work/recipes/run-ops.json} +3 -3
  127. package/skills/project-work/recipes/skill-summary.json +11 -0
  128. package/skills/project-work/scripts/project-utils.mjs +89 -0
  129. package/skills/recipe-memory/SKILL.md +16 -0
  130. package/skills/swarm/SKILL.md +4 -4
  131. package/{dist/recipes/pipeline-architect-coordinator.json → skills/swarm/recipes/architect.json} +5 -5
  132. package/{dist/recipes/pipeline-checkpoint-continuation.json → skills/swarm/recipes/checkpoint-continuation.json} +3 -3
  133. package/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
  134. package/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
  135. package/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
  136. package/{dist/recipes/pipeline-research-synthesis.json → skills/swarm/recipes/research-synthesis.json} +6 -6
  137. package/{dist/recipes/pipeline-review-readiness.json → skills/swarm/recipes/review-readiness.json} +1 -1
  138. package/{dist/recipes/subagents-prompts.json → skills/swarm/recipes/subagent-prompts.json} +1 -1
  139. package/{dist → skills/swarm}/recipes/subagent-quorum.json +1 -1
  140. package/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
  141. package/dist/recipes/utility-changelog-section.json +0 -13
  142. package/dist/recipes/utility-package-summary.json +0 -11
  143. package/dist/recipes/utility-run-summary.json +0 -11
  144. package/dist/recipes/utility-skill-summary.json +0 -13
  145. package/dist/recipes/utility-validate-recipe.json +0 -13
  146. package/dist/scripts/executable-block-style.d.mts +0 -1
  147. package/dist/scripts/executable-block-style.mjs +0 -73
  148. package/dist/scripts/recipe-utils.mjs +0 -404
  149. package/dist/scripts/release-gates.mjs +0 -281
  150. package/recipes/utility-changelog-section.json +0 -13
  151. package/recipes/utility-package-summary.json +0 -11
  152. package/recipes/utility-run-summary.json +0 -11
  153. package/recipes/utility-skill-summary.json +0 -13
  154. package/recipes/utility-validate-recipe.json +0 -13
  155. package/scripts/executable-block-style.d.mts +0 -1
  156. package/scripts/executable-block-style.mjs +0 -73
  157. package/scripts/recipe-utils.mjs +0 -404
  158. package/scripts/release-gates.mjs +0 -281
  159. /package/dist/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
  160. /package/dist/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
  161. /package/dist/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
  162. /package/dist/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
  163. /package/dist/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
  164. /package/dist/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
  165. /package/dist/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
  166. /package/dist/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
  167. /package/dist/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
  168. /package/dist/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
  169. /package/dist/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
  170. /package/dist/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
  171. /package/dist/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
  172. /package/dist/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
  173. /package/dist/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
  174. /package/dist/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
  175. /package/dist/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
  176. /package/dist/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
  177. /package/dist/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
  178. /package/dist/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
  179. /package/dist/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
  180. /package/dist/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
  181. /package/dist/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
  182. /package/dist/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
  183. /package/dist/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
  184. /package/dist/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
  185. /package/dist/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
  186. /package/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
  187. /package/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
  188. /package/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
  189. /package/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
  190. /package/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
  191. /package/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
  192. /package/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
  193. /package/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
  194. /package/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
  195. /package/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
  196. /package/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
  197. /package/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
  198. /package/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
  199. /package/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
  200. /package/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
  201. /package/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
  202. /package/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
  203. /package/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
  204. /package/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
  205. /package/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
  206. /package/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
  207. /package/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
  208. /package/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
  209. /package/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
  210. /package/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
  211. /package/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
  212. /package/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
@@ -4,9 +4,8 @@
4
4
  * Owns detection, loading, and recipe-layer expansion for template recipe files
5
5
  */
6
6
 
7
- import { existsSync, readFileSync, statSync } from "node:fs";
8
- import { homedir } from "node:os";
9
- import { basename, dirname, extname, isAbsolute, relative, resolve } from "node:path";
7
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
10
9
 
11
10
  import type {
12
11
  CommandTemplateConfig,
@@ -29,7 +28,6 @@ export interface TemplateRecipeImportBinding {
29
28
  export type TemplateRecipeImport = string | TemplateRecipeImportBinding;
30
29
 
31
30
  export interface TemplateRecipeDefinition {
32
- name?: string;
33
31
  description?: string;
34
32
  disabled?: boolean;
35
33
  imports?: Record<string, TemplateRecipeImport>;
@@ -57,6 +55,7 @@ export interface TemplateRecipeDefinition {
57
55
  }
58
56
 
59
57
  export interface TemplateRecipeConfig extends TemplateRecipeDefinition {
58
+ name?: string;
60
59
  async?: boolean;
61
60
  recipe_dir?: string;
62
61
  skill_dir?: string;
@@ -66,7 +65,7 @@ interface ImportedRecipe {
66
65
  alias: string;
67
66
  file: string;
68
67
  name: string;
69
- config: TemplateRecipeDefinition;
68
+ config: TemplateRecipeConfig;
70
69
  defaults: Record<string, unknown>;
71
70
  values: Record<string, unknown>;
72
71
  }
@@ -74,20 +73,25 @@ interface ImportedRecipe {
74
73
  export interface TemplateRecipeContextRecord {
75
74
  alias?: string;
76
75
  depth: number;
77
- file: string;
78
76
  import_path: string[];
77
+ logical_reference: string;
79
78
  name: string;
80
- qualified_name?: string;
81
79
  recipe: Record<string, unknown>;
82
80
  role: "entry" | "import";
81
+ skill?: string;
82
+ source_file: string;
83
+ source_kind:
84
+ | "active_skill_component"
85
+ | "explicit_file_recipe"
86
+ | "user_registry_capability";
83
87
  }
84
88
 
85
89
  export interface ReadResolvedRecipeConfigOptions {
86
90
  includeActorRecipeContext?: boolean;
91
+ skillContext?: ActiveSkillRecipeContext;
87
92
  }
88
93
 
89
94
  const RUNTIME_RECIPE_VALUE_NAMES = new Set(["recipe_dir", "skill_dir"]);
90
- const activeSkillRecipeRoots = new Map<string, Set<string>>();
91
95
 
92
96
  export interface ActiveSkillRecipeSource {
93
97
  name: string;
@@ -95,10 +99,14 @@ export interface ActiveSkillRecipeSource {
95
99
  baseDir?: string;
96
100
  }
97
101
 
98
- export function setActiveSkillRecipeSources(
102
+ export interface ActiveSkillRecipeContext {
103
+ readonly namespaces: Readonly<Record<string, readonly string[]>>;
104
+ }
105
+
106
+ export function createActiveSkillRecipeContext(
99
107
  skills: ActiveSkillRecipeSource[],
100
- ): void {
101
- activeSkillRecipeRoots.clear();
108
+ ): ActiveSkillRecipeContext {
109
+ const rootsByName = new Map<string, Set<string>>();
102
110
  for (const skill of skills) {
103
111
  const name = skill.name.trim();
104
112
  const skillDir = skill.baseDir
@@ -107,72 +115,179 @@ export function setActiveSkillRecipeSources(
107
115
  ? dirname(resolve(skill.filePath))
108
116
  : undefined;
109
117
  if (!name || !skillDir) continue;
110
- const roots = activeSkillRecipeRoots.get(name) ?? new Set<string>();
118
+ const roots = rootsByName.get(name) ?? new Set<string>();
111
119
  roots.add(resolve(skillDir, "recipes"));
112
- activeSkillRecipeRoots.set(name, roots);
120
+ rootsByName.set(name, roots);
113
121
  }
122
+ const namespaces = Object.fromEntries(
123
+ [...rootsByName.entries()]
124
+ .sort(([left], [right]) => left.localeCompare(right))
125
+ .map(([name, roots]) => [name, Object.freeze([...roots].sort())]),
126
+ );
127
+ return Object.freeze({ namespaces: Object.freeze(namespaces) });
114
128
  }
115
129
 
116
- export function getActiveSkillRecipeNamespaces(): Record<string, string[]> {
130
+ export const EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT =
131
+ createActiveSkillRecipeContext([]);
132
+
133
+ export function getActiveSkillRecipeNamespaces(
134
+ context: ActiveSkillRecipeContext,
135
+ ): Record<string, string[]> {
117
136
  return Object.fromEntries(
118
- [...activeSkillRecipeRoots.entries()]
119
- .sort(([left], [right]) => left.localeCompare(right))
120
- .map(([name, roots]) => [name, [...roots].sort()]),
137
+ Object.entries(context.namespaces).map(([name, roots]) => [
138
+ name,
139
+ [...roots],
140
+ ]),
121
141
  );
122
142
  }
123
143
 
124
- function qualifiedRecipeNameForFile(file: string): string | undefined {
125
- const path = resolve(file);
126
- const stdRelation = relative(Paths.getPackagedRecipeRoot(), path);
127
- if (
128
- stdRelation &&
129
- !stdRelation.startsWith("..") &&
130
- !isAbsolute(stdRelation)
131
- ) {
132
- return `std:${stdRelation.replace(/\.(?:json|md)$/u, "")}`;
144
+ export interface ActiveSkillRecipeComponent {
145
+ file: string;
146
+ identity: string;
147
+ imports: Record<string, string>;
148
+ skill: string;
149
+ stem: string;
150
+ }
151
+
152
+ function nestedRecipeFiles(root: string): string[] {
153
+ if (!existsSync(root)) return [];
154
+ return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
155
+ const path = join(root, entry.name);
156
+ if (entry.isDirectory()) return nestedRecipeFiles(path);
157
+ return entry.isFile() && /\.(?:json|md)$/u.test(entry.name) ? [path] : [];
158
+ });
159
+ }
160
+
161
+ export function listActiveSkillRecipeComponents(
162
+ context: ActiveSkillRecipeContext,
163
+ ): ActiveSkillRecipeComponent[] {
164
+ const components: ActiveSkillRecipeComponent[] = [];
165
+ for (const [skill, roots] of Object.entries(context.namespaces)) {
166
+ if (roots.length > 1)
167
+ throw new Error(`Duplicate active Skill identity ${skill}: ${roots.join(", ")}`);
168
+ const root = roots[0];
169
+ if (!root || !existsSync(root)) continue;
170
+ const direct = readdirSync(root, { withFileTypes: true });
171
+ for (const entry of direct.filter((candidate) => candidate.isDirectory())) {
172
+ const nested = nestedRecipeFiles(join(root, entry.name));
173
+ if (nested.length > 0)
174
+ throw new Error(`Nested Skill Recipe file is not addressable: ${nested[0]}`);
175
+ }
176
+ const files = direct
177
+ .filter(
178
+ (entry) => entry.isFile() && /\.(?:json|md)$/u.test(entry.name),
179
+ )
180
+ .map((entry) => join(root, entry.name));
181
+ const byStem = new Map<string, string[]>();
182
+ for (const file of files) {
183
+ const stem = getRecipeIdFromPath(file);
184
+ const matches = byStem.get(stem) ?? [];
185
+ matches.push(file);
186
+ byStem.set(stem, matches);
187
+ }
188
+ for (const [stem, matches] of [...byStem].sort(([a], [b]) => a.localeCompare(b))) {
189
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(stem))
190
+ throw new Error(`Invalid Skill Recipe filename stem: ${stem}`);
191
+ if (matches.length > 1)
192
+ throw new Error(`Skill Recipe stem collision: ${skill}/${stem} has both .json and .md files`);
193
+ const file = matches[0];
194
+ const raw = readRawRecipeConfig(file);
195
+ if (!raw || !Object.hasOwn(raw, "template"))
196
+ throw new Error(`Invalid Skill Recipe component: ${skill}/${stem}`);
197
+ assertRecipeHasNoDeclaredName(raw);
198
+ components.push({
199
+ file,
200
+ identity: `${skill}/${stem}`,
201
+ imports: Object.fromEntries(
202
+ Object.entries(getRecipeImports(raw)).map(([alias, value]) => [
203
+ alias,
204
+ getImportFrom(value),
205
+ ]),
206
+ ),
207
+ skill,
208
+ stem,
209
+ });
210
+ }
133
211
  }
134
- for (const [skillName, roots] of activeSkillRecipeRoots) {
135
- if (roots.size !== 1) continue;
136
- const root = [...roots][0];
212
+ return components;
213
+ }
214
+
215
+ const SKILL_RECIPE_REFERENCE =
216
+ /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\/([a-z][a-z0-9]*(?:-[a-z0-9]+)*)$/;
217
+
218
+ function qualifiedRecipeNameForFile(
219
+ file: string,
220
+ context: ActiveSkillRecipeContext,
221
+ ): string | undefined {
222
+ const path = resolve(file);
223
+ for (const [skillName, roots] of Object.entries(context.namespaces)) {
224
+ if (roots.length !== 1) continue;
225
+ const root = roots[0];
137
226
  const relation = relative(root, path);
138
- if (relation && !relation.startsWith("..") && !isAbsolute(relation))
139
- return `skill:${skillName}/${relation.replaceAll("\\", "/").replace(/\.(?:json|md)$/u, "")}`;
227
+ if (
228
+ relation &&
229
+ dirname(relation) === "." &&
230
+ !relation.startsWith("..") &&
231
+ !isAbsolute(relation)
232
+ ) {
233
+ return `${skillName}/${basename(relation, extname(relation))}`;
234
+ }
140
235
  }
141
236
  return undefined;
142
237
  }
143
238
 
144
- function qualifiedRecipeCandidates(value: string): string[] | undefined {
239
+ function assertRecipeReferencePrefixWasNotRemoved(value: string): void {
145
240
  if (value.startsWith("std:")) {
146
- return recipeNameFiles(value.slice(4)).map((file) =>
147
- resolve(Paths.getPackagedRecipeRoot(), file),
241
+ throw new Error(
242
+ "std: Recipe references were removed in pi-actors 0.46.\nUse <skill>/<recipe> or an explicit .json/.md file path.",
243
+ );
244
+ }
245
+ if (value.startsWith("skill:")) {
246
+ throw new Error(
247
+ "skill: Recipe references were removed in pi-actors 0.46.\nUse <skill>/<recipe> without a prefix.",
148
248
  );
149
249
  }
150
- if (!value.startsWith("skill:")) return undefined;
151
- const qualified = value.slice(6);
152
- const separator = qualified.indexOf("/");
153
- if (separator <= 0 || separator === qualified.length - 1)
154
- throw new Error(`Invalid Skill Recipe reference: ${value}`);
155
- const skillName = qualified.slice(0, separator);
156
- const recipeName = qualified.slice(separator + 1);
157
- const roots = [...(activeSkillRecipeRoots.get(skillName) ?? [])];
250
+ }
251
+
252
+ function skillRecipeCandidates(
253
+ value: string,
254
+ context: ActiveSkillRecipeContext,
255
+ ): string[] | undefined {
256
+ const match = SKILL_RECIPE_REFERENCE.exec(value);
257
+ if (!match) return undefined;
258
+ const [, skillName, recipeName] = match;
259
+ const roots = [...(context.namespaces[skillName] ?? [])];
158
260
  if (roots.length === 0)
159
- throw new Error(`Active Skill Recipe namespace not found: ${skillName}`);
261
+ throw new Error(`Active Skill Recipe not found: ${skillName}/${recipeName}`);
160
262
  if (roots.length > 1)
161
263
  throw new Error(
162
- `Ambiguous active Skill Recipe namespace ${skillName}: ${roots.join(", ")}`,
264
+ `Duplicate active Skill identity ${skillName}: ${roots.join(", ")}`,
163
265
  );
164
266
  const root = roots[0];
165
- return recipeNameFiles(recipeName).map((file) => {
166
- const candidate = resolve(root, file);
167
- const relation = relative(root, candidate);
168
- if (!relation || relation.startsWith("..") || isAbsolute(relation))
169
- throw new Error(`Skill Recipe reference escapes its recipes root: ${value}`);
170
- return candidate;
171
- });
267
+ const candidates = ["json", "md"].map((extension) =>
268
+ resolve(root, `${recipeName}.${extension}`),
269
+ );
270
+ const existing = candidates.filter((candidate) => existsSync(candidate));
271
+ if (existing.length > 1) {
272
+ throw new Error(
273
+ `Skill Recipe stem collision: ${skillName}/${recipeName} has both .json and .md files`,
274
+ );
275
+ }
276
+ return candidates;
172
277
  }
173
278
 
174
- function hasWhitespace(value: string): boolean {
175
- return /\s/.test(value);
279
+ function recipeReferenceCandidates(
280
+ value: string,
281
+ baseDir: string,
282
+ context: ActiveSkillRecipeContext,
283
+ ): string[] | undefined {
284
+ const trimmed = value.trim();
285
+ if (!trimmed) return undefined;
286
+ assertRecipeReferencePrefixWasNotRemoved(trimmed);
287
+ if (trimmed.endsWith(".json") || trimmed.endsWith(".md")) {
288
+ return [resolve(baseDir, trimmed)];
289
+ }
290
+ return skillRecipeCandidates(trimmed, context);
176
291
  }
177
292
 
178
293
  function findOwningSkillDir(recipeFile: string): string | undefined {
@@ -185,6 +300,13 @@ function findOwningSkillDir(recipeFile: string): string | undefined {
185
300
  }
186
301
  }
187
302
 
303
+ function assertRecipeHasNoDeclaredName(raw: Record<string, unknown>): void {
304
+ if (!Object.hasOwn(raw, "name")) return;
305
+ throw new Error(
306
+ "Recipe.name was removed in pi-actors 0.46.\nFile-backed Recipe identity is derived from its file name.",
307
+ );
308
+ }
309
+
188
310
  function assertRuntimeRecipeValuesNotDeclared(raw: Record<string, unknown>): void {
189
311
  const args = Array.isArray(raw.args)
190
312
  ? Schema.getExplicitToolArgNames(raw.args.map(String))
@@ -202,100 +324,51 @@ function assertRuntimeRecipeValuesNotDeclared(raw: Record<string, unknown>): voi
202
324
 
203
325
  export function resolveRecipePath(
204
326
  value: string,
205
- recipeRoot = Paths.getRecipeRoot(),
327
+ baseDir = Paths.getRecipeRoot(),
206
328
  ): string {
207
- const trimmed = value.trim();
208
- const repoRoot = resolve(recipeRoot, "..");
209
- const expanded = trimmed
210
- .replaceAll("{repo}", repoRoot)
211
- .replaceAll("{agent}", Paths.getAgentDir());
212
- if (expanded.startsWith("~/")) return resolve(homedir(), expanded.slice(2));
213
- if (expanded.includes("/")) return resolve(expanded);
214
- return resolve(
215
- recipeRoot,
216
- expanded.endsWith(".json") || expanded.endsWith(".md")
217
- ? expanded
218
- : `${expanded}.json`,
219
- );
220
- }
221
-
222
- function isBareRecipeName(value: string): boolean {
223
- const trimmed = value.trim();
224
- return (
225
- Boolean(trimmed) &&
226
- !trimmed.includes("/") &&
227
- !trimmed.startsWith("~") &&
228
- !trimmed.includes("{")
329
+ const candidates = recipeReferenceCandidates(
330
+ value,
331
+ baseDir,
332
+ EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT,
229
333
  );
230
- }
231
-
232
- function recipeNameFiles(value: string): string[] {
233
- const trimmed = value.trim();
234
- if (trimmed.endsWith(".json") || trimmed.endsWith(".md")) return [trimmed];
235
- return [`${trimmed}.json`, `${trimmed}.md`];
236
- }
237
-
238
- function recipeCandidatePaths(
239
- value: string,
240
- currentRecipeRoot: string,
241
- ): string[] {
242
- const qualified = qualifiedRecipeCandidates(value.trim());
243
- if (qualified) return qualified;
244
- if (!isBareRecipeName(value))
245
- return [resolveRecipePath(value, currentRecipeRoot)];
246
- const roots = [
247
- Paths.getRecipeRoot(),
248
- currentRecipeRoot,
249
- Paths.getPackagedRecipeRoot(),
250
- ];
251
- return [
252
- ...new Set(
253
- roots.flatMap((root) =>
254
- recipeNameFiles(value).map((file) => resolve(root, file)),
255
- ),
256
- ),
257
- ];
334
+ if (!candidates || candidates.length !== 1) {
335
+ throw new Error(
336
+ `Recipe file reference must be an explicit .json or .md path: ${value}`,
337
+ );
338
+ }
339
+ return candidates[0];
258
340
  }
259
341
 
260
342
  function resolveRecipeImportPath(
261
343
  value: string,
262
- currentRecipeRoot: string,
344
+ recipeDir: string,
345
+ context: ActiveSkillRecipeContext,
263
346
  ): string {
264
- const candidates = recipeCandidatePaths(value, currentRecipeRoot);
347
+ const candidates = recipeReferenceCandidates(value, recipeDir, context);
348
+ if (!candidates) {
349
+ throw new Error(
350
+ `Recipe import must use <skill>/<recipe> or an explicit .json/.md file path: ${value}`,
351
+ );
352
+ }
265
353
  return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
266
354
  }
267
355
 
268
356
  export function resolveRecipeReferencePath(
269
357
  value: unknown,
270
- currentRecipeRoot = Paths.getRecipeRoot(),
358
+ baseDir = Paths.getRecipeRoot(),
359
+ context = EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT,
271
360
  ): string | undefined {
272
361
  if (typeof value !== "string") return undefined;
273
- const trimmed = value.trim();
274
- if (!trimmed || hasWhitespace(trimmed)) return undefined;
275
- for (const path of recipeCandidatePaths(trimmed, currentRecipeRoot)) {
276
- if (!existsSync(path)) continue;
277
- try {
278
- const raw = readRawRecipeConfig(path);
279
- if (raw && typeof raw === "object" && Object.hasOwn(raw, "template"))
280
- return path;
281
- return path;
282
- } catch {
283
- return path;
284
- }
285
- }
286
- return undefined;
362
+ const candidates = recipeReferenceCandidates(value, baseDir, context);
363
+ return candidates?.find((candidate) => existsSync(candidate));
287
364
  }
288
365
 
289
366
  export function getRecipePath(
290
367
  value: unknown,
291
- recipeRoot = Paths.getRecipeRoot(),
368
+ baseDir = Paths.getRecipeRoot(),
369
+ context = EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT,
292
370
  ): string | undefined {
293
- if (typeof value !== "string") return undefined;
294
- const trimmed = value.trim();
295
- if (!trimmed || hasWhitespace(trimmed)) return undefined;
296
- if (trimmed.endsWith(".json") || trimmed.endsWith(".md"))
297
- return resolveRecipePath(trimmed, recipeRoot);
298
- return resolveRecipeReferencePath(trimmed, recipeRoot);
371
+ return resolveRecipeReferencePath(value, baseDir, context);
299
372
  }
300
373
 
301
374
  function isImportNode(value: unknown): boolean {
@@ -873,7 +946,11 @@ function loadDelegatedRecipe(
873
946
  stack: string[],
874
947
  options: ReadResolvedRecipeConfigOptions,
875
948
  ): TemplateRecipeConfig | undefined {
876
- const path = resolveRecipeReferencePath(value, dirname(currentRecipeFile));
949
+ const path = resolveRecipeReferencePath(
950
+ value,
951
+ dirname(currentRecipeFile),
952
+ options.skillContext,
953
+ );
877
954
  if (!path) return undefined;
878
955
  const config = readResolvedRecipeConfig(
879
956
  path,
@@ -1082,6 +1159,7 @@ export function readResolvedRecipeConfig(
1082
1159
  }
1083
1160
  const raw = readRawRecipeConfig(path);
1084
1161
  if (!raw || !Object.hasOwn(raw, "template")) return undefined;
1162
+ assertRecipeHasNoDeclaredName(raw);
1085
1163
  RecipeControl.assertRecipeHasNoMailbox(raw);
1086
1164
  assertRuntimeRecipeValuesNotDeclared(raw);
1087
1165
  assertRecipeArgumentContract(raw as unknown as TemplateRecipeDefinition);
@@ -1090,6 +1168,7 @@ export function readResolvedRecipeConfig(
1090
1168
  const importPath = resolveRecipeImportPath(
1091
1169
  getImportFrom(binding),
1092
1170
  dirname(path),
1171
+ options.skillContext ?? EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT,
1093
1172
  );
1094
1173
  const config = readResolvedRecipeConfig(
1095
1174
  importPath,
@@ -1109,7 +1188,7 @@ export function readResolvedRecipeConfig(
1109
1188
  imports[alias] = {
1110
1189
  alias,
1111
1190
  file: importPath,
1112
- name: config.name ?? alias,
1191
+ name: getRecipeIdFromPath(importPath),
1113
1192
  config,
1114
1193
  defaults: { ...(config.defaults ?? {}), ...(bindingDefaults ?? {}) },
1115
1194
  values: {
@@ -1286,6 +1365,7 @@ function collectRecipeContextRecords(
1286
1365
  file: string,
1287
1366
  stack: string[],
1288
1367
  importPath: string[],
1368
+ context: ActiveSkillRecipeContext,
1289
1369
  alias?: string,
1290
1370
  ): TemplateRecipeContextRecord[] {
1291
1371
  const path = resolveRecipePath(
@@ -1302,27 +1382,35 @@ function collectRecipeContextRecords(
1302
1382
  }
1303
1383
  const raw = readRawRecipeConfig(path);
1304
1384
  if (!raw || !Object.hasOwn(raw, "template")) return [];
1305
- const qualifiedName = qualifiedRecipeNameForFile(path);
1385
+ assertRecipeHasNoDeclaredName(raw);
1386
+ const qualifiedName = qualifiedRecipeNameForFile(path, context);
1387
+ const skill = qualifiedName?.split("/", 1)[0];
1306
1388
  const record: TemplateRecipeContextRecord = {
1307
1389
  ...(alias ? { alias } : {}),
1308
1390
  depth: stack.length,
1309
- file: path,
1310
1391
  import_path: importPath,
1392
+ logical_reference: qualifiedName ?? basename(path),
1311
1393
  name: getRecipeIdFromPath(path),
1312
- ...(qualifiedName ? { qualified_name: qualifiedName } : {}),
1313
1394
  recipe: raw,
1314
1395
  role: stack.length === 0 ? "entry" : "import",
1396
+ ...(skill ? { skill } : {}),
1397
+ source_file: path,
1398
+ source_kind: qualifiedName
1399
+ ? "active_skill_component"
1400
+ : "explicit_file_recipe",
1315
1401
  };
1316
1402
  const imports = getRecipeImports(raw);
1317
1403
  const children = Object.entries(imports).flatMap(([childAlias, binding]) => {
1318
1404
  const importFile = resolveRecipeImportPath(
1319
1405
  getImportFrom(binding),
1320
1406
  dirname(path),
1407
+ context,
1321
1408
  );
1322
1409
  return collectRecipeContextRecords(
1323
1410
  importFile,
1324
1411
  [...stack, path],
1325
1412
  [...importPath, childAlias],
1413
+ context,
1326
1414
  childAlias,
1327
1415
  );
1328
1416
  });
@@ -1331,8 +1419,9 @@ function collectRecipeContextRecords(
1331
1419
 
1332
1420
  export function buildRecipeContextRecords(
1333
1421
  file: string,
1422
+ context = EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT,
1334
1423
  ): TemplateRecipeContextRecord[] {
1335
- return collectRecipeContextRecords(file, [], []);
1424
+ return collectRecipeContextRecords(file, [], [], context);
1336
1425
  }
1337
1426
 
1338
1427
  export function getRecipeTemplate(
package/lib/runtime.ts CHANGED
@@ -23,7 +23,6 @@ export interface RuntimeContext {
23
23
  export interface ToolRegistryRuntimeDeps {
24
24
  configPath: string;
25
25
  exec: RegisteredToolExec;
26
- packagedRecipeRoot?: string;
27
26
  recipeRoot?: string;
28
27
  getActiveTools?: () => string[];
29
28
  registerTool: (
@@ -125,7 +124,7 @@ export function createAutoToolsRuntime(
125
124
  );
126
125
  const lines = ["pi-actors recipe registry warning"];
127
126
  if (shadowed.length > 0) {
128
- lines.push("User recipes override packaged recipes:");
127
+ lines.push("User recipes shadow lower-priority user sources:");
129
128
  lines.push(...shadowed.map((warning) => `• ${warning}`));
130
129
  }
131
130
  if (skipped.length > 0) {
@@ -141,11 +140,8 @@ export function createAutoToolsRuntime(
141
140
  function loadTools(ctx: RuntimeContext) {
142
141
  const warnings: string[] = [];
143
142
  const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
144
- const packagedRecipeRoot =
145
- deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot();
146
143
  const discovered = RecipesDiscovery.discoverRecipeSources([
147
144
  { root: recipeRoot, defaultTool: true, mutableUsage: true },
148
- { root: packagedRecipeRoot },
149
145
  ]);
150
146
  warnings.push(...discovered.diagnostics);
151
147
  tools.clear();
@@ -33,7 +33,6 @@ export interface InspectToolDeps<TContext = unknown> {
33
33
  getRunStatus?: (runOrDir: string) => Record<string, any>;
34
34
  getTool?: (name: string) => any | undefined;
35
35
  listRuns?: () => Array<Record<string, any>>;
36
- packagedRecipeRoot?: string;
37
36
  recipeRoot?: string;
38
37
  }
39
38
 
@@ -195,6 +194,7 @@ function inspectRuntime(
195
194
  function inspectRecipes(
196
195
  view: string,
197
196
  deps: InspectToolDeps,
197
+ context: unknown,
198
198
  ): Record<string, unknown> {
199
199
  if (
200
200
  view !== "status" &&
@@ -213,18 +213,51 @@ function inspectRecipes(
213
213
  }
214
214
  const discovered = RecipesDiscovery.discoverRecipeSources([
215
215
  { root: recipeRoot, defaultTool: true, mutableUsage: true },
216
- { root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
217
216
  ]);
218
- const skillRecipeNamespaces =
219
- RecipesReferences.getActiveSkillRecipeNamespaces();
217
+ const skillContext =
218
+ context && typeof context === "object" && "activeSkillRecipeContext" in context
219
+ ? (context as {
220
+ activeSkillRecipeContext?: RecipesReferences.ActiveSkillRecipeContext;
221
+ }).activeSkillRecipeContext
222
+ : undefined;
223
+ const activeSkillContext =
224
+ skillContext ?? RecipesReferences.EMPTY_ACTIVE_SKILL_RECIPE_CONTEXT;
225
+ const skillRecipeNamespaces = RecipesReferences.getActiveSkillRecipeNamespaces(
226
+ activeSkillContext,
227
+ );
228
+ let skillRecipeComponents: Array<Record<string, unknown>> = [];
229
+ const skillRecipeComponentDiagnostics: Array<Record<string, unknown>> = [];
230
+ try {
231
+ skillRecipeComponents = RecipesReferences.listActiveSkillRecipeComponents(
232
+ activeSkillContext,
233
+ ).map((component) => ({
234
+ identity: component.identity,
235
+ source_kind: "active_skill_component",
236
+ skill: component.skill,
237
+ stem: component.stem,
238
+ imports: component.imports,
239
+ }));
240
+ } catch (error) {
241
+ skillRecipeComponentDiagnostics.push({
242
+ error: error instanceof Error ? error.message : String(error),
243
+ });
244
+ }
245
+ const discoverySummary = RecipesDiscovery.summarizeDiscovery(discovered);
220
246
  const summary = {
221
- ...RecipesDiscovery.summarizeDiscovery(discovered),
222
- skill_recipe_namespaces: skillRecipeNamespaces,
247
+ ...discoverySummary,
248
+ active: Array.isArray(discoverySummary.active)
249
+ ? discoverySummary.active.map((entry) => ({
250
+ ...(entry as Record<string, unknown>),
251
+ source_kind: "user_registry_capability",
252
+ }))
253
+ : discoverySummary.active,
254
+ skill_recipe_components: skillRecipeComponents,
255
+ skill_recipe_component_diagnostics: skillRecipeComponentDiagnostics,
256
+ active_skill_recipe_identities: Object.keys(skillRecipeNamespaces).sort(),
223
257
  skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
224
258
  .filter(([, roots]) => roots.length > 1)
225
- .map(([name, roots]) => ({
259
+ .map(([name]) => ({
226
260
  name,
227
- roots,
228
261
  error: `Ambiguous active Skill Recipe namespace ${name}`,
229
262
  })),
230
263
  drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
@@ -386,7 +419,7 @@ export function createInspectToolDefinition<TContext = unknown>(
386
419
  if (target === "runtime") {
387
420
  details = inspectRuntime(view, input, ctx, deps);
388
421
  } else if (target === "recipes") {
389
- details = inspectRecipes(view, deps);
422
+ details = inspectRecipes(view, deps, ctx);
390
423
  } else {
391
424
  const run = parseRunTarget(target);
392
425
  if (run) details = inspectRun(run, view, input, ctx, deps);
@@ -18,6 +18,7 @@ import * as ToolsResponse from "./tools-response.ts";
18
18
  type JsonSchema = Schema.JsonSchema;
19
19
 
20
20
  export interface RuntimeToolContext extends ModelContext.CurrentModelContext {
21
+ activeSkillRecipeContext?: RecipesReferences.ActiveSkillRecipeContext;
21
22
  cwd: string;
22
23
  sessionManager?: { getSessionId?: () => string };
23
24
  }
@@ -243,6 +244,7 @@ export function createRuntimeToolDefinition(
243
244
  values: resolveRecipeToolValues(cfg, values, ctx),
244
245
  },
245
246
  ctx.cwd,
247
+ { skillContext: ctx.activeSkillRecipeContext },
246
248
  );
247
249
  return {
248
250
  content: [