@davidorex/pi-workflows 0.2.0 → 0.3.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 (215) 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 +172 -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 +675 -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 +156 -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 +57 -0
  84. package/dist/step-shared.d.ts.map +1 -0
  85. package/dist/step-shared.js +125 -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 +164 -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 +726 -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 +438 -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 +464 -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 +84 -0
  140. package/skills/pi-workflows/SKILL.md +247 -0
  141. package/templates/architecture-designer/task.md +117 -0
  142. package/templates/architecture-inferrer/task.md +61 -0
  143. package/templates/gap-identifier/task.md +103 -0
  144. package/templates/handoff-writer/task.md +91 -0
  145. package/templates/plan-creator/task.md +143 -0
  146. package/templates/project-definer/task.md +67 -0
  147. package/templates/project-inferrer/task.md +56 -0
  148. package/templates/requirements-gatherer/task.md +90 -0
  149. package/workflows/analyze-existing-project.workflow.yaml +74 -0
  150. package/workflows/create-handoff.workflow.yaml +58 -0
  151. package/workflows/create-phase.workflow.yaml +1 -1
  152. package/workflows/do-gap.workflow.yaml +4 -4
  153. package/workflows/fix-audit.workflow.yaml +1 -1
  154. package/workflows/gap-to-phase.workflow.yaml +1 -1
  155. package/workflows/init-new-project.workflow.yaml +55 -0
  156. package/workflows/plan-from-requirements.workflow.yaml +51 -0
  157. package/workflows/self-implement.workflow.yaml +3 -3
  158. package/workflows/typed-analysis.workflow.yaml +4 -4
  159. package/src/agent-spec.test.ts +0 -289
  160. package/src/agent-spec.ts +0 -122
  161. package/src/block-validation.test.ts +0 -350
  162. package/src/checkpoint.test.ts +0 -237
  163. package/src/checkpoint.ts +0 -140
  164. package/src/completion.test.ts +0 -143
  165. package/src/completion.ts +0 -68
  166. package/src/dag.test.ts +0 -350
  167. package/src/dag.ts +0 -219
  168. package/src/dispatch.test.ts +0 -475
  169. package/src/dispatch.ts +0 -353
  170. package/src/expression.test.ts +0 -353
  171. package/src/expression.ts +0 -332
  172. package/src/format.test.ts +0 -80
  173. package/src/format.ts +0 -41
  174. package/src/graduated-failure.test.ts +0 -556
  175. package/src/index.test.ts +0 -114
  176. package/src/index.ts +0 -516
  177. package/src/loop.test.ts +0 -551
  178. package/src/output.test.ts +0 -213
  179. package/src/output.ts +0 -70
  180. package/src/parallel-integration.test.ts +0 -175
  181. package/src/resume.test.ts +0 -192
  182. package/src/state.test.ts +0 -192
  183. package/src/state.ts +0 -254
  184. package/src/step-agent.test.ts +0 -327
  185. package/src/step-agent.ts +0 -178
  186. package/src/step-command.test.ts +0 -330
  187. package/src/step-command.ts +0 -132
  188. package/src/step-foreach.test.ts +0 -647
  189. package/src/step-foreach.ts +0 -148
  190. package/src/step-gate.test.ts +0 -185
  191. package/src/step-gate.ts +0 -116
  192. package/src/step-loop.test.ts +0 -626
  193. package/src/step-loop.ts +0 -323
  194. package/src/step-parallel.test.ts +0 -475
  195. package/src/step-parallel.ts +0 -168
  196. package/src/step-pause.test.ts +0 -30
  197. package/src/step-shared.test.ts +0 -355
  198. package/src/step-shared.ts +0 -157
  199. package/src/step-transform.test.ts +0 -155
  200. package/src/step-transform.ts +0 -47
  201. package/src/template-integration.test.ts +0 -72
  202. package/src/template.test.ts +0 -162
  203. package/src/test-helpers.ts +0 -51
  204. package/src/tui.test.ts +0 -355
  205. package/src/tui.ts +0 -182
  206. package/src/types.ts +0 -195
  207. package/src/verifier-schema.test.ts +0 -226
  208. package/src/workflow-discovery.test.ts +0 -105
  209. package/src/workflow-discovery.ts +0 -114
  210. package/src/workflow-executor.test.ts +0 -1532
  211. package/src/workflow-executor.ts +0 -810
  212. package/src/workflow-sdk.test.ts +0 -238
  213. package/src/workflow-sdk.ts +0 -262
  214. package/src/workflow-spec.test.ts +0 -576
  215. package/src/workflow-spec.ts +0 -471
@@ -26,7 +26,7 @@ steps:
26
26
  path: ${{ input.path }}
27
27
  output:
28
28
  format: json
29
- schema: schemas/structure-analysis.schema.json
29
+ schema: ../schemas/structure-analysis.schema.json
30
30
 
31
31
  analyze-quality:
32
32
  agent: quality-analyzer
@@ -35,7 +35,7 @@ steps:
35
35
  path: ${{ input.path }}
36
36
  output:
37
37
  format: json
38
- schema: schemas/quality-analysis.schema.json
38
+ schema: ../schemas/quality-analysis.schema.json
39
39
 
40
40
  analyze-patterns:
41
41
  agent: pattern-analyzer
@@ -44,7 +44,7 @@ steps:
44
44
  path: ${{ input.path }}
45
45
  output:
46
46
  format: json
47
- schema: schemas/pattern-analysis.schema.json
47
+ schema: ../schemas/pattern-analysis.schema.json
48
48
 
49
49
  synthesize:
50
50
  agent: synthesizer
@@ -54,7 +54,7 @@ steps:
54
54
  patterns: ${{ steps.analyze-patterns.output }}
55
55
  output:
56
56
  format: json
57
- schema: schemas/synthesis.schema.json
57
+ schema: ../schemas/synthesis.schema.json
58
58
 
59
59
  completion:
60
60
  message: |
@@ -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
- }