@llblab/pi-actors 0.45.0 → 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 (211) hide show
  1. package/AGENTS.md +10 -6
  2. package/CHANGELOG.md +14 -1
  3. package/README.md +28 -12
  4. package/dist/fixtures/protocol/recipe-summary.json +1 -1
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.js +10 -110
  7. package/dist/lib/async-runs.d.ts +4 -1
  8. package/dist/lib/async-runs.js +22 -23
  9. package/dist/lib/automatic-review-runtime.d.ts +2 -0
  10. package/dist/lib/automatic-review-runtime.js +12 -5
  11. package/dist/lib/draft-sleep.js +0 -1
  12. package/dist/lib/extension-runtime.d.ts +21 -0
  13. package/dist/lib/extension-runtime.js +122 -0
  14. package/dist/lib/inspector.js +45 -7
  15. package/dist/lib/paths.d.ts +0 -1
  16. package/dist/lib/paths.js +0 -7
  17. package/dist/lib/prompts.d.ts +1 -1
  18. package/dist/lib/prompts.js +2 -2
  19. package/dist/lib/recipes-context.js +2 -2
  20. package/dist/lib/recipes-references.d.ts +24 -9
  21. package/dist/lib/recipes-references.js +152 -126
  22. package/dist/lib/runtime.d.ts +0 -1
  23. package/dist/lib/runtime.js +1 -3
  24. package/dist/lib/tools-inspect.d.ts +0 -1
  25. package/dist/lib/tools-inspect.js +35 -8
  26. package/dist/lib/tools-local.d.ts +2 -0
  27. package/dist/lib/tools-local.js +1 -1
  28. package/dist/lib/tools-spawn.d.ts +2 -0
  29. package/dist/lib/tools-spawn.js +24 -52
  30. package/dist/scripts/async-runner.mjs +0 -16
  31. package/dist/scripts/build-dist.mjs +13 -4
  32. package/dist/skills/actors/SKILL.md +6 -6
  33. package/dist/skills/actors/recipes/recipe-validate.json +11 -0
  34. package/{recipes/utility-coordinator-lock-snapshot.json → dist/skills/actors/recipes/resource-locker-snapshot.json} +1 -3
  35. package/dist/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
  36. package/dist/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
  37. package/dist/skills/actors/recipes/run-summary.json +9 -0
  38. package/dist/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -23
  39. package/dist/skills/actors/scripts/run-utils.mjs +164 -0
  40. package/{scripts → dist/skills/actors/scripts}/validate-recipe.mjs +134 -18
  41. package/dist/skills/artifacts/SKILL.md +16 -0
  42. package/{recipes/pipeline-artifact-bundle.json → dist/skills/artifacts/recipes/bundle.json} +4 -4
  43. package/dist/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
  44. package/dist/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
  45. package/{recipes/pipeline-artifact-report.json → dist/skills/artifacts/recipes/report.json} +2 -2
  46. package/dist/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
  47. package/dist/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  48. package/dist/skills/media/SKILL.md +16 -0
  49. package/dist/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
  50. package/{recipes/music-player.json → dist/skills/media/recipes/player.json} +1 -3
  51. package/dist/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
  52. package/dist/skills/media/scripts/media-utils.mjs +47 -0
  53. package/dist/{scripts → skills/media/scripts}/music-player.mjs +9 -3
  54. package/dist/skills/project-work/SKILL.md +16 -0
  55. package/dist/skills/project-work/recipes/changelog-section.json +11 -0
  56. package/{recipes/pipeline-docs-maintenance.json → dist/skills/project-work/recipes/docs-maintenance.json} +4 -4
  57. package/dist/skills/project-work/recipes/package-summary.json +9 -0
  58. package/{recipes/pipeline-release-readiness.json → dist/skills/project-work/recipes/release-readiness.json} +6 -6
  59. package/dist/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
  60. package/{recipes/pipeline-repo-health.json → dist/skills/project-work/recipes/repo-health.json} +6 -6
  61. package/{recipes/pipeline-async-run-ops.json → dist/skills/project-work/recipes/run-ops.json} +3 -3
  62. package/dist/skills/project-work/recipes/skill-summary.json +11 -0
  63. package/dist/skills/project-work/scripts/project-utils.mjs +89 -0
  64. package/dist/skills/recipe-memory/SKILL.md +16 -0
  65. package/dist/skills/swarm/SKILL.md +4 -4
  66. package/{recipes/pipeline-architect-coordinator.json → dist/skills/swarm/recipes/architect.json} +5 -5
  67. package/{recipes/pipeline-checkpoint-continuation.json → dist/skills/swarm/recipes/checkpoint-continuation.json} +3 -3
  68. package/dist/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
  69. package/dist/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
  70. package/dist/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
  71. package/{recipes/pipeline-research-synthesis.json → dist/skills/swarm/recipes/research-synthesis.json} +6 -6
  72. package/{recipes/pipeline-review-readiness.json → dist/skills/swarm/recipes/review-readiness.json} +1 -1
  73. package/{recipes/subagents-prompts.json → dist/skills/swarm/recipes/subagent-prompts.json} +1 -1
  74. package/{recipes → dist/skills/swarm/recipes}/subagent-quorum.json +1 -1
  75. package/dist/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
  76. package/docs/actor-inspector.md +4 -3
  77. package/docs/async-runs.md +2 -2
  78. package/docs/recipe-library.md +23 -23
  79. package/docs/releasing.md +2 -2
  80. package/docs/template-recipes.md +30 -14
  81. package/docs/tool-registry.md +2 -2
  82. package/fixtures/protocol/recipe-summary.json +1 -1
  83. package/index.ts +18 -112
  84. package/lib/async-runs.ts +47 -24
  85. package/lib/automatic-review-runtime.ts +16 -3
  86. package/lib/draft-sleep.ts +0 -1
  87. package/lib/extension-runtime.ts +158 -0
  88. package/lib/inspector.ts +56 -11
  89. package/lib/paths.ts +0 -7
  90. package/lib/prompts.ts +2 -2
  91. package/lib/recipes-context.ts +2 -2
  92. package/lib/recipes-references.ts +223 -134
  93. package/lib/runtime.ts +1 -5
  94. package/lib/tools-inspect.ts +42 -9
  95. package/lib/tools-local.ts +2 -0
  96. package/lib/tools-spawn.ts +37 -74
  97. package/package.json +3 -5
  98. package/scripts/async-runner.mjs +0 -16
  99. package/scripts/build-dist.mjs +13 -4
  100. package/skills/actors/SKILL.md +6 -6
  101. package/skills/actors/recipes/recipe-validate.json +11 -0
  102. package/{dist/recipes/utility-coordinator-lock-snapshot.json → skills/actors/recipes/resource-locker-snapshot.json} +1 -3
  103. package/{recipes → skills/actors/recipes}/resource-locker.json +1 -3
  104. package/{recipes/utility-run-ops-snapshot.json → skills/actors/recipes/run-ops-snapshot.json} +1 -3
  105. package/skills/actors/recipes/run-summary.json +9 -0
  106. package/{scripts/locker.mjs → skills/actors/scripts/resource-locker.mjs} +2 -23
  107. package/skills/actors/scripts/run-utils.mjs +164 -0
  108. package/{dist → skills/actors}/scripts/validate-recipe.mjs +134 -18
  109. package/skills/artifacts/SKILL.md +16 -0
  110. package/{dist/recipes/pipeline-artifact-bundle.json → skills/artifacts/recipes/bundle.json} +4 -4
  111. package/{recipes/utility-artifact-write.json → skills/artifacts/recipes/file-write.json} +1 -3
  112. package/{recipes/utility-artifact-manifest.json → skills/artifacts/recipes/manifest.json} +1 -3
  113. package/{dist/recipes/pipeline-artifact-report.json → skills/artifacts/recipes/report.json} +2 -2
  114. package/{recipes/pipeline-artifact-write.json → skills/artifacts/recipes/write.json} +3 -3
  115. package/skills/artifacts/scripts/artifact-utils.mjs +63 -0
  116. package/skills/media/SKILL.md +16 -0
  117. package/{recipes/pipeline-media-library.json → skills/media/recipes/library.json} +2 -2
  118. package/{dist/recipes/music-player.json → skills/media/recipes/player.json} +1 -3
  119. package/{recipes/utility-playlist-build.json → skills/media/recipes/playlist-build.json} +1 -3
  120. package/skills/media/scripts/media-utils.mjs +47 -0
  121. package/{scripts → skills/media/scripts}/music-player.mjs +9 -3
  122. package/skills/project-work/SKILL.md +16 -0
  123. package/skills/project-work/recipes/changelog-section.json +11 -0
  124. package/{dist/recipes/pipeline-docs-maintenance.json → skills/project-work/recipes/docs-maintenance.json} +4 -4
  125. package/skills/project-work/recipes/package-summary.json +9 -0
  126. package/{dist/recipes/pipeline-release-readiness.json → skills/project-work/recipes/release-readiness.json} +6 -6
  127. package/{recipes/pipeline-release-summary.json → skills/project-work/recipes/release-summary.json} +5 -5
  128. package/{dist/recipes/pipeline-repo-health.json → skills/project-work/recipes/repo-health.json} +6 -6
  129. package/{dist/recipes/pipeline-async-run-ops.json → skills/project-work/recipes/run-ops.json} +3 -3
  130. package/skills/project-work/recipes/skill-summary.json +11 -0
  131. package/skills/project-work/scripts/project-utils.mjs +89 -0
  132. package/skills/recipe-memory/SKILL.md +16 -0
  133. package/skills/swarm/SKILL.md +4 -4
  134. package/{dist/recipes/pipeline-architect-coordinator.json → skills/swarm/recipes/architect.json} +5 -5
  135. package/{dist/recipes/pipeline-checkpoint-continuation.json → skills/swarm/recipes/checkpoint-continuation.json} +3 -3
  136. package/{recipes/pipeline-development-tasking.json → skills/swarm/recipes/development-tasking.json} +4 -4
  137. package/{recipes/lens-swarm.json → skills/swarm/recipes/lens-review.json} +1 -1
  138. package/{recipes/pipeline-quorum-review.json → skills/swarm/recipes/quorum-review.json} +4 -4
  139. package/{dist/recipes/pipeline-research-synthesis.json → skills/swarm/recipes/research-synthesis.json} +6 -6
  140. package/{dist/recipes/pipeline-review-readiness.json → skills/swarm/recipes/review-readiness.json} +1 -1
  141. package/{dist/recipes/subagents-prompts.json → skills/swarm/recipes/subagent-prompts.json} +1 -1
  142. package/{dist → skills/swarm}/recipes/subagent-quorum.json +1 -1
  143. package/{recipes → skills/swarm/recipes}/subagent-review-coordinator.json +6 -6
  144. package/dist/recipes/utility-changelog-section.json +0 -13
  145. package/dist/recipes/utility-package-summary.json +0 -11
  146. package/dist/recipes/utility-run-summary.json +0 -11
  147. package/dist/recipes/utility-skill-summary.json +0 -13
  148. package/dist/recipes/utility-validate-recipe.json +0 -13
  149. package/dist/scripts/recipe-utils.mjs +0 -405
  150. package/dist/scripts/release-gates.mjs +0 -278
  151. package/recipes/utility-changelog-section.json +0 -13
  152. package/recipes/utility-package-summary.json +0 -11
  153. package/recipes/utility-run-summary.json +0 -11
  154. package/recipes/utility-skill-summary.json +0 -13
  155. package/recipes/utility-validate-recipe.json +0 -13
  156. package/scripts/recipe-utils.mjs +0 -405
  157. package/scripts/release-gates.mjs +0 -278
  158. /package/dist/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
  159. /package/dist/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
  160. /package/dist/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
  161. /package/dist/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
  162. /package/dist/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
  163. /package/dist/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
  164. /package/dist/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
  165. /package/dist/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
  166. /package/dist/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
  167. /package/dist/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
  168. /package/dist/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
  169. /package/dist/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
  170. /package/dist/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
  171. /package/dist/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
  172. /package/dist/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
  173. /package/dist/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
  174. /package/dist/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
  175. /package/dist/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
  176. /package/dist/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
  177. /package/dist/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
  178. /package/dist/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
  179. /package/dist/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
  180. /package/dist/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
  181. /package/dist/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
  182. /package/dist/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
  183. /package/dist/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
  184. /package/dist/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
  185. /package/{recipes/utility-validation-wrapper.json → skills/actors/recipes/command-validate.json} +0 -0
  186. /package/{recipes/utility-jsonl-tail.json → skills/actors/recipes/jsonl-tail.json} +0 -0
  187. /package/{recipes/utility-run-state-files.json → skills/actors/recipes/run-state-files.json} +0 -0
  188. /package/{recipes/utility-playlist-scan.json → skills/media/recipes/playlist-scan.json} +0 -0
  189. /package/{recipes/utility-changelog-head.json → skills/project-work/recipes/changelog-head.json} +0 -0
  190. /package/{recipes/utility-git-log.json → skills/project-work/recipes/git-log.json} +0 -0
  191. /package/{recipes/utility-git-status.json → skills/project-work/recipes/git-status.json} +0 -0
  192. /package/{recipes/utility-markdown-index.json → skills/project-work/recipes/markdown-index.json} +0 -0
  193. /package/{recipes → skills/recipe-memory/recipes}/draft-review.json +0 -0
  194. /package/{recipes → skills/recipe-memory/recipes}/tool-review.json +0 -0
  195. /package/{recipes → skills/swarm/recipes}/subagent-artifact.json +0 -0
  196. /package/{recipes → skills/swarm/recipes}/subagent-checkpoint.json +0 -0
  197. /package/{recipes → skills/swarm/recipes}/subagent-conflict-report.json +0 -0
  198. /package/{recipes → skills/swarm/recipes}/subagent-contradiction-map.json +0 -0
  199. /package/{recipes → skills/swarm/recipes}/subagent-critic.json +0 -0
  200. /package/{recipes → skills/swarm/recipes}/subagent-evidence-map.json +0 -0
  201. /package/{recipes → skills/swarm/recipes}/subagent-followup.json +0 -0
  202. /package/{recipes → skills/swarm/recipes}/subagent-judge.json +0 -0
  203. /package/{recipes → skills/swarm/recipes}/subagent-merge.json +0 -0
  204. /package/{recipes → skills/swarm/recipes}/subagent-normalize.json +0 -0
  205. /package/{recipes → skills/swarm/recipes}/subagent-plan.json +0 -0
  206. /package/{recipes → skills/swarm/recipes}/subagent-preflight.json +0 -0
  207. /package/{recipes → skills/swarm/recipes}/subagent-prompt.json +0 -0
  208. /package/{recipes → skills/swarm/recipes}/subagent-review.json +0 -0
  209. /package/{recipes → skills/swarm/recipes}/subagent-task-card.json +0 -0
  210. /package/{recipes → skills/swarm/recipes}/subagent-tools.json +0 -0
  211. /package/{recipes → skills/swarm/recipes}/subagent-verify.json +0 -0
@@ -21,12 +21,12 @@ import readline from "node:readline";
21
21
  import { fileURLToPath, pathToFileURL } from "node:url";
22
22
 
23
23
  function packageRoot() {
24
- return dirname(dirname(fileURLToPath(import.meta.url)));
24
+ return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
25
25
  }
26
26
 
27
27
  async function importRuntimeModule(name) {
28
28
  const root = packageRoot();
29
- const compiled = join(root, "dist", "lib", `${name}.js`);
29
+ const compiled = join(root, "lib", `${name}.js`);
30
30
  const source = join(root, "lib", `${name}.ts`);
31
31
  return await import(pathToFileURL(existsSync(compiled) ? compiled : source).href);
32
32
  }
@@ -65,7 +65,6 @@ async function runLocker(argv = process.argv.slice(2)) {
65
65
  const controlPath = join(stateDir, "control.fifo");
66
66
  let runInstanceId;
67
67
  mkdirSync(stateDir, { recursive: true });
68
-
69
68
  function readJson(path, fallback) {
70
69
  if (!existsSync(path)) return fallback;
71
70
  try {
@@ -74,11 +73,9 @@ async function runLocker(argv = process.argv.slice(2)) {
74
73
  return fallback;
75
74
  }
76
75
  }
77
-
78
76
  function writeJson(path, value) {
79
77
  writeJsonAtomic(path, value);
80
78
  }
81
-
82
79
  function getControlEndpoint() {
83
80
  if (process.platform !== "win32") return { path: controlPath, type: "fifo" };
84
81
  const hash = createHash("sha256")
@@ -87,7 +84,6 @@ async function runLocker(argv = process.argv.slice(2)) {
87
84
  .slice(0, 20);
88
85
  return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
89
86
  }
90
-
91
87
  function writeControlEndpoint(endpoint) {
92
88
  if (!runInstanceId) throw new Error("Run generation unavailable for Control endpoint");
93
89
  writeJson(join(stateDir, "control-endpoint.json"), {
@@ -96,7 +92,6 @@ async function runLocker(argv = process.argv.slice(2)) {
96
92
  run_instance_id: runInstanceId,
97
93
  });
98
94
  }
99
-
100
95
  function journal(event, data = {}) {
101
96
  const release = acquireFileMutationLock(journalPath);
102
97
  try {
@@ -111,11 +106,9 @@ async function runLocker(argv = process.argv.slice(2)) {
111
106
  writeTextAtomic(journalPath, content);
112
107
  } finally { release(); }
113
108
  }
114
-
115
109
  function encodeJournal(records) {
116
110
  return records.length ? `${records.map((record) => JSON.stringify(record)).join("\n")}\n` : "";
117
111
  }
118
-
119
112
  function emitTrace(kind, summary, data = {}, level = "info") {
120
113
  appendRunTraceEvent(stateDir, {
121
114
  attention: "followup",
@@ -125,11 +118,9 @@ async function runLocker(argv = process.argv.slice(2)) {
125
118
  summary,
126
119
  });
127
120
  }
128
-
129
121
  function now() {
130
122
  return Date.now();
131
123
  }
132
-
133
124
  function cleanExpiredLocks(locks) {
134
125
  const current = now();
135
126
  const kept = {};
@@ -139,7 +130,6 @@ async function runLocker(argv = process.argv.slice(2)) {
139
130
  }
140
131
  return kept;
141
132
  }
142
-
143
133
  function normalizeControl(line) {
144
134
  const trimmed = line.trim();
145
135
  if (!trimmed) return undefined;
@@ -160,8 +150,6 @@ async function runLocker(argv = process.argv.slice(2)) {
160
150
  );
161
151
  }
162
152
  }
163
-
164
-
165
153
  function tailJournal(count) {
166
154
  if (!existsSync(journalPath)) return [];
167
155
  return readFileSync(journalPath, "utf8")
@@ -177,7 +165,6 @@ async function runLocker(argv = process.argv.slice(2)) {
177
165
  }
178
166
  });
179
167
  }
180
-
181
168
  function printSnapshot() {
182
169
  const locks = cleanExpiredLocks(readJson(locksPath, {}));
183
170
  writeJson(locksPath, locks);
@@ -195,7 +182,6 @@ async function runLocker(argv = process.argv.slice(2)) {
195
182
  ),
196
183
  );
197
184
  }
198
-
199
185
  function nextTask(queue, locks) {
200
186
  const items = Array.isArray(queue.items) ? queue.items : [];
201
187
  const index = items.findIndex((item) => {
@@ -205,7 +191,6 @@ async function runLocker(argv = process.argv.slice(2)) {
205
191
  if (index < 0) return undefined;
206
192
  return items.splice(index, 1)[0];
207
193
  }
208
-
209
194
  function handle(control) {
210
195
  const action = control.action;
211
196
  const body =
@@ -333,18 +318,15 @@ async function runLocker(argv = process.argv.slice(2)) {
333
318
  journal("lock.unknown", { action, input: body });
334
319
  emitTrace("lock.unknown", `Unknown Control ${action}`, { action, input: body }, "warning");
335
320
  }
336
-
337
321
  if (mode === "snapshot") {
338
322
  printSnapshot();
339
323
  process.exit(0);
340
324
  }
341
-
342
325
  const startupRun = readJson(join(stateDir, "run.json"), undefined);
343
326
  if (!startupRun || typeof startupRun.run_instance_id !== "string") {
344
327
  throw new Error("Run generation unavailable for Control service");
345
328
  }
346
329
  runInstanceId = startupRun.run_instance_id;
347
-
348
330
  function handleLine(line) {
349
331
  const control = normalizeControl(line);
350
332
  if (!control) return false;
@@ -376,7 +358,6 @@ async function runLocker(argv = process.argv.slice(2)) {
376
358
  return false;
377
359
  }
378
360
  }
379
-
380
361
  async function serveFifo(endpoint) {
381
362
  if (!existsSync(endpoint.path)) {
382
363
  const result = spawnSync("mkfifo", [endpoint.path]);
@@ -403,7 +384,6 @@ async function runLocker(argv = process.argv.slice(2)) {
403
384
  fs.closeSync(fd);
404
385
  }
405
386
  }
406
-
407
387
  async function serveNamedPipe(endpoint) {
408
388
  let resolveStopped;
409
389
  const stopped = new Promise((resolve) => {
@@ -428,7 +408,6 @@ async function runLocker(argv = process.argv.slice(2)) {
428
408
  });
429
409
  await stopped;
430
410
  }
431
-
432
411
  const endpoint = getControlEndpoint();
433
412
  writeJson(queuePath, readJson(queuePath, { items: [] }));
434
413
  writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env node
2
+
3
+ /** Actor-owned Run summary utilities. */
4
+
5
+ // @ts-nocheck
6
+
7
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
8
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
9
+
10
+ function fail(message) {
11
+ console.error(message);
12
+ process.exit(1);
13
+ }
14
+
15
+ function usage() {
16
+ console.error(`Usage:
17
+ run-utils.mjs run-summary <state-root>
18
+ run-utils.mjs run-ops-snapshot <state-root> <run-id> [lines] [stale-minutes]`);
19
+ }
20
+
21
+ function walkFiles(dir, maxDepth = 2, depth = 0, out = []) {
22
+ if (depth > maxDepth || !existsSync(dir)) return out;
23
+ for (const entry of readdirSync(dir)) {
24
+ const path = join(dir, entry);
25
+ const stat = statSync(path);
26
+ if (stat.isDirectory()) walkFiles(path, maxDepth, depth + 1, out);
27
+ else if (stat.isFile()) out.push(path);
28
+ }
29
+ return out;
30
+ }
31
+
32
+ function readJson(file) {
33
+ if (!existsSync(file)) return undefined;
34
+ try {
35
+ return JSON.parse(readFileSync(file, "utf8"));
36
+ } catch {
37
+ return undefined;
38
+ }
39
+ }
40
+
41
+ function getRunStatus(run, progress, result) {
42
+ if (progress?.phase) return progress.phase;
43
+ if (result?.code !== undefined) return result.code === 0 ? "done" : "failed";
44
+ return run.status ?? "unknown";
45
+ }
46
+
47
+ function collectRunSummary(rootValue) {
48
+ const root = resolve(rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
49
+ const files = walkFiles(root, 2).filter((file) => basename(file) === "run.json");
50
+ const rows = [];
51
+ for (const file of files) {
52
+ const run = readJson(file);
53
+ if (!run) {
54
+ rows.push({ run: relative(root, file), status: "invalid-json", recipe: "", updated: "" });
55
+ continue;
56
+ }
57
+ const runDir = dirname(file);
58
+ const progress = readJson(join(runDir, "progress.json"));
59
+ const result = readJson(join(runDir, "result.json"));
60
+ rows.push({
61
+ run: run.run_id ?? run.run ?? relative(root, file).split(sep)[0],
62
+ status: getRunStatus(run, progress, result),
63
+ recipe: run.recipe ?? run.recipe_file ?? "",
64
+ updated:
65
+ progress?.updatedAt ??
66
+ result?.completedAt ??
67
+ run.updated_at ??
68
+ run.completed_at ??
69
+ run.started_at ??
70
+ "",
71
+ });
72
+ }
73
+ rows.sort((a, b) => `${a.status}:${a.run}`.localeCompare(`${b.status}:${b.run}`));
74
+ return rows;
75
+ }
76
+
77
+ function tailJsonl(fileValue, linesValue = "80") {
78
+ const file = resolve(fileValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
79
+ if (!existsSync(file)) return [];
80
+ const lines = Number.parseInt(linesValue, 10);
81
+ const count = Number.isFinite(lines) && lines > 0 ? lines : 80;
82
+ return readFileSync(file, "utf8")
83
+ .trimEnd()
84
+ .split("\n")
85
+ .filter(Boolean)
86
+ .slice(-count)
87
+ .map((line) => {
88
+ try {
89
+ return JSON.parse(line);
90
+ } catch {
91
+ return { raw: line };
92
+ }
93
+ });
94
+ }
95
+
96
+ function runOpsSnapshot(
97
+ rootValue,
98
+ runIdValue = "music",
99
+ linesValue = "80",
100
+ staleMinutesValue = "60",
101
+ ) {
102
+ const runs = collectRunSummary(rootValue);
103
+ const root = resolve(rootValue.replace(/^~(?=\/|$)/, process.env.HOME ?? "~"));
104
+ const inspectedRun = String(runIdValue || "music");
105
+ const staleMs = Number(staleMinutesValue) * 60 * 1000;
106
+ const now = Date.now();
107
+ const recommendations = runs.flatMap((run) => {
108
+ const updatedMs = Date.parse(run.updated || "");
109
+ const stale =
110
+ Number.isFinite(updatedMs) &&
111
+ Number.isFinite(staleMs) &&
112
+ now - updatedMs > staleMs;
113
+ if (run.status === "running" && stale) {
114
+ return [{
115
+ run: run.run,
116
+ reason: "running-stale",
117
+ suggested_inspect: { target: `run:${run.run}`, view: "control" },
118
+ }];
119
+ }
120
+ if (["failed", "exited", "killed"].includes(run.status)) {
121
+ return [{
122
+ run: run.run,
123
+ reason: `terminal-${run.status}`,
124
+ suggested_inspect: { target: `run:${run.run}`, view: "trace" },
125
+ }];
126
+ }
127
+ return [];
128
+ });
129
+ console.log(JSON.stringify({
130
+ runs,
131
+ inspectedRun,
132
+ trace: tailJsonl(join(root, inspectedRun, "trace.jsonl"), linesValue),
133
+ recommendations,
134
+ }, null, 2));
135
+ }
136
+
137
+ function runSummary(rootValue) {
138
+ console.log(JSON.stringify(collectRunSummary(rootValue), null, 2));
139
+ }
140
+
141
+ function run(argv = process.argv.slice(2)) {
142
+ const [command, ...args] = argv;
143
+ if (command === "run-summary") {
144
+ runSummary(args[0] ?? "~/.pi/agent/tmp/pi-actors/runs");
145
+ return;
146
+ }
147
+ if (command === "run-ops-snapshot") {
148
+ runOpsSnapshot(
149
+ args[0] ?? "~/.pi/agent/tmp/pi-actors/runs",
150
+ args[1] ?? "music",
151
+ args[2],
152
+ args[3],
153
+ );
154
+ return;
155
+ }
156
+ usage();
157
+ fail(`Unknown command: ${command ?? ""}`);
158
+ }
159
+
160
+ try {
161
+ run();
162
+ } catch (error) {
163
+ fail(error instanceof Error ? error.message : String(error));
164
+ }
@@ -9,27 +9,49 @@
9
9
 
10
10
  import { existsSync, readdirSync, statSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
- import { dirname, join, resolve } from "node:path";
12
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
13
13
  import { fileURLToPath, pathToFileURL } from "node:url";
14
14
 
15
15
  function packageRoot() {
16
- return dirname(dirname(fileURLToPath(import.meta.url)));
16
+ return dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))));
17
17
  }
18
18
 
19
19
  async function importRuntimeModule(name) {
20
20
  const root = packageRoot();
21
- const compiled = join(root, "dist", "lib", `${name}.js`);
21
+ const localCompiled = join(root, "lib", `${name}.js`);
22
+ const installedCompiled = join(root, "dist", "lib", `${name}.js`);
22
23
  const source = join(root, "lib", `${name}.ts`);
23
- return await import(pathToFileURL(existsSync(compiled) ? compiled : source).href);
24
+ const module = existsSync(localCompiled)
25
+ ? localCompiled
26
+ : root.includes(`${sep}node_modules${sep}`) && existsSync(installedCompiled)
27
+ ? installedCompiled
28
+ : source;
29
+ return await import(pathToFileURL(module).href);
24
30
  }
25
31
 
26
- const { readResolvedRecipeConfig } = await importRuntimeModule("recipes-references");
32
+ const {
33
+ createActiveSkillRecipeContext,
34
+ readRawRecipeConfig,
35
+ readResolvedRecipeConfig,
36
+ } = await importRuntimeModule("recipes-references");
37
+
38
+ const skillsDir = join(packageRoot(), "skills");
39
+ const packageSkillContext = createActiveSkillRecipeContext(
40
+ existsSync(skillsDir)
41
+ ? readdirSync(skillsDir, { withFileTypes: true })
42
+ .filter((entry) => entry.isDirectory())
43
+ .map((entry) => ({
44
+ name: entry.name,
45
+ baseDir: join(skillsDir, entry.name),
46
+ }))
47
+ : [],
48
+ );
27
49
 
28
50
  export function validateRecipeUsage() {
29
51
  return `Usage:
30
- validate-recipe.mjs <recipe-file-or-dir> [--all] [--qa] [--summary]
52
+ validate-recipe.mjs <recipe-file-or-dir> [--all] [--skills] [--qa] [--summary]
31
53
 
32
- Validates one template recipe file, or all *.json/*.md files in a directory when --all is set. Add --qa for packaged-recipe quality checks, where diagnostics and warnings fail validation. Add --summary for compact CLI output.`;
54
+ Validates one template Recipe file or all direct *.json/*.md files in a directory with --all. Use --skills on a Skills root to validate every direct <skill>/recipes component and reject nested files or duplicate stems. Add --qa for shipped-component capability checks, where diagnostics and warnings fail validation. Add --summary for compact CLI output.`;
33
55
  }
34
56
 
35
57
  function expandPath(value) {
@@ -63,6 +85,79 @@ function recipeFiles(target, all) {
63
85
  .map((file) => resolve(target, file));
64
86
  }
65
87
 
88
+ function nestedRecipeFiles(root) {
89
+ return readdirSync(root, { withFileTypes: true }).flatMap((entry) => {
90
+ const path = join(root, entry.name);
91
+ if (entry.isDirectory()) return nestedRecipeFiles(path);
92
+ return entry.isFile() && /\.(?:json|md)$/.test(entry.name) ? [path] : [];
93
+ });
94
+ }
95
+
96
+ function scanSkillRecipes(target) {
97
+ if (!existsSync(target) || !statSync(target).isDirectory())
98
+ throw new Error(`Skills root is not a directory: ${target}`);
99
+ const skills = readdirSync(target, { withFileTypes: true })
100
+ .filter((entry) => entry.isDirectory())
101
+ .sort((a, b) => a.name.localeCompare(b.name));
102
+ const files = [];
103
+ const failures = [];
104
+ for (const skill of skills) {
105
+ const recipeDir = join(target, skill.name, "recipes");
106
+ if (!existsSync(recipeDir)) continue;
107
+ const direct = readdirSync(recipeDir, { withFileTypes: true });
108
+ const byStem = new Map();
109
+ for (const entry of direct) {
110
+ const path = join(recipeDir, entry.name);
111
+ if (entry.isDirectory()) {
112
+ for (const nested of nestedRecipeFiles(path)) {
113
+ failures.push({
114
+ file: nested,
115
+ ok: false,
116
+ error: `Nested Skill Recipe files are not allowed: ${relative(recipeDir, nested)}`,
117
+ });
118
+ }
119
+ continue;
120
+ }
121
+ if (!entry.isFile() || !/\.(?:json|md)$/.test(entry.name)) continue;
122
+ files.push(path);
123
+ const stem = basename(entry.name, extname(entry.name));
124
+ const previous = byStem.get(stem);
125
+ if (previous) {
126
+ failures.push({
127
+ file: path,
128
+ ok: false,
129
+ error: `Skill Recipe stem collision: ${skill.name}/${stem} has both ${extname(previous)} and ${extname(path)} files`,
130
+ });
131
+ } else byStem.set(stem, path);
132
+ }
133
+ }
134
+ let skillContext = packageSkillContext;
135
+ try {
136
+ skillContext = createActiveSkillRecipeContext(
137
+ skills.map((entry) => ({ name: entry.name, baseDir: join(target, entry.name) })),
138
+ );
139
+ } catch (error) {
140
+ failures.push({
141
+ file: target,
142
+ ok: false,
143
+ error: error instanceof Error ? error.message : String(error),
144
+ });
145
+ }
146
+ return {
147
+ files: files.sort((a, b) => a.localeCompare(b)),
148
+ failures,
149
+ skillContext,
150
+ };
151
+ }
152
+
153
+ function collectStrings(value, out = []) {
154
+ if (typeof value === "string") out.push(value);
155
+ else if (Array.isArray(value)) value.forEach((item) => collectStrings(item, out));
156
+ else if (value && typeof value === "object")
157
+ Object.values(value).forEach((item) => collectStrings(item, out));
158
+ return out;
159
+ }
160
+
66
161
  function collectTemplateStrings(value, out = []) {
67
162
  if (typeof value === "string") out.push(value);
68
163
  else if (Array.isArray(value)) value.forEach((item) => collectTemplateStrings(item, out));
@@ -105,24 +200,35 @@ function validateArtifactDeclarations(config) {
105
200
 
106
201
  function validateHelperPaths(file, config) {
107
202
  const diagnostics = [];
203
+ const skillDir = dirname(dirname(file));
108
204
  for (const template of collectTemplateStrings(config.template)) {
109
205
  if (/(^|\s)(?:node\s+)?scripts\/[\w.-]+\.mjs/.test(template))
110
- diagnostics.push("template: helper scripts must be referenced through {repo}/scripts for installed packages");
111
- for (const match of template.matchAll(/\{repo\}\/scripts\/([^\s"']+\.mjs)/g)) {
112
- const scriptPath = join(packageRoot(), "scripts", match[1]);
113
- if (!existsSync(scriptPath))
114
- diagnostics.push(`template: referenced helper script not found: scripts/${match[1]}`);
206
+ diagnostics.push("template: Skill helper scripts must be referenced through {skill_dir}/scripts");
207
+ for (const match of template.matchAll(/\{skill_dir\}\/([^\s"']+)/g)) {
208
+ const target = match[1].replace(/[),;]+$/, "");
209
+ if (!existsSync(join(skillDir, target)))
210
+ diagnostics.push(`template: referenced Skill helper not found: ${target}`);
115
211
  }
116
212
  }
117
213
  return diagnostics;
118
214
  }
119
215
 
216
+ function validatePortablePaths(config) {
217
+ const diagnostics = [];
218
+ for (const value of collectStrings(config)) {
219
+ if (/^(?:\/home\/|\/Users\/|[A-Za-z]:[\\/])/.test(value))
220
+ diagnostics.push(`recipe: must not use a machine-local absolute path: ${value}`);
221
+ }
222
+ return diagnostics;
223
+ }
224
+
120
225
  function qaDiagnostics(file, config) {
121
226
  const diagnostics = [];
122
227
  const warnings = [];
123
228
  if (config.mailbox !== undefined)
124
229
  diagnostics.push("recipe.mailbox was removed; use control actions and Trace events");
125
230
  diagnostics.push(...validateArtifactDeclarations(config));
231
+ diagnostics.push(...validatePortablePaths(config));
126
232
  diagnostics.push(...validateHelperPaths(file, config));
127
233
  const platformSpecificTemplate = collectTemplateStrings(config.template).some(
128
234
  (template) =>
@@ -139,12 +245,16 @@ function qaOk(qaReport) {
139
245
  return qaReport.diagnostics.length === 0 && qaReport.warnings.length === 0;
140
246
  }
141
247
 
142
- function validateFile(file, qa = false) {
248
+ function validateFile(file, qa = false, skillContext = packageSkillContext) {
143
249
  try {
144
- const config = readResolvedRecipeConfig(file);
250
+ const rawConfig = readRawRecipeConfig(file);
251
+ const config = readResolvedRecipeConfig(file, [], { skillContext });
145
252
  if (!config?.template)
146
253
  throw new Error("Recipe must define a non-empty template.");
147
- const qaReport = qa ? qaDiagnostics(file, config) : { diagnostics: [], warnings: [] };
254
+ const expectedName = basename(file, extname(file));
255
+ if (config.name !== expectedName)
256
+ throw new Error(`Recipe identity mismatch: expected ${expectedName}, received ${config.name ?? "<none>"}`);
257
+ const qaReport = qa ? qaDiagnostics(file, rawConfig) : { diagnostics: [], warnings: [] };
148
258
  return {
149
259
  file,
150
260
  ok: qaOk(qaReport),
@@ -183,13 +293,19 @@ function validateFile(file, qa = false) {
183
293
  export function validateRecipes(argv) {
184
294
  const targetArg = argv.find((arg) => !arg.startsWith("-"));
185
295
  const all = argv.includes("--all");
296
+ const skills = argv.includes("--skills");
186
297
  const qa = argv.includes("--qa");
187
298
  if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
188
299
  return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
189
300
  }
190
-
191
- const files = recipeFiles(expandPath(targetArg), all);
192
- const results = files.map((file) => validateFile(file, qa));
301
+ const target = expandPath(targetArg);
302
+ const scan = skills
303
+ ? scanSkillRecipes(target)
304
+ : { files: recipeFiles(target, all), failures: [], skillContext: packageSkillContext };
305
+ const results = [
306
+ ...scan.failures,
307
+ ...scan.files.map((file) => validateFile(file, qa, scan.skillContext)),
308
+ ];
193
309
  const failed = results.filter((result) => !result.ok).length;
194
310
  return {
195
311
  ok: failed === 0,
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: artifacts
3
+ description: Deterministic artifact writing, reporting, manifests, and bundles for reusable actor workflows.
4
+ ---
5
+
6
+ # Artifacts
7
+
8
+ Own reusable capabilities that turn bounded input or prior command output into declared files, reports, manifests, and artifact bundles.
9
+
10
+ ## Scope
11
+
12
+ - Write or assemble caller-declared artifacts with explicit paths and overwrite policy.
13
+ - Produce bounded human-readable reports and machine-readable manifests.
14
+ - Compose validation evidence into artifact outputs without owning the validation policy.
15
+
16
+ This Skill does not own repository workflow, multi-agent review methodology, Run lifecycle, retention policy, or transport delivery. Its Recipe identity is `artifacts/<filename stem>`; Recipe files have no top-level `name`. Cross-capability composition uses exact `<skill>/<recipe>` references; helper-backed Recipes use only this Skill's `scripts/` through `{skill_dir}`.
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "async": true,
3
3
  "imports": {
4
- "artifactWrite": "pipeline-artifact-write.json",
5
- "manifest": "utility-artifact-manifest.json",
6
- "manifestWrite": "utility-artifact-write.json",
7
- "validation": "utility-validation-wrapper.json"
4
+ "artifactWrite": "artifacts/write",
5
+ "manifest": "artifacts/manifest",
6
+ "manifestWrite": "artifacts/file-write",
7
+ "validation": "actors/command-validate"
8
8
  },
9
9
  "args": [
10
10
  "input:string",
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "async": false,
3
3
  "args": [
4
- "repo:path",
5
4
  "artifact_path:path",
6
5
  "mode:enum(create,overwrite,append)"
7
6
  ],
8
7
  "defaults": {
9
- "repo": "~/.pi/agent/extensions/pi-actors",
10
8
  "mode": "create"
11
9
  },
12
10
  "template": {
13
- "template": "{repo}/scripts/recipe-utils.mjs artifact-write {artifact_path} {mode}",
11
+ "template": "{skill_dir}/scripts/artifact-utils.mjs write {artifact_path} {mode}",
14
12
  "output": "stdout"
15
13
  }
16
14
  }
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "args": [
3
- "repo:path",
4
3
  "artifact_path:path",
5
4
  "title:string",
6
5
  "status:enum(draft,ready,blocked,accepted)",
7
6
  "summary:string"
8
7
  ],
9
8
  "defaults": {
10
- "repo": "~/.pi/agent/extensions/pi-actors",
11
9
  "title": "Artifact",
12
10
  "status": "draft",
13
11
  "summary": "Machine-readable artifact manifest."
14
12
  },
15
- "template": "{repo}/scripts/recipe-utils.mjs artifact-manifest {artifact_path} {title} {status} {summary}"
13
+ "template": "{skill_dir}/scripts/artifact-utils.mjs manifest {artifact_path} {title} {status} {summary}"
16
14
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "async": true,
3
3
  "imports": {
4
- "normalizer": "subagent-normalize.json",
5
- "artifact": "subagent-artifact.json"
4
+ "normalizer": "swarm/subagent-normalize",
5
+ "artifact": "swarm/subagent-artifact"
6
6
  },
7
7
  "args": [
8
8
  "input:string",
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "async": true,
3
3
  "imports": {
4
- "normalizer": "subagent-normalize.json",
5
- "artifact": "subagent-artifact.json",
6
- "writer": "utility-artifact-write.json"
4
+ "normalizer": "swarm/subagent-normalize",
5
+ "artifact": "swarm/subagent-artifact",
6
+ "writer": "artifacts/file-write"
7
7
  },
8
8
  "args": [
9
9
  "input:string",