@davidorex/pi-workflows 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/CHANGELOG.md +7 -4
  2. package/README.md +12 -2
  3. package/agents/architecture-designer.agent.yaml +58 -0
  4. package/agents/architecture-inferrer.agent.yaml +60 -0
  5. package/agents/gap-identifier.agent.yaml +66 -0
  6. package/agents/handoff-writer.agent.yaml +64 -0
  7. package/agents/plan-creator.agent.yaml +63 -0
  8. package/agents/project-definer.agent.yaml +53 -0
  9. package/agents/project-inferrer.agent.yaml +63 -0
  10. package/agents/requirements-gatherer.agent.yaml +58 -0
  11. package/dist/agent-spec.d.ts +27 -0
  12. package/dist/agent-spec.d.ts.map +1 -0
  13. package/dist/agent-spec.js +112 -0
  14. package/dist/agent-spec.js.map +1 -0
  15. package/dist/checkpoint.d.ts +50 -0
  16. package/dist/checkpoint.d.ts.map +1 -0
  17. package/dist/checkpoint.js +109 -0
  18. package/dist/checkpoint.js.map +1 -0
  19. package/dist/completion.d.ts +18 -0
  20. package/dist/completion.d.ts.map +1 -0
  21. package/dist/completion.js +58 -0
  22. package/dist/completion.js.map +1 -0
  23. package/dist/dag.d.ts +56 -0
  24. package/dist/dag.d.ts.map +1 -0
  25. package/dist/dag.js +178 -0
  26. package/dist/dag.js.map +1 -0
  27. package/dist/dispatch.d.ts +35 -0
  28. package/dist/dispatch.d.ts.map +1 -0
  29. package/dist/dispatch.js +311 -0
  30. package/dist/dispatch.js.map +1 -0
  31. package/dist/expression.d.ts +64 -0
  32. package/dist/expression.d.ts.map +1 -0
  33. package/dist/expression.js +294 -0
  34. package/dist/expression.js.map +1 -0
  35. package/dist/format.d.ts +19 -0
  36. package/dist/format.d.ts.map +1 -0
  37. package/dist/format.js +42 -0
  38. package/dist/format.js.map +1 -0
  39. package/dist/index.d.ts +8 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +724 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/output.d.ts +12 -0
  44. package/dist/output.d.ts.map +1 -0
  45. package/dist/output.js +59 -0
  46. package/dist/output.js.map +1 -0
  47. package/dist/state.d.ts +66 -0
  48. package/dist/state.d.ts.map +1 -0
  49. package/dist/state.js +227 -0
  50. package/dist/state.js.map +1 -0
  51. package/dist/step-agent.d.ts +35 -0
  52. package/dist/step-agent.d.ts.map +1 -0
  53. package/dist/step-agent.js +171 -0
  54. package/dist/step-agent.js.map +1 -0
  55. package/dist/step-command.d.ts +21 -0
  56. package/dist/step-command.d.ts.map +1 -0
  57. package/dist/step-command.js +150 -0
  58. package/dist/step-command.js.map +1 -0
  59. package/dist/step-foreach.d.ts +33 -0
  60. package/dist/step-foreach.d.ts.map +1 -0
  61. package/dist/step-foreach.js +112 -0
  62. package/dist/step-foreach.js.map +1 -0
  63. package/dist/step-gate.d.ts +18 -0
  64. package/dist/step-gate.d.ts.map +1 -0
  65. package/dist/step-gate.js +137 -0
  66. package/dist/step-gate.js.map +1 -0
  67. package/dist/step-loop.d.ts +32 -0
  68. package/dist/step-loop.d.ts.map +1 -0
  69. package/dist/step-loop.js +278 -0
  70. package/dist/step-loop.js.map +1 -0
  71. package/dist/step-monitor.d.ts +56 -0
  72. package/dist/step-monitor.d.ts.map +1 -0
  73. package/dist/step-monitor.js +317 -0
  74. package/dist/step-monitor.js.map +1 -0
  75. package/dist/step-parallel.d.ts +43 -0
  76. package/dist/step-parallel.d.ts.map +1 -0
  77. package/dist/step-parallel.js +122 -0
  78. package/dist/step-parallel.js.map +1 -0
  79. package/{src/step-pause.ts → dist/step-pause.d.ts} +3 -13
  80. package/dist/step-pause.d.ts.map +1 -0
  81. package/dist/step-pause.js +22 -0
  82. package/dist/step-pause.js.map +1 -0
  83. package/dist/step-shared.d.ts +63 -0
  84. package/dist/step-shared.d.ts.map +1 -0
  85. package/dist/step-shared.js +136 -0
  86. package/dist/step-shared.js.map +1 -0
  87. package/dist/step-transform.d.ts +11 -0
  88. package/dist/step-transform.d.ts.map +1 -0
  89. package/dist/step-transform.js +41 -0
  90. package/dist/step-transform.js.map +1 -0
  91. package/dist/template.d.ts +51 -0
  92. package/dist/template.d.ts.map +1 -0
  93. package/{src/template.ts → dist/template.js} +32 -38
  94. package/dist/template.js.map +1 -0
  95. package/dist/test-helpers.d.ts +17 -0
  96. package/dist/test-helpers.d.ts.map +1 -0
  97. package/dist/test-helpers.js +46 -0
  98. package/dist/test-helpers.js.map +1 -0
  99. package/dist/tui.d.ts +47 -0
  100. package/dist/tui.d.ts.map +1 -0
  101. package/dist/tui.js +143 -0
  102. package/dist/tui.js.map +1 -0
  103. package/dist/types.d.ts +165 -0
  104. package/dist/types.d.ts.map +1 -0
  105. package/dist/types.js +3 -0
  106. package/dist/types.js.map +1 -0
  107. package/dist/workflow-discovery.d.ts +23 -0
  108. package/dist/workflow-discovery.d.ts.map +1 -0
  109. package/dist/workflow-discovery.js +103 -0
  110. package/dist/workflow-discovery.js.map +1 -0
  111. package/dist/workflow-executor.d.ts +50 -0
  112. package/dist/workflow-executor.d.ts.map +1 -0
  113. package/dist/workflow-executor.js +738 -0
  114. package/dist/workflow-executor.js.map +1 -0
  115. package/dist/workflow-sdk.d.ts +44 -0
  116. package/dist/workflow-sdk.d.ts.map +1 -0
  117. package/dist/workflow-sdk.js +457 -0
  118. package/dist/workflow-sdk.js.map +1 -0
  119. package/dist/workflow-spec.d.ts +33 -0
  120. package/dist/workflow-spec.d.ts.map +1 -0
  121. package/dist/workflow-spec.js +476 -0
  122. package/dist/workflow-spec.js.map +1 -0
  123. package/dist/workflows-dir.d.ts +3 -0
  124. package/dist/workflows-dir.d.ts.map +1 -0
  125. package/{src/workflows-dir.ts → dist/workflows-dir.js} +1 -0
  126. package/dist/workflows-dir.js.map +1 -0
  127. package/package.json +53 -38
  128. package/schemas/decomposition-specs.schema.json +48 -48
  129. package/schemas/execution-results.schema.json +48 -48
  130. package/schemas/investigation-findings.schema.json +43 -43
  131. package/schemas/pattern-analysis.schema.json +41 -41
  132. package/schemas/phase.schema.json +48 -0
  133. package/schemas/plan-breakdown.schema.json +20 -20
  134. package/schemas/quality-analysis.schema.json +33 -33
  135. package/schemas/research-findings.schema.json +42 -42
  136. package/schemas/structure-analysis.schema.json +40 -40
  137. package/schemas/synthesis.schema.json +31 -31
  138. package/schemas/verifier-output.schema.json +94 -94
  139. package/skill-narrative.md +107 -0
  140. package/skills/pi-workflows/SKILL.md +185 -0
  141. package/skills/pi-workflows/references/bundled-resources.md +87 -0
  142. package/templates/architecture-designer/task.md +117 -0
  143. package/templates/architecture-inferrer/task.md +61 -0
  144. package/templates/gap-identifier/task.md +103 -0
  145. package/templates/handoff-writer/task.md +91 -0
  146. package/templates/plan-creator/task.md +143 -0
  147. package/templates/project-definer/task.md +67 -0
  148. package/templates/project-inferrer/task.md +56 -0
  149. package/templates/requirements-gatherer/task.md +90 -0
  150. package/workflows/analyze-existing-project.workflow.yaml +94 -0
  151. package/workflows/create-handoff.workflow.yaml +66 -0
  152. package/workflows/create-phase.workflow.yaml +1 -1
  153. package/workflows/do-gap.workflow.yaml +4 -4
  154. package/workflows/fix-audit.workflow.yaml +1 -1
  155. package/workflows/gap-to-phase.workflow.yaml +1 -1
  156. package/workflows/init-new-project.workflow.yaml +81 -0
  157. package/workflows/plan-from-requirements.workflow.yaml +59 -0
  158. package/workflows/self-implement.workflow.yaml +3 -3
  159. package/workflows/typed-analysis.workflow.yaml +4 -4
  160. package/src/agent-spec.test.ts +0 -289
  161. package/src/agent-spec.ts +0 -122
  162. package/src/block-validation.test.ts +0 -350
  163. package/src/checkpoint.test.ts +0 -237
  164. package/src/checkpoint.ts +0 -140
  165. package/src/completion.test.ts +0 -143
  166. package/src/completion.ts +0 -68
  167. package/src/dag.test.ts +0 -350
  168. package/src/dag.ts +0 -219
  169. package/src/dispatch.test.ts +0 -475
  170. package/src/dispatch.ts +0 -353
  171. package/src/expression.test.ts +0 -353
  172. package/src/expression.ts +0 -332
  173. package/src/format.test.ts +0 -80
  174. package/src/format.ts +0 -41
  175. package/src/graduated-failure.test.ts +0 -556
  176. package/src/index.test.ts +0 -114
  177. package/src/index.ts +0 -516
  178. package/src/loop.test.ts +0 -551
  179. package/src/output.test.ts +0 -213
  180. package/src/output.ts +0 -70
  181. package/src/parallel-integration.test.ts +0 -175
  182. package/src/resume.test.ts +0 -192
  183. package/src/state.test.ts +0 -192
  184. package/src/state.ts +0 -254
  185. package/src/step-agent.test.ts +0 -327
  186. package/src/step-agent.ts +0 -178
  187. package/src/step-command.test.ts +0 -330
  188. package/src/step-command.ts +0 -132
  189. package/src/step-foreach.test.ts +0 -647
  190. package/src/step-foreach.ts +0 -148
  191. package/src/step-gate.test.ts +0 -185
  192. package/src/step-gate.ts +0 -116
  193. package/src/step-loop.test.ts +0 -626
  194. package/src/step-loop.ts +0 -323
  195. package/src/step-parallel.test.ts +0 -475
  196. package/src/step-parallel.ts +0 -168
  197. package/src/step-pause.test.ts +0 -30
  198. package/src/step-shared.test.ts +0 -355
  199. package/src/step-shared.ts +0 -157
  200. package/src/step-transform.test.ts +0 -155
  201. package/src/step-transform.ts +0 -47
  202. package/src/template-integration.test.ts +0 -72
  203. package/src/template.test.ts +0 -162
  204. package/src/test-helpers.ts +0 -51
  205. package/src/tui.test.ts +0 -355
  206. package/src/tui.ts +0 -182
  207. package/src/types.ts +0 -195
  208. package/src/verifier-schema.test.ts +0 -226
  209. package/src/workflow-discovery.test.ts +0 -105
  210. package/src/workflow-discovery.ts +0 -114
  211. package/src/workflow-executor.test.ts +0 -1532
  212. package/src/workflow-executor.ts +0 -810
  213. package/src/workflow-sdk.test.ts +0 -238
  214. package/src/workflow-sdk.ts +0 -262
  215. package/src/workflow-spec.test.ts +0 -576
  216. package/src/workflow-spec.ts +0 -471
@@ -1,289 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "node:assert";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import os from "node:os";
6
- import { parseAgentYaml, createAgentLoader, AgentNotFoundError, AgentParseError } from "./agent-spec.ts";
7
- import { compileAgentSpec } from "./step-shared.ts";
8
- import { createTemplateEnv } from "./template.ts";
9
-
10
- describe("parseAgentYaml", () => {
11
- it("parses YAML agent spec with all fields", (t) => {
12
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
13
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
14
-
15
- const specPath = path.join(tmpDir, "test.agent.yaml");
16
- fs.writeFileSync(specPath, `
17
- name: test-agent
18
- role: sensor
19
- description: A test agent
20
- model: anthropic/claude-sonnet-4-6
21
- thinking: low
22
- tools: [read, bash]
23
- extensions: [./ext.ts]
24
- skills: [coding]
25
- input:
26
- type: object
27
- required: [path]
28
- properties:
29
- path: { type: string }
30
- output:
31
- format: json
32
- schema: test.schema.json
33
- file: result.json
34
- prompt:
35
- system: test/system.md
36
- task: test/task.md
37
- `);
38
-
39
- const spec = parseAgentYaml(specPath);
40
- assert.strictEqual(spec.name, "test-agent");
41
- assert.strictEqual(spec.role, "sensor");
42
- assert.strictEqual(spec.description, "A test agent");
43
- assert.strictEqual(spec.model, "anthropic/claude-sonnet-4-6");
44
- assert.strictEqual(spec.thinking, "low");
45
- assert.deepStrictEqual(spec.tools, ["read", "bash"]);
46
- assert.deepStrictEqual(spec.extensions, ["./ext.ts"]);
47
- assert.deepStrictEqual(spec.skills, ["coding"]);
48
- assert.strictEqual(spec.promptTemplate, "test/system.md");
49
- assert.strictEqual(spec.taskTemplate, "test/task.md");
50
- assert.strictEqual(spec.outputFormat, "json");
51
- assert.strictEqual(spec.outputSchema, "test.schema.json");
52
- assert.strictEqual(spec.output, "result.json");
53
- assert.deepStrictEqual(spec.inputSchema?.required, ["path"]);
54
- });
55
-
56
- it("uses filename as name when name field is missing", (t) => {
57
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
58
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
59
-
60
- const specPath = path.join(tmpDir, "my-agent.agent.yaml");
61
- fs.writeFileSync(specPath, "tools: [read]\n");
62
-
63
- const spec = parseAgentYaml(specPath);
64
- assert.strictEqual(spec.name, "my-agent");
65
- });
66
-
67
- it("throws AgentParseError for malformed YAML", (t) => {
68
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
69
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
70
-
71
- const specPath = path.join(tmpDir, "bad.agent.yaml");
72
- fs.writeFileSync(specPath, `
73
- name: bad-agent
74
- tools: [read
75
- this is not valid yaml: {{{}}}
76
- `);
77
-
78
- assert.throws(
79
- () => parseAgentYaml(specPath),
80
- (err: any) => {
81
- assert.strictEqual(err.name, "AgentParseError");
82
- assert.strictEqual(err.agentName, "bad");
83
- assert.strictEqual(err.filePath, specPath);
84
- assert.ok(err.message.includes("bad"), "error message should include agent name");
85
- assert.ok(err.message.includes(specPath), "error message should include file path");
86
- return true;
87
- },
88
- );
89
- });
90
-
91
- it("throws AgentParseError for empty file", (t) => {
92
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
93
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
94
-
95
- const specPath = path.join(tmpDir, "empty.agent.yaml");
96
- fs.writeFileSync(specPath, "");
97
-
98
- assert.throws(
99
- () => parseAgentYaml(specPath),
100
- (err: any) => {
101
- assert.strictEqual(err.name, "AgentParseError");
102
- assert.ok(err.message.includes("empty") || err.message.includes("does not contain"));
103
- return true;
104
- },
105
- );
106
- });
107
-
108
- it("throws AgentParseError for file with only document markers", (t) => {
109
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
110
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
111
-
112
- const specPath = path.join(tmpDir, "marker.agent.yaml");
113
- fs.writeFileSync(specPath, "---\n---\n");
114
-
115
- assert.throws(
116
- () => parseAgentYaml(specPath),
117
- (err: any) => {
118
- assert.strictEqual(err.name, "AgentParseError");
119
- return true;
120
- },
121
- );
122
- });
123
-
124
- it("throws AgentParseError for scalar YAML content", (t) => {
125
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
126
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
127
-
128
- const specPath = path.join(tmpDir, "scalar.agent.yaml");
129
- fs.writeFileSync(specPath, "just a string, not a mapping\n");
130
-
131
- assert.throws(
132
- () => parseAgentYaml(specPath),
133
- (err: any) => {
134
- assert.strictEqual(err.name, "AgentParseError");
135
- assert.ok(err.message.includes("does not contain a YAML mapping"));
136
- return true;
137
- },
138
- );
139
- });
140
- });
141
-
142
- describe("createAgentLoader", () => {
143
- it("finds .agent.yaml specs in .pi/agents/", (t) => {
144
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
145
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
146
-
147
- const agentDir = path.join(tmpDir, ".pi", "agents");
148
- fs.mkdirSync(agentDir, { recursive: true });
149
- fs.writeFileSync(path.join(agentDir, "test.agent.yaml"), "name: test\nrole: sensor\ntools: [read]\n");
150
-
151
- const loader = createAgentLoader(tmpDir);
152
- const spec = loader("test");
153
- assert.strictEqual(spec.name, "test");
154
- assert.strictEqual(spec.role, "sensor");
155
- assert.deepStrictEqual(spec.tools, ["read"]);
156
- });
157
-
158
- it("finds specs in builtin directory", (t) => {
159
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
160
- const builtinDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-builtin-"));
161
- t.after(() => {
162
- fs.rmSync(tmpDir, { recursive: true, force: true });
163
- fs.rmSync(builtinDir, { recursive: true, force: true });
164
- });
165
-
166
- fs.writeFileSync(path.join(builtinDir, "builtin.agent.yaml"), "name: builtin\nrole: quality\n");
167
-
168
- const loader = createAgentLoader(tmpDir, builtinDir);
169
- const spec = loader("builtin");
170
- assert.strictEqual(spec.name, "builtin");
171
- assert.strictEqual(spec.role, "quality");
172
- });
173
-
174
- it("throws AgentNotFoundError for missing agent", (t) => {
175
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
176
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
177
-
178
- const loader = createAgentLoader(tmpDir, tmpDir); // builtinDir also empty
179
- assert.throws(
180
- () => loader("nonexistent-agent"),
181
- (err: any) => {
182
- assert.strictEqual(err.name, "AgentNotFoundError");
183
- assert.strictEqual(err.agentName, "nonexistent-agent");
184
- assert.ok(Array.isArray(err.searchPaths));
185
- assert.ok(err.searchPaths.length >= 2, "should list at least project and user search paths");
186
- assert.ok(err.message.includes("nonexistent-agent"), "error message should include agent name");
187
- assert.ok(err.message.includes("Searched"), "error message should indicate search was performed");
188
- return true;
189
- },
190
- );
191
- });
192
-
193
- it("throws AgentParseError when found file has invalid YAML", (t) => {
194
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-agent-"));
195
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
196
-
197
- const agentDir = path.join(tmpDir, ".pi", "agents");
198
- fs.mkdirSync(agentDir, { recursive: true });
199
- fs.writeFileSync(path.join(agentDir, "broken.agent.yaml"), "name: broken\ntools: [read\n");
200
-
201
- const loader = createAgentLoader(tmpDir, tmpDir);
202
- assert.throws(
203
- () => loader("broken"),
204
- (err: any) => {
205
- assert.strictEqual(err.name, "AgentParseError");
206
- assert.strictEqual(err.agentName, "broken");
207
- assert.ok(err.filePath.includes("broken.agent.yaml"));
208
- return true;
209
- },
210
- );
211
- });
212
- });
213
-
214
- describe("compileAgentSpec", () => {
215
- it("renders plain text system prompt unchanged", () => {
216
- const env = createTemplateEnv("/nonexistent");
217
- const spec = { name: "test", systemPrompt: "Plain text prompt." };
218
- const result = compileAgentSpec(spec, {}, env);
219
- assert.strictEqual(result.systemPrompt, "Plain text prompt.");
220
- });
221
-
222
- it("renders system prompt with template variables", () => {
223
- const env = createTemplateEnv("/nonexistent");
224
- const spec = { name: "test", systemPrompt: "Analyze {{ path }} for {{ concern }}." };
225
- const result = compileAgentSpec(spec, { path: "src/index.ts", concern: "security" }, env);
226
- assert.strictEqual(result.systemPrompt, "Analyze src/index.ts for security.");
227
- });
228
-
229
- it("renders system prompt from file template", (t) => {
230
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-tmpl-"));
231
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
232
-
233
- fs.writeFileSync(path.join(tmpDir, "system.md"), "You analyze {{ focus }} code.");
234
-
235
- const env = createTemplateEnv("/nonexistent", tmpDir);
236
- const spec = { name: "test", promptTemplate: "system.md" };
237
- const result = compileAgentSpec(spec, { focus: "TypeScript" }, env);
238
- assert.strictEqual(result.systemPrompt, "You analyze TypeScript code.");
239
- assert.strictEqual(result.promptTemplate, undefined);
240
- });
241
-
242
- it("renders task template from typed input", (t) => {
243
- const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-tmpl-"));
244
- t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
245
-
246
- fs.writeFileSync(path.join(tmpDir, "task.md"), "Fix {{ diagnosis.rootCause }} in {{ diagnosis.file }}.");
247
-
248
- const env = createTemplateEnv("/nonexistent", tmpDir);
249
- const spec = { name: "test", taskTemplate: "task.md" };
250
- const result = compileAgentSpec(spec, { diagnosis: { rootCause: "null ref", file: "auth.ts" } }, env);
251
- assert.strictEqual(result.taskTemplate, "Fix null ref in auth.ts.");
252
- });
253
-
254
- it("returns spec unchanged when no templateEnv provided", () => {
255
- const spec = { name: "test", systemPrompt: "Hello {{ name }}", taskTemplate: "task.md" };
256
- const result = compileAgentSpec(spec, { name: "world" });
257
- assert.strictEqual(result.systemPrompt, "Hello {{ name }}");
258
- assert.strictEqual(result.taskTemplate, "task.md");
259
- });
260
- });
261
-
262
- describe("verifier agent spec", () => {
263
- const builtinDir = path.resolve(import.meta.dirname, "..", "agents");
264
-
265
- it("parses verifier.agent.yaml correctly", () => {
266
- const specPath = path.join(builtinDir, "verifier.agent.yaml");
267
- const spec = parseAgentYaml(specPath);
268
- assert.strictEqual(spec.name, "verifier");
269
- assert.strictEqual(spec.role, "quality");
270
- assert.strictEqual(spec.description, "Verifies step outputs against declared intent and success criteria");
271
- assert.strictEqual(spec.model, undefined); // model comes from .project/model-config.json, not agent spec
272
- assert.strictEqual(spec.outputFormat, "json");
273
- assert.strictEqual(spec.outputSchema, "schemas/verifier-output.schema.json");
274
- assert.strictEqual(spec.taskTemplate, "verifier/task.md");
275
- });
276
-
277
- it("has correct tools for verification", () => {
278
- const specPath = path.join(builtinDir, "verifier.agent.yaml");
279
- const spec = parseAgentYaml(specPath);
280
- assert.deepStrictEqual(spec.tools, ["read", "bash", "grep", "find"]);
281
- });
282
-
283
- it("output schema path resolves to a valid file", () => {
284
- const specPath = path.join(builtinDir, "verifier.agent.yaml");
285
- const spec = parseAgentYaml(specPath);
286
- const schemaPath = path.resolve(path.dirname(specPath), "..", "schemas", spec.outputSchema!.replace("schemas/", ""));
287
- assert.ok(fs.existsSync(schemaPath), `Schema file not found at ${schemaPath}`);
288
- });
289
- });
package/src/agent-spec.ts DELETED
@@ -1,122 +0,0 @@
1
- /**
2
- * Agent spec loading — YAML specs are the source of truth.
3
- *
4
- * Agent specs are declarative YAML files that define typed functions:
5
- * InputSchema → OutputSchema, with template references for prompt
6
- * composition. The .md that pi consumes is compiled at dispatch time
7
- * from spec + templates + typed input. It exists in memory only.
8
- *
9
- * Search order (first match wins):
10
- * 1. .pi/agents/<name>.agent.yaml (project)
11
- * 2. ~/.pi/agent/agents/<name>.agent.yaml (user)
12
- * 3. <package>/agents/<name>.agent.yaml (builtin)
13
- */
14
- import fs from "node:fs";
15
- import path from "node:path";
16
-
17
- /** Check if a prompt.system value looks like a template file path vs inline text. */
18
- function isTemplatePath(value: string | undefined): boolean {
19
- if (!value) return false;
20
- return value.endsWith(".md") || value.endsWith(".txt") || (value.includes("/") && !value.includes("\n"));
21
- }
22
- import os from "node:os";
23
- import { parse as parseYaml } from "yaml";
24
- import type { AgentSpec } from "./types.ts";
25
-
26
- /**
27
- * Thrown when an agent spec file is not found in any search path.
28
- */
29
- export class AgentNotFoundError extends Error {
30
- public readonly agentName: string;
31
- public readonly searchPaths: string[];
32
-
33
- constructor(agentName: string, searchPaths: string[]) {
34
- const pathList = searchPaths.map(p => ` - ${p}`).join("\n");
35
- super(`Agent '${agentName}' not found. Searched:\n${pathList}`);
36
- this.name = "AgentNotFoundError";
37
- this.agentName = agentName;
38
- this.searchPaths = searchPaths;
39
- }
40
- }
41
-
42
- /**
43
- * Thrown when an agent spec file exists but cannot be read or parsed.
44
- */
45
- export class AgentParseError extends Error {
46
- public readonly agentName: string;
47
- public readonly filePath: string;
48
- public readonly cause: Error;
49
-
50
- constructor(agentName: string, filePath: string, cause: Error) {
51
- super(`Agent '${agentName}' at ${filePath}: ${cause.message}`);
52
- this.name = "AgentParseError";
53
- this.agentName = agentName;
54
- this.filePath = filePath;
55
- this.cause = cause;
56
- }
57
- }
58
-
59
- /**
60
- * Parse a YAML agent spec file into an AgentSpec.
61
- */
62
- export function parseAgentYaml(filePath: string): AgentSpec {
63
- const name = path.basename(filePath, ".agent.yaml");
64
-
65
- let content: string;
66
- try {
67
- content = fs.readFileSync(filePath, "utf-8");
68
- } catch (err) {
69
- throw new AgentParseError(name, filePath, err instanceof Error ? err : new Error(String(err)));
70
- }
71
-
72
- let spec: any;
73
- try {
74
- spec = parseYaml(content);
75
- } catch (err) {
76
- throw new AgentParseError(name, filePath, err instanceof Error ? err : new Error(String(err)));
77
- }
78
-
79
- // Handle null/undefined from parsing empty file or non-mapping YAML
80
- if (!spec || typeof spec !== "object") {
81
- throw new AgentParseError(name, filePath, new Error("File is empty or does not contain a YAML mapping"));
82
- }
83
-
84
- return {
85
- name: spec.name || name,
86
- description: spec.description,
87
- role: spec.role,
88
- model: spec.model,
89
- thinking: spec.thinking,
90
- tools: spec.tools,
91
- extensions: spec.extensions,
92
- skills: spec.skills,
93
- output: spec.output?.file,
94
- promptTemplate: isTemplatePath(spec.prompt?.system) ? spec.prompt?.system : undefined,
95
- systemPrompt: isTemplatePath(spec.prompt?.system) ? undefined : spec.prompt?.system,
96
- taskTemplate: spec.prompt?.task,
97
- inputSchema: spec.input,
98
- outputFormat: spec.output?.format,
99
- outputSchema: spec.output?.schema,
100
- };
101
- }
102
-
103
- /**
104
- * Create an agent loader that finds .agent.yaml specs.
105
- */
106
- export function createAgentLoader(cwd: string, builtinDir?: string): (name: string) => AgentSpec {
107
- const defaultBuiltinDir = builtinDir ?? path.resolve(import.meta.dirname, "..", "agents");
108
-
109
- return (name: string): AgentSpec => {
110
- const searchPaths = [
111
- path.join(cwd, ".pi", "agents", `${name}.agent.yaml`),
112
- path.join(os.homedir(), ".pi", "agent", "agents", `${name}.agent.yaml`),
113
- path.join(defaultBuiltinDir, `${name}.agent.yaml`),
114
- ];
115
-
116
- for (const p of searchPaths) {
117
- if (fs.existsSync(p)) return parseAgentYaml(p);
118
- }
119
-
120
- throw new AgentNotFoundError(name, searchPaths);
121
- };
122
- }