popeye-cli 1.10.0 → 2.1.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 (326) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/CONTRIBUTING.md +38 -3
  3. package/README.md +104 -18
  4. package/dist/adapters/gemini.js +3 -3
  5. package/dist/adapters/openai.js +2 -2
  6. package/dist/adapters/openai.js.map +1 -1
  7. package/dist/auth/gemini.js +1 -1
  8. package/dist/cli/commands/create.d.ts.map +1 -1
  9. package/dist/cli/commands/create.js +11 -5
  10. package/dist/cli/commands/create.js.map +1 -1
  11. package/dist/cli/commands/resume.d.ts.map +1 -1
  12. package/dist/cli/commands/resume.js +9 -1
  13. package/dist/cli/commands/resume.js.map +1 -1
  14. package/dist/cli/interactive.d.ts.map +1 -1
  15. package/dist/cli/interactive.js +29 -3
  16. package/dist/cli/interactive.js.map +1 -1
  17. package/dist/config/defaults.d.ts.map +1 -1
  18. package/dist/config/defaults.js +7 -2
  19. package/dist/config/defaults.js.map +1 -1
  20. package/dist/config/index.d.ts +1 -7
  21. package/dist/config/index.d.ts.map +1 -1
  22. package/dist/config/popeye-md.d.ts +32 -0
  23. package/dist/config/popeye-md.d.ts.map +1 -0
  24. package/dist/config/popeye-md.js +111 -0
  25. package/dist/config/popeye-md.js.map +1 -0
  26. package/dist/config/schema.d.ts +3 -21
  27. package/dist/config/schema.d.ts.map +1 -1
  28. package/dist/config/schema.js +21 -8
  29. package/dist/config/schema.js.map +1 -1
  30. package/dist/pipeline/artifact-manager.d.ts +47 -0
  31. package/dist/pipeline/artifact-manager.d.ts.map +1 -0
  32. package/dist/pipeline/artifact-manager.js +251 -0
  33. package/dist/pipeline/artifact-manager.js.map +1 -0
  34. package/dist/pipeline/artifact-validators.d.ts +29 -0
  35. package/dist/pipeline/artifact-validators.d.ts.map +1 -0
  36. package/dist/pipeline/artifact-validators.js +173 -0
  37. package/dist/pipeline/artifact-validators.js.map +1 -0
  38. package/dist/pipeline/bridges/review-bridge.d.ts +70 -0
  39. package/dist/pipeline/bridges/review-bridge.d.ts.map +1 -0
  40. package/dist/pipeline/bridges/review-bridge.js +266 -0
  41. package/dist/pipeline/bridges/review-bridge.js.map +1 -0
  42. package/dist/pipeline/change-request.d.ts +47 -0
  43. package/dist/pipeline/change-request.d.ts.map +1 -0
  44. package/dist/pipeline/change-request.js +91 -0
  45. package/dist/pipeline/change-request.js.map +1 -0
  46. package/dist/pipeline/check-runner.d.ts +47 -0
  47. package/dist/pipeline/check-runner.d.ts.map +1 -0
  48. package/dist/pipeline/check-runner.js +417 -0
  49. package/dist/pipeline/check-runner.js.map +1 -0
  50. package/dist/pipeline/command-resolver.d.ts +9 -0
  51. package/dist/pipeline/command-resolver.d.ts.map +1 -0
  52. package/dist/pipeline/command-resolver.js +140 -0
  53. package/dist/pipeline/command-resolver.js.map +1 -0
  54. package/dist/pipeline/consensus/consensus-runner.d.ts +44 -0
  55. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -0
  56. package/dist/pipeline/consensus/consensus-runner.js +212 -0
  57. package/dist/pipeline/consensus/consensus-runner.js.map +1 -0
  58. package/dist/pipeline/constitution.d.ts +45 -0
  59. package/dist/pipeline/constitution.d.ts.map +1 -0
  60. package/dist/pipeline/constitution.js +82 -0
  61. package/dist/pipeline/constitution.js.map +1 -0
  62. package/dist/pipeline/gate-engine.d.ts +55 -0
  63. package/dist/pipeline/gate-engine.d.ts.map +1 -0
  64. package/dist/pipeline/gate-engine.js +270 -0
  65. package/dist/pipeline/gate-engine.js.map +1 -0
  66. package/dist/pipeline/index.d.ts +26 -0
  67. package/dist/pipeline/index.d.ts.map +1 -0
  68. package/dist/pipeline/index.js +35 -0
  69. package/dist/pipeline/index.js.map +1 -0
  70. package/dist/pipeline/migration.d.ts +15 -0
  71. package/dist/pipeline/migration.d.ts.map +1 -0
  72. package/dist/pipeline/migration.js +76 -0
  73. package/dist/pipeline/migration.js.map +1 -0
  74. package/dist/pipeline/orchestrator.d.ts +30 -0
  75. package/dist/pipeline/orchestrator.d.ts.map +1 -0
  76. package/dist/pipeline/orchestrator.js +242 -0
  77. package/dist/pipeline/orchestrator.js.map +1 -0
  78. package/dist/pipeline/packets/audit-report-builder.d.ts +11 -0
  79. package/dist/pipeline/packets/audit-report-builder.d.ts.map +1 -0
  80. package/dist/pipeline/packets/audit-report-builder.js +32 -0
  81. package/dist/pipeline/packets/audit-report-builder.js.map +1 -0
  82. package/dist/pipeline/packets/consensus-packet-builder.d.ts +35 -0
  83. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -0
  84. package/dist/pipeline/packets/consensus-packet-builder.js +80 -0
  85. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -0
  86. package/dist/pipeline/packets/index.d.ts +12 -0
  87. package/dist/pipeline/packets/index.d.ts.map +1 -0
  88. package/dist/pipeline/packets/index.js +8 -0
  89. package/dist/pipeline/packets/index.js.map +1 -0
  90. package/dist/pipeline/packets/plan-packet-builder.d.ts +21 -0
  91. package/dist/pipeline/packets/plan-packet-builder.d.ts.map +1 -0
  92. package/dist/pipeline/packets/plan-packet-builder.js +27 -0
  93. package/dist/pipeline/packets/plan-packet-builder.js.map +1 -0
  94. package/dist/pipeline/packets/rca-packet-builder.d.ts +19 -0
  95. package/dist/pipeline/packets/rca-packet-builder.d.ts.map +1 -0
  96. package/dist/pipeline/packets/rca-packet-builder.js +22 -0
  97. package/dist/pipeline/packets/rca-packet-builder.js.map +1 -0
  98. package/dist/pipeline/phases/architecture.d.ts +7 -0
  99. package/dist/pipeline/phases/architecture.d.ts.map +1 -0
  100. package/dist/pipeline/phases/architecture.js +60 -0
  101. package/dist/pipeline/phases/architecture.js.map +1 -0
  102. package/dist/pipeline/phases/audit.d.ts +8 -0
  103. package/dist/pipeline/phases/audit.d.ts.map +1 -0
  104. package/dist/pipeline/phases/audit.js +144 -0
  105. package/dist/pipeline/phases/audit.js.map +1 -0
  106. package/dist/pipeline/phases/consensus-architecture.d.ts +7 -0
  107. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -0
  108. package/dist/pipeline/phases/consensus-architecture.js +84 -0
  109. package/dist/pipeline/phases/consensus-architecture.js.map +1 -0
  110. package/dist/pipeline/phases/consensus-master-plan.d.ts +7 -0
  111. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -0
  112. package/dist/pipeline/phases/consensus-master-plan.js +81 -0
  113. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -0
  114. package/dist/pipeline/phases/consensus-role-plans.d.ts +7 -0
  115. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -0
  116. package/dist/pipeline/phases/consensus-role-plans.js +85 -0
  117. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -0
  118. package/dist/pipeline/phases/done.d.ts +7 -0
  119. package/dist/pipeline/phases/done.d.ts.map +1 -0
  120. package/dist/pipeline/phases/done.js +45 -0
  121. package/dist/pipeline/phases/done.js.map +1 -0
  122. package/dist/pipeline/phases/implementation.d.ts +8 -0
  123. package/dist/pipeline/phases/implementation.d.ts.map +1 -0
  124. package/dist/pipeline/phases/implementation.js +45 -0
  125. package/dist/pipeline/phases/implementation.js.map +1 -0
  126. package/dist/pipeline/phases/index.d.ts +20 -0
  127. package/dist/pipeline/phases/index.d.ts.map +1 -0
  128. package/dist/pipeline/phases/index.js +19 -0
  129. package/dist/pipeline/phases/index.js.map +1 -0
  130. package/dist/pipeline/phases/intake.d.ts +8 -0
  131. package/dist/pipeline/phases/intake.d.ts.map +1 -0
  132. package/dist/pipeline/phases/intake.js +49 -0
  133. package/dist/pipeline/phases/intake.js.map +1 -0
  134. package/dist/pipeline/phases/phase-context.d.ts +30 -0
  135. package/dist/pipeline/phases/phase-context.d.ts.map +1 -0
  136. package/dist/pipeline/phases/phase-context.js +33 -0
  137. package/dist/pipeline/phases/phase-context.js.map +1 -0
  138. package/dist/pipeline/phases/production-gate.d.ts +8 -0
  139. package/dist/pipeline/phases/production-gate.d.ts.map +1 -0
  140. package/dist/pipeline/phases/production-gate.js +84 -0
  141. package/dist/pipeline/phases/production-gate.js.map +1 -0
  142. package/dist/pipeline/phases/qa-validation.d.ts +7 -0
  143. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -0
  144. package/dist/pipeline/phases/qa-validation.js +50 -0
  145. package/dist/pipeline/phases/qa-validation.js.map +1 -0
  146. package/dist/pipeline/phases/recovery-loop.d.ts +7 -0
  147. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -0
  148. package/dist/pipeline/phases/recovery-loop.js +93 -0
  149. package/dist/pipeline/phases/recovery-loop.js.map +1 -0
  150. package/dist/pipeline/phases/review.d.ts +8 -0
  151. package/dist/pipeline/phases/review.d.ts.map +1 -0
  152. package/dist/pipeline/phases/review.js +127 -0
  153. package/dist/pipeline/phases/review.js.map +1 -0
  154. package/dist/pipeline/phases/role-planning.d.ts +7 -0
  155. package/dist/pipeline/phases/role-planning.d.ts.map +1 -0
  156. package/dist/pipeline/phases/role-planning.js +75 -0
  157. package/dist/pipeline/phases/role-planning.js.map +1 -0
  158. package/dist/pipeline/phases/stuck.d.ts +7 -0
  159. package/dist/pipeline/phases/stuck.d.ts.map +1 -0
  160. package/dist/pipeline/phases/stuck.js +51 -0
  161. package/dist/pipeline/phases/stuck.js.map +1 -0
  162. package/dist/pipeline/repo-snapshot.d.ts +24 -0
  163. package/dist/pipeline/repo-snapshot.d.ts.map +1 -0
  164. package/dist/pipeline/repo-snapshot.js +343 -0
  165. package/dist/pipeline/repo-snapshot.js.map +1 -0
  166. package/dist/pipeline/role-execution-adapter.d.ts +59 -0
  167. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -0
  168. package/dist/pipeline/role-execution-adapter.js +159 -0
  169. package/dist/pipeline/role-execution-adapter.js.map +1 -0
  170. package/dist/pipeline/skill-loader.d.ts +34 -0
  171. package/dist/pipeline/skill-loader.d.ts.map +1 -0
  172. package/dist/pipeline/skill-loader.js +156 -0
  173. package/dist/pipeline/skill-loader.js.map +1 -0
  174. package/dist/pipeline/skills/defaults.d.ts +16 -0
  175. package/dist/pipeline/skills/defaults.d.ts.map +1 -0
  176. package/dist/pipeline/skills/defaults.js +189 -0
  177. package/dist/pipeline/skills/defaults.js.map +1 -0
  178. package/dist/pipeline/type-defs/artifacts.d.ts +207 -0
  179. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -0
  180. package/dist/pipeline/type-defs/artifacts.js +67 -0
  181. package/dist/pipeline/type-defs/artifacts.js.map +1 -0
  182. package/dist/pipeline/type-defs/audit.d.ts +259 -0
  183. package/dist/pipeline/type-defs/audit.d.ts.map +1 -0
  184. package/dist/pipeline/type-defs/audit.js +54 -0
  185. package/dist/pipeline/type-defs/audit.js.map +1 -0
  186. package/dist/pipeline/type-defs/checks.d.ts +82 -0
  187. package/dist/pipeline/type-defs/checks.d.ts.map +1 -0
  188. package/dist/pipeline/type-defs/checks.js +38 -0
  189. package/dist/pipeline/type-defs/checks.js.map +1 -0
  190. package/dist/pipeline/type-defs/enums.d.ts +43 -0
  191. package/dist/pipeline/type-defs/enums.d.ts.map +1 -0
  192. package/dist/pipeline/type-defs/enums.js +55 -0
  193. package/dist/pipeline/type-defs/enums.js.map +1 -0
  194. package/dist/pipeline/type-defs/index.d.ts +12 -0
  195. package/dist/pipeline/type-defs/index.d.ts.map +1 -0
  196. package/dist/pipeline/type-defs/index.js +12 -0
  197. package/dist/pipeline/type-defs/index.js.map +1 -0
  198. package/dist/pipeline/type-defs/packets.d.ts +821 -0
  199. package/dist/pipeline/type-defs/packets.d.ts.map +1 -0
  200. package/dist/pipeline/type-defs/packets.js +109 -0
  201. package/dist/pipeline/type-defs/packets.js.map +1 -0
  202. package/dist/pipeline/type-defs/snapshot.d.ts +52 -0
  203. package/dist/pipeline/type-defs/snapshot.d.ts.map +1 -0
  204. package/dist/pipeline/type-defs/snapshot.js +35 -0
  205. package/dist/pipeline/type-defs/snapshot.js.map +1 -0
  206. package/dist/pipeline/type-defs/state.d.ts +455 -0
  207. package/dist/pipeline/type-defs/state.d.ts.map +1 -0
  208. package/dist/pipeline/type-defs/state.js +90 -0
  209. package/dist/pipeline/type-defs/state.js.map +1 -0
  210. package/dist/pipeline/types.d.ts +16 -0
  211. package/dist/pipeline/types.d.ts.map +1 -0
  212. package/dist/pipeline/types.js +16 -0
  213. package/dist/pipeline/types.js.map +1 -0
  214. package/dist/types/audit.d.ts +6 -6
  215. package/dist/types/consensus.d.ts +5 -1
  216. package/dist/types/consensus.d.ts.map +1 -1
  217. package/dist/types/consensus.js +15 -4
  218. package/dist/types/consensus.js.map +1 -1
  219. package/dist/types/index.d.ts +1 -1
  220. package/dist/types/index.d.ts.map +1 -1
  221. package/dist/types/index.js +1 -1
  222. package/dist/types/index.js.map +1 -1
  223. package/dist/types/project.d.ts +1 -1
  224. package/dist/types/project.d.ts.map +1 -1
  225. package/dist/types/project.js +39 -10
  226. package/dist/types/project.js.map +1 -1
  227. package/dist/types/workflow.d.ts +1 -7
  228. package/dist/types/workflow.d.ts.map +1 -1
  229. package/dist/types/workflow.js +1 -1
  230. package/dist/types/workflow.js.map +1 -1
  231. package/dist/upgrade/handlers.js +5 -5
  232. package/dist/upgrade/handlers.js.map +1 -1
  233. package/dist/workflow/index.d.ts.map +1 -1
  234. package/dist/workflow/index.js +52 -0
  235. package/dist/workflow/index.js.map +1 -1
  236. package/dist/workflow/website-strategy.js +1 -1
  237. package/dist/workflow/website-strategy.js.map +1 -1
  238. package/package.json +1 -1
  239. package/skills/PHASE_GATE_ENGINE_SPEC.md +113 -20
  240. package/skills/POPEYE_FULL_AUTONOMY_PIPELINE.md +66 -13
  241. package/src/adapters/gemini.ts +3 -3
  242. package/src/adapters/openai.ts +2 -2
  243. package/src/auth/gemini.ts +1 -1
  244. package/src/cli/commands/create.ts +12 -6
  245. package/src/cli/commands/resume.ts +9 -1
  246. package/src/cli/interactive.ts +32 -3
  247. package/src/config/defaults.ts +7 -2
  248. package/src/config/popeye-md.ts +139 -0
  249. package/src/config/schema.ts +21 -8
  250. package/src/pipeline/artifact-manager.ts +339 -0
  251. package/src/pipeline/artifact-validators.ts +224 -0
  252. package/src/pipeline/bridges/review-bridge.ts +371 -0
  253. package/src/pipeline/change-request.ts +119 -0
  254. package/src/pipeline/check-runner.ts +504 -0
  255. package/src/pipeline/command-resolver.ts +168 -0
  256. package/src/pipeline/consensus/consensus-runner.ts +317 -0
  257. package/src/pipeline/constitution.ts +109 -0
  258. package/src/pipeline/gate-engine.ts +347 -0
  259. package/src/pipeline/index.ts +82 -0
  260. package/src/pipeline/migration.ts +91 -0
  261. package/src/pipeline/orchestrator.ts +322 -0
  262. package/src/pipeline/packets/audit-report-builder.ts +47 -0
  263. package/src/pipeline/packets/consensus-packet-builder.ts +112 -0
  264. package/src/pipeline/packets/index.ts +15 -0
  265. package/src/pipeline/packets/plan-packet-builder.ts +52 -0
  266. package/src/pipeline/packets/rca-packet-builder.ts +38 -0
  267. package/src/pipeline/phases/architecture.ts +73 -0
  268. package/src/pipeline/phases/audit.ts +193 -0
  269. package/src/pipeline/phases/consensus-architecture.ts +104 -0
  270. package/src/pipeline/phases/consensus-master-plan.ts +100 -0
  271. package/src/pipeline/phases/consensus-role-plans.ts +105 -0
  272. package/src/pipeline/phases/done.ts +68 -0
  273. package/src/pipeline/phases/implementation.ts +52 -0
  274. package/src/pipeline/phases/index.ts +21 -0
  275. package/src/pipeline/phases/intake.ts +68 -0
  276. package/src/pipeline/phases/phase-context.ts +86 -0
  277. package/src/pipeline/phases/production-gate.ts +113 -0
  278. package/src/pipeline/phases/qa-validation.ts +63 -0
  279. package/src/pipeline/phases/recovery-loop.ts +120 -0
  280. package/src/pipeline/phases/review.ts +149 -0
  281. package/src/pipeline/phases/role-planning.ts +92 -0
  282. package/src/pipeline/phases/stuck.ts +62 -0
  283. package/src/pipeline/repo-snapshot.ts +395 -0
  284. package/src/pipeline/role-execution-adapter.ts +238 -0
  285. package/src/pipeline/skill-loader.ts +192 -0
  286. package/src/pipeline/skills/defaults.ts +215 -0
  287. package/src/pipeline/type-defs/artifacts.ts +82 -0
  288. package/src/pipeline/type-defs/audit.ts +67 -0
  289. package/src/pipeline/type-defs/checks.ts +47 -0
  290. package/src/pipeline/type-defs/enums.ts +62 -0
  291. package/src/pipeline/type-defs/index.ts +12 -0
  292. package/src/pipeline/type-defs/packets.ts +131 -0
  293. package/src/pipeline/type-defs/snapshot.ts +55 -0
  294. package/src/pipeline/type-defs/state.ts +167 -0
  295. package/src/pipeline/types.ts +16 -0
  296. package/src/types/consensus.ts +16 -4
  297. package/src/types/index.ts +1 -0
  298. package/src/types/project.ts +39 -10
  299. package/src/types/workflow.ts +1 -1
  300. package/src/upgrade/handlers.ts +5 -5
  301. package/src/workflow/index.ts +52 -0
  302. package/src/workflow/website-strategy.ts +1 -1
  303. package/tests/cli/model-command.test.ts +19 -9
  304. package/tests/config/config.test.ts +3 -3
  305. package/tests/config/popeye-md.test.ts +168 -0
  306. package/tests/pipeline/artifact-manager.test.ts +183 -0
  307. package/tests/pipeline/artifact-validators.test.ts +207 -0
  308. package/tests/pipeline/bridges/review-bridge.test.ts +243 -0
  309. package/tests/pipeline/change-request.test.ts +180 -0
  310. package/tests/pipeline/check-runner.test.ts +157 -0
  311. package/tests/pipeline/command-resolver.test.ts +159 -0
  312. package/tests/pipeline/consensus-runner.test.ts +206 -0
  313. package/tests/pipeline/consensus-scoring.test.ts +163 -0
  314. package/tests/pipeline/constitution.test.ts +122 -0
  315. package/tests/pipeline/gate-engine.test.ts +195 -0
  316. package/tests/pipeline/migration.test.ts +133 -0
  317. package/tests/pipeline/orchestrator.test.ts +614 -0
  318. package/tests/pipeline/packets/builders.test.ts +347 -0
  319. package/tests/pipeline/repo-snapshot.test.ts +189 -0
  320. package/tests/pipeline/role-execution-adapter.test.ts +299 -0
  321. package/tests/pipeline/session-guidance.test.ts +205 -0
  322. package/tests/pipeline/skill-loader.test.ts +186 -0
  323. package/tests/pipeline/start-env-checks.test.ts +123 -0
  324. package/tests/pipeline/types.test.ts +156 -0
  325. package/tests/types/consensus.test.ts +1 -1
  326. package/tests/workflow/pipeline-bootstrap.test.ts +162 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Role Execution Adapter tests — context building, prompt injection, role detection.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdirSync, writeFileSync, rmSync, existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import {
9
+ buildRoleExecutionContext,
10
+ executeWithRoleContext,
11
+ buildAllRoleContexts,
12
+ } from '../../src/pipeline/role-execution-adapter.js';
13
+ import type { RoleExecutionContext, ClaudeExecuteOptions } from '../../src/pipeline/role-execution-adapter.js';
14
+ import type { ArtifactEntry, PipelineRole } from '../../src/pipeline/types.js';
15
+ import { createDefaultPipelineState } from '../../src/pipeline/types.js';
16
+
17
+ const TEST_DIR = join(process.cwd(), 'tmp-role-adapter-test');
18
+
19
+ function makeMockSkill(role: string) {
20
+ return {
21
+ role,
22
+ systemPrompt: `You are ${role}. Follow best practices.`,
23
+ constraints: ['Stay in scope', 'Do not modify tests'],
24
+ tools: [],
25
+ };
26
+ }
27
+
28
+ function makeRolePlanArtifact(role: string): ArtifactEntry {
29
+ return {
30
+ id: `plan-${role}`,
31
+ type: 'role_plan',
32
+ phase: 'ROLE_PLANNING',
33
+ version: 1,
34
+ path: `docs/role-plans/${role}.md`,
35
+ sha256: 'abc123',
36
+ timestamp: new Date().toISOString(),
37
+ immutable: true,
38
+ content_type: 'markdown',
39
+ group_id: `group-${role}`,
40
+ };
41
+ }
42
+
43
+ beforeEach(() => {
44
+ mkdirSync(join(TEST_DIR, 'docs', 'role-plans'), { recursive: true });
45
+ });
46
+
47
+ afterEach(() => {
48
+ if (existsSync(TEST_DIR)) {
49
+ rmSync(TEST_DIR, { recursive: true, force: true });
50
+ }
51
+ });
52
+
53
+ describe('buildRoleExecutionContext', () => {
54
+ it('should build context with role, system prompt, and task scope', () => {
55
+ const planContent = [
56
+ '# FRONTEND_PROGRAMMER Role Plan',
57
+ '## Tasks',
58
+ '- Build login page in src/app/login/',
59
+ '- Implement dashboard components',
60
+ '## Dependencies',
61
+ 'Requires API contracts from BACKEND_PROGRAMMER.',
62
+ ].join('\n');
63
+ const artifact = makeRolePlanArtifact('FRONTEND_PROGRAMMER');
64
+ writeFileSync(join(TEST_DIR, artifact.path), planContent);
65
+
66
+ const ctx = buildRoleExecutionContext(
67
+ 'FRONTEND_PROGRAMMER',
68
+ makeMockSkill('FRONTEND_PROGRAMMER'),
69
+ artifact,
70
+ TEST_DIR,
71
+ );
72
+
73
+ expect(ctx.role).toBe('FRONTEND_PROGRAMMER');
74
+ expect(ctx.systemPrompt).toContain('FRONTEND_PROGRAMMER');
75
+ expect(ctx.systemPrompt).toContain('You are FRONTEND_PROGRAMMER');
76
+ expect(ctx.taskScope).toContain('Build login page');
77
+ expect(ctx.allowedPaths.length).toBeGreaterThan(0);
78
+ });
79
+
80
+ it('should include forbidden patterns for frontend role', () => {
81
+ const artifact = makeRolePlanArtifact('FRONTEND_PROGRAMMER');
82
+ writeFileSync(join(TEST_DIR, artifact.path), '# FRONTEND_PROGRAMMER Plan\n## Tasks\n- UI work');
83
+
84
+ const ctx = buildRoleExecutionContext(
85
+ 'FRONTEND_PROGRAMMER',
86
+ makeMockSkill('FRONTEND_PROGRAMMER'),
87
+ artifact,
88
+ TEST_DIR,
89
+ );
90
+
91
+ expect(ctx.forbiddenPatterns).toContain('server/');
92
+ expect(ctx.forbiddenPatterns).toContain('prisma/');
93
+ });
94
+
95
+ it('should include forbidden patterns in system prompt', () => {
96
+ const artifact = makeRolePlanArtifact('FRONTEND_PROGRAMMER');
97
+ writeFileSync(join(TEST_DIR, artifact.path), '# FRONTEND_PROGRAMMER Plan\n## Tasks\n- UI');
98
+
99
+ const ctx = buildRoleExecutionContext(
100
+ 'FRONTEND_PROGRAMMER',
101
+ makeMockSkill('FRONTEND_PROGRAMMER'),
102
+ artifact,
103
+ TEST_DIR,
104
+ );
105
+
106
+ expect(ctx.systemPrompt).toContain('Forbidden Paths');
107
+ expect(ctx.systemPrompt).toContain('server/');
108
+ });
109
+
110
+ it('should have no forbidden patterns for QA_TESTER', () => {
111
+ const artifact = makeRolePlanArtifact('QA_TESTER');
112
+ writeFileSync(join(TEST_DIR, artifact.path), '# QA_TESTER Plan\n## Tasks\n- Test all');
113
+
114
+ const ctx = buildRoleExecutionContext(
115
+ 'QA_TESTER',
116
+ makeMockSkill('QA_TESTER'),
117
+ artifact,
118
+ TEST_DIR,
119
+ );
120
+
121
+ expect(ctx.forbiddenPatterns).toEqual([]);
122
+ expect(ctx.systemPrompt).not.toContain('Forbidden Paths');
123
+ });
124
+
125
+ it('should include skill constraints in system prompt', () => {
126
+ const artifact = makeRolePlanArtifact('BACKEND_PROGRAMMER');
127
+ writeFileSync(join(TEST_DIR, artifact.path), '# BACKEND_PROGRAMMER Plan\n## Tasks\n- API');
128
+
129
+ const ctx = buildRoleExecutionContext(
130
+ 'BACKEND_PROGRAMMER',
131
+ makeMockSkill('BACKEND_PROGRAMMER'),
132
+ artifact,
133
+ TEST_DIR,
134
+ );
135
+
136
+ expect(ctx.systemPrompt).toContain('Stay in scope');
137
+ expect(ctx.systemPrompt).toContain('Do not modify tests');
138
+ });
139
+
140
+ it('should extract file paths from plan content as allowed paths', () => {
141
+ const planContent = [
142
+ '# BACKEND_PROGRAMMER Plan',
143
+ '## Tasks',
144
+ '- Implement API routes in src/api/routes.ts',
145
+ '- Update server/middleware.ts',
146
+ ].join('\n');
147
+ const artifact = makeRolePlanArtifact('BACKEND_PROGRAMMER');
148
+ writeFileSync(join(TEST_DIR, artifact.path), planContent);
149
+
150
+ const ctx = buildRoleExecutionContext(
151
+ 'BACKEND_PROGRAMMER',
152
+ makeMockSkill('BACKEND_PROGRAMMER'),
153
+ artifact,
154
+ TEST_DIR,
155
+ );
156
+
157
+ expect(ctx.allowedPaths).toContain('src/api/routes.ts');
158
+ expect(ctx.allowedPaths).toContain('server/middleware.ts');
159
+ });
160
+
161
+ it('should handle missing plan file gracefully', () => {
162
+ const artifact = makeRolePlanArtifact('BACKEND_PROGRAMMER');
163
+ // Don't write the file
164
+
165
+ const ctx = buildRoleExecutionContext(
166
+ 'BACKEND_PROGRAMMER',
167
+ makeMockSkill('BACKEND_PROGRAMMER'),
168
+ artifact,
169
+ TEST_DIR,
170
+ );
171
+
172
+ expect(ctx.role).toBe('BACKEND_PROGRAMMER');
173
+ expect(ctx.taskScope).toBe('');
174
+ expect(ctx.systemPrompt).toContain('BACKEND_PROGRAMMER');
175
+ });
176
+ });
177
+
178
+ describe('executeWithRoleContext', () => {
179
+ it('should inject systemPrompt into options', () => {
180
+ const ctx: RoleExecutionContext = {
181
+ role: 'FRONTEND_PROGRAMMER',
182
+ systemPrompt: 'You are FRONTEND_PROGRAMMER. Build the UI.',
183
+ allowedPaths: ['src/'],
184
+ forbiddenPatterns: ['server/'],
185
+ taskScope: 'Build login page',
186
+ };
187
+
188
+ const options: ClaudeExecuteOptions = {
189
+ projectDir: '/test/project',
190
+ };
191
+
192
+ const result = executeWithRoleContext(ctx, options);
193
+ expect(result.systemPrompt).toBe('You are FRONTEND_PROGRAMMER. Build the UI.');
194
+ expect(result.projectDir).toBe('/test/project');
195
+ });
196
+
197
+ it('should override existing systemPrompt', () => {
198
+ const ctx: RoleExecutionContext = {
199
+ role: 'BACKEND_PROGRAMMER',
200
+ systemPrompt: 'Role-specific prompt',
201
+ allowedPaths: [],
202
+ forbiddenPatterns: [],
203
+ taskScope: '',
204
+ };
205
+
206
+ const options: ClaudeExecuteOptions = {
207
+ projectDir: '/test',
208
+ systemPrompt: 'Old prompt',
209
+ };
210
+
211
+ const result = executeWithRoleContext(ctx, options);
212
+ expect(result.systemPrompt).toBe('Role-specific prompt');
213
+ });
214
+
215
+ it('should preserve other options', () => {
216
+ const ctx: RoleExecutionContext = {
217
+ role: 'QA_TESTER',
218
+ systemPrompt: 'Test prompt',
219
+ allowedPaths: [],
220
+ forbiddenPatterns: [],
221
+ taskScope: '',
222
+ };
223
+
224
+ const options: ClaudeExecuteOptions = {
225
+ projectDir: '/test',
226
+ customField: 'preserved',
227
+ };
228
+
229
+ const result = executeWithRoleContext(ctx, options);
230
+ expect(result.customField).toBe('preserved');
231
+ });
232
+ });
233
+
234
+ describe('buildAllRoleContexts', () => {
235
+ it('should build contexts for all detected roles', () => {
236
+ const pipeline = createDefaultPipelineState();
237
+ pipeline.activeRoles = ['FRONTEND_PROGRAMMER', 'BACKEND_PROGRAMMER'];
238
+
239
+ // Create plan files
240
+ const fePlan = makeRolePlanArtifact('FRONTEND_PROGRAMMER');
241
+ const bePlan = makeRolePlanArtifact('BACKEND_PROGRAMMER');
242
+ pipeline.artifacts.push(fePlan, bePlan);
243
+
244
+ writeFileSync(
245
+ join(TEST_DIR, fePlan.path),
246
+ '# FRONTEND_PROGRAMMER Role Plan\n## Tasks\n- Build UI',
247
+ );
248
+ writeFileSync(
249
+ join(TEST_DIR, bePlan.path),
250
+ '# BACKEND_PROGRAMMER Role Plan\n## Tasks\n- Build API',
251
+ );
252
+
253
+ const mockSkillLoader = {
254
+ loadSkill: (role: string) => makeMockSkill(role),
255
+ listSkills: () => [],
256
+ };
257
+
258
+ const contexts = buildAllRoleContexts(pipeline, mockSkillLoader as any, TEST_DIR);
259
+ expect(contexts.size).toBe(2);
260
+ expect(contexts.has('FRONTEND_PROGRAMMER')).toBe(true);
261
+ expect(contexts.has('BACKEND_PROGRAMMER')).toBe(true);
262
+ });
263
+
264
+ it('should skip roles with missing plan files', () => {
265
+ const pipeline = createDefaultPipelineState();
266
+ pipeline.activeRoles = ['FRONTEND_PROGRAMMER', 'BACKEND_PROGRAMMER'];
267
+
268
+ const fePlan = makeRolePlanArtifact('FRONTEND_PROGRAMMER');
269
+ pipeline.artifacts.push(fePlan);
270
+
271
+ // Only write FE plan, not BE
272
+ writeFileSync(
273
+ join(TEST_DIR, fePlan.path),
274
+ '# FRONTEND_PROGRAMMER Role Plan\n## Tasks\n- Build UI',
275
+ );
276
+
277
+ const mockSkillLoader = {
278
+ loadSkill: (role: string) => makeMockSkill(role),
279
+ listSkills: () => [],
280
+ };
281
+
282
+ const contexts = buildAllRoleContexts(pipeline, mockSkillLoader as any, TEST_DIR);
283
+ expect(contexts.size).toBe(1);
284
+ expect(contexts.has('FRONTEND_PROGRAMMER')).toBe(true);
285
+ });
286
+
287
+ it('should return empty map when no role plans exist', () => {
288
+ const pipeline = createDefaultPipelineState();
289
+ pipeline.activeRoles = ['FRONTEND_PROGRAMMER'];
290
+
291
+ const mockSkillLoader = {
292
+ loadSkill: (role: string) => makeMockSkill(role),
293
+ listSkills: () => [],
294
+ };
295
+
296
+ const contexts = buildAllRoleContexts(pipeline, mockSkillLoader as any, TEST_DIR);
297
+ expect(contexts.size).toBe(0);
298
+ });
299
+ });
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Fix A tests — sessionGuidance threading through pipeline.
3
+ * Verifies additionalContext persists in PipelineState and reaches phases.
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import {
8
+ createDefaultPipelineState,
9
+ PipelineStateSchema,
10
+ ArtifactTypeSchema,
11
+ } from '../../src/pipeline/types.js';
12
+
13
+ describe('Fix A: sessionGuidance threading', () => {
14
+ describe('PipelineState.sessionGuidance', () => {
15
+ it('should accept sessionGuidance in pipeline state', () => {
16
+ const state = createDefaultPipelineState();
17
+ state.sessionGuidance = 'Upgrade from v1 to v2: preserve API backwards compat';
18
+
19
+ const result = PipelineStateSchema.safeParse(state);
20
+ expect(result.success).toBe(true);
21
+ expect(result.data?.sessionGuidance).toBe(
22
+ 'Upgrade from v1 to v2: preserve API backwards compat',
23
+ );
24
+ });
25
+
26
+ it('should allow omitting sessionGuidance (backward compat)', () => {
27
+ const state = createDefaultPipelineState();
28
+ // No sessionGuidance set
29
+
30
+ const result = PipelineStateSchema.safeParse(state);
31
+ expect(result.success).toBe(true);
32
+ expect(result.data?.sessionGuidance).toBeUndefined();
33
+ });
34
+
35
+ it('should allow empty string for sessionGuidance', () => {
36
+ const state = createDefaultPipelineState();
37
+ state.sessionGuidance = '';
38
+
39
+ const result = PipelineStateSchema.safeParse(state);
40
+ expect(result.success).toBe(true);
41
+ });
42
+ });
43
+
44
+ describe('additional_context artifact type', () => {
45
+ it('should accept additional_context as valid artifact type', () => {
46
+ const result = ArtifactTypeSchema.safeParse('additional_context');
47
+ expect(result.success).toBe(true);
48
+ });
49
+
50
+ it('should still accept all existing artifact types', () => {
51
+ const existingTypes = [
52
+ 'master_plan', 'architecture', 'role_plan', 'consensus',
53
+ 'arbitration', 'audit_report', 'rca_report', 'production_readiness',
54
+ 'change_request',
55
+ ];
56
+
57
+ for (const type of existingTypes) {
58
+ expect(ArtifactTypeSchema.safeParse(type).success).toBe(true);
59
+ }
60
+ });
61
+ });
62
+
63
+ describe('PipelineOptions additionalContext -> sessionGuidance', () => {
64
+ it('should store additionalContext in pipeline state when not already set', () => {
65
+ const pipeline = createDefaultPipelineState();
66
+ const additionalContext = 'Focus on mobile-first responsive design';
67
+
68
+ // Simulates orchestrator logic
69
+ if (additionalContext && !pipeline.sessionGuidance) {
70
+ pipeline.sessionGuidance = additionalContext;
71
+ }
72
+
73
+ expect(pipeline.sessionGuidance).toBe('Focus on mobile-first responsive design');
74
+ });
75
+
76
+ it('should not overwrite existing sessionGuidance on resume', () => {
77
+ const pipeline = createDefaultPipelineState();
78
+ pipeline.sessionGuidance = 'Original guidance from first run';
79
+ const additionalContext = 'New guidance on resume';
80
+
81
+ // Simulates orchestrator logic
82
+ if (additionalContext && !pipeline.sessionGuidance) {
83
+ pipeline.sessionGuidance = additionalContext;
84
+ }
85
+
86
+ expect(pipeline.sessionGuidance).toBe('Original guidance from first run');
87
+ });
88
+
89
+ it('should leave sessionGuidance undefined when no additionalContext', () => {
90
+ const pipeline = createDefaultPipelineState();
91
+ const additionalContext: string | undefined = undefined;
92
+
93
+ if (additionalContext && !pipeline.sessionGuidance) {
94
+ pipeline.sessionGuidance = additionalContext;
95
+ }
96
+
97
+ expect(pipeline.sessionGuidance).toBeUndefined();
98
+ });
99
+ });
100
+
101
+ describe('INTAKE phase guidance injection', () => {
102
+ it('should prepend guidance to plan input when provided', () => {
103
+ const guidance = 'Upgrade context: migrate from Express to Fastify';
104
+ const expandedIdea = 'Build a REST API with user authentication';
105
+
106
+ const planInput = guidance
107
+ ? `${guidance}\n\n---\n\n${expandedIdea}`
108
+ : expandedIdea;
109
+
110
+ expect(planInput).toContain(guidance);
111
+ expect(planInput).toContain('---');
112
+ expect(planInput).toContain(expandedIdea);
113
+ expect(planInput.indexOf(guidance)).toBeLessThan(planInput.indexOf(expandedIdea));
114
+ });
115
+
116
+ it('should pass through expandedIdea unchanged when no guidance', () => {
117
+ const guidance = '';
118
+ const expandedIdea = 'Build a REST API with user authentication';
119
+
120
+ const planInput = guidance
121
+ ? `${guidance}\n\n---\n\n${expandedIdea}`
122
+ : expandedIdea;
123
+
124
+ expect(planInput).toBe(expandedIdea);
125
+ });
126
+ });
127
+
128
+ describe('IMPLEMENTATION phase guidance injection', () => {
129
+ it('should merge role prompt with guidance', () => {
130
+ const combinedRolePrompt = '## BACKEND_PROGRAMMER\nScope: API endpoints';
131
+ const guidance = 'Preserve backwards compatibility with v1 API';
132
+
133
+ const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
134
+
135
+ expect(systemPrompt).toContain(combinedRolePrompt);
136
+ expect(systemPrompt).toContain(guidance);
137
+ });
138
+
139
+ it('should use only role prompt when no guidance', () => {
140
+ const combinedRolePrompt = '## BACKEND_PROGRAMMER\nScope: API endpoints';
141
+ const guidance: string | undefined = undefined;
142
+
143
+ const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
144
+
145
+ expect(systemPrompt).toBe(combinedRolePrompt);
146
+ });
147
+
148
+ it('should use only guidance when no role prompt', () => {
149
+ const combinedRolePrompt = '';
150
+ const guidance = 'Focus on performance';
151
+
152
+ const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
153
+
154
+ expect(systemPrompt).toBe(guidance);
155
+ });
156
+
157
+ it('should return undefined when neither role prompt nor guidance', () => {
158
+ const combinedRolePrompt = '';
159
+ const guidance: string | undefined = undefined;
160
+
161
+ const systemPrompt = [combinedRolePrompt, guidance].filter(Boolean).join('\n\n') || undefined;
162
+
163
+ expect(systemPrompt).toBeUndefined();
164
+ });
165
+ });
166
+
167
+ describe('RECOVERY_LOOP guidance in RCA prompt', () => {
168
+ it('should include user guidance in RCA prompt when available', () => {
169
+ const debuggerPrompt = 'You are a debugger agent...';
170
+ const guidance = 'User wants API backwards compat preserved';
171
+ const failureEvidence = 'Failed phase: QA_VALIDATION';
172
+
173
+ const rcaPrompt = [
174
+ debuggerPrompt,
175
+ '',
176
+ ...(guidance ? ['## User Guidance', guidance, ''] : []),
177
+ '## Failure Evidence',
178
+ failureEvidence,
179
+ ].join('\n');
180
+
181
+ expect(rcaPrompt).toContain('## User Guidance');
182
+ expect(rcaPrompt).toContain(guidance);
183
+ expect(rcaPrompt.indexOf('User Guidance')).toBeLessThan(
184
+ rcaPrompt.indexOf('Failure Evidence'),
185
+ );
186
+ });
187
+
188
+ it('should omit User Guidance section when no guidance', () => {
189
+ const debuggerPrompt = 'You are a debugger agent...';
190
+ const guidance: string | undefined = undefined;
191
+ const failureEvidence = 'Failed phase: QA_VALIDATION';
192
+
193
+ const rcaPrompt = [
194
+ debuggerPrompt,
195
+ '',
196
+ ...(guidance ? ['## User Guidance', guidance, ''] : []),
197
+ '## Failure Evidence',
198
+ failureEvidence,
199
+ ].join('\n');
200
+
201
+ expect(rcaPrompt).not.toContain('## User Guidance');
202
+ expect(rcaPrompt).toContain('## Failure Evidence');
203
+ });
204
+ });
205
+ });
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Skill Loader tests — frontmatter parsing, raw fallback, merge, caching.
3
+ */
4
+
5
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
6
+ import { mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import {
9
+ SkillLoader,
10
+ parseSkillMarkdown,
11
+ getDefaultSkill,
12
+ createSkillLoader,
13
+ } from '../../src/pipeline/skill-loader.js';
14
+
15
+ const TEST_DIR = join(process.cwd(), '.test-skill-loader');
16
+ const SKILLS_DIR = join(TEST_DIR, 'skills');
17
+
18
+ describe('SkillLoader', () => {
19
+ beforeEach(() => {
20
+ if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
21
+ mkdirSync(SKILLS_DIR, { recursive: true });
22
+ });
23
+
24
+ afterEach(() => {
25
+ if (existsSync(TEST_DIR)) rmSync(TEST_DIR, { recursive: true });
26
+ });
27
+
28
+ describe('parseSkillMarkdown', () => {
29
+ it('should parse frontmatter with body', () => {
30
+ const md = `---
31
+ role: ARCHITECT
32
+ version: 2.0
33
+ required_outputs:
34
+ - architecture_doc
35
+ - api_contracts
36
+ constraints:
37
+ - no implementation details
38
+ - all contracts explicit
39
+ ---
40
+ # System Prompt
41
+ You are the Architect responsible for system design.`;
42
+
43
+ const result = parseSkillMarkdown(md);
44
+ expect(result.role).toBe('ARCHITECT');
45
+ expect(result.version).toBe('2.0');
46
+ expect(result.required_outputs).toEqual(['architecture_doc', 'api_contracts']);
47
+ expect(result.constraints).toEqual(['no implementation details', 'all contracts explicit']);
48
+ expect(result.systemPrompt).toContain('You are the Architect');
49
+ });
50
+
51
+ it('should treat entire content as systemPrompt when no frontmatter', () => {
52
+ const md = 'You are a specialized reviewer.\nReview all code carefully.';
53
+ const result = parseSkillMarkdown(md);
54
+
55
+ expect(result.systemPrompt).toBe(md);
56
+ expect(result.role).toBeUndefined();
57
+ expect(result.version).toBeUndefined();
58
+ });
59
+
60
+ it('should handle frontmatter with scalar values', () => {
61
+ const md = `---
62
+ role: DEBUGGER
63
+ version: 1.5
64
+ ---
65
+ Debug the issue.`;
66
+
67
+ const result = parseSkillMarkdown(md);
68
+ expect(result.role).toBe('DEBUGGER');
69
+ expect(result.version).toBe('1.5');
70
+ expect(result.systemPrompt).toBe('Debug the issue.');
71
+ });
72
+
73
+ it('should handle empty body after frontmatter', () => {
74
+ const md = `---
75
+ role: QA_TESTER
76
+ version: 1.0
77
+ ---
78
+ `;
79
+
80
+ const result = parseSkillMarkdown(md);
81
+ expect(result.role).toBe('QA_TESTER');
82
+ expect(result.systemPrompt).toBeUndefined();
83
+ });
84
+ });
85
+
86
+ describe('getDefaultSkill', () => {
87
+ it('should return a valid skill for known roles', () => {
88
+ const skill = getDefaultSkill('ARCHITECT');
89
+ expect(skill.role).toBe('ARCHITECT');
90
+ expect(skill.systemPrompt).toBeDefined();
91
+ expect(skill.systemPrompt.length).toBeGreaterThan(0);
92
+ expect(Array.isArray(skill.required_outputs)).toBe(true);
93
+ expect(Array.isArray(skill.constraints)).toBe(true);
94
+ });
95
+
96
+ it('should return fallback for unknown roles', () => {
97
+ const skill = getDefaultSkill('NONEXISTENT' as any);
98
+ expect(skill.role).toBe('NONEXISTENT');
99
+ expect(skill.systemPrompt).toContain('NONEXISTENT');
100
+ });
101
+ });
102
+
103
+ describe('SkillLoader class', () => {
104
+ it('should load default skills when no override exists', () => {
105
+ const loader = new SkillLoader(SKILLS_DIR);
106
+ const skill = loader.loadSkill('ARCHITECT');
107
+
108
+ expect(skill.role).toBe('ARCHITECT');
109
+ expect(skill.systemPrompt).toBeDefined();
110
+ });
111
+
112
+ it('should merge .md override with default', () => {
113
+ writeFileSync(join(SKILLS_DIR, 'ARCHITECT.md'), `---
114
+ version: 3.0
115
+ constraints:
116
+ - custom constraint
117
+ ---
118
+ Custom architect prompt.`);
119
+
120
+ const loader = new SkillLoader(SKILLS_DIR);
121
+ const skill = loader.loadSkill('ARCHITECT');
122
+
123
+ expect(skill.role).toBe('ARCHITECT');
124
+ expect(skill.version).toBe('3.0');
125
+ expect(skill.systemPrompt).toBe('Custom architect prompt.');
126
+ expect(skill.constraints).toEqual(['custom constraint']);
127
+ });
128
+
129
+ it('should cache loaded skills', () => {
130
+ const loader = new SkillLoader(SKILLS_DIR);
131
+ const s1 = loader.loadSkill('DEBUGGER');
132
+ const s2 = loader.loadSkill('DEBUGGER');
133
+ expect(s1).toBe(s2); // Same reference = cached
134
+ });
135
+
136
+ it('should clear cache', () => {
137
+ const loader = new SkillLoader(SKILLS_DIR);
138
+ const s1 = loader.loadSkill('DEBUGGER');
139
+ loader.clearCache();
140
+ const s2 = loader.loadSkill('DEBUGGER');
141
+ expect(s1).not.toBe(s2); // Different reference after cache clear
142
+ });
143
+
144
+ it('should load all skills for given roles', () => {
145
+ const loader = new SkillLoader(SKILLS_DIR);
146
+ const skills = loader.loadAllSkills(['ARCHITECT', 'DEBUGGER', 'QA_TESTER']);
147
+
148
+ expect(skills.size).toBe(3);
149
+ expect(skills.has('ARCHITECT')).toBe(true);
150
+ expect(skills.has('DEBUGGER')).toBe(true);
151
+ expect(skills.has('QA_TESTER')).toBe(true);
152
+ });
153
+
154
+ it('should list available overrides', () => {
155
+ writeFileSync(join(SKILLS_DIR, 'ARCHITECT.md'), 'prompt');
156
+ writeFileSync(join(SKILLS_DIR, 'REVIEWER.md'), 'prompt');
157
+
158
+ const loader = new SkillLoader(SKILLS_DIR);
159
+ const overrides = loader.listAvailableOverrides();
160
+
161
+ expect(overrides).toContain('ARCHITECT');
162
+ expect(overrides).toContain('REVIEWER');
163
+ });
164
+
165
+ it('should handle no skills directory gracefully', () => {
166
+ const loader = new SkillLoader('/nonexistent/path');
167
+ const skill = loader.loadSkill('ARCHITECT');
168
+
169
+ // Falls back to default
170
+ expect(skill.role).toBe('ARCHITECT');
171
+ expect(skill.systemPrompt).toBeDefined();
172
+ });
173
+ });
174
+
175
+ describe('createSkillLoader', () => {
176
+ it('should create loader with skills dir', () => {
177
+ const loader = createSkillLoader(TEST_DIR);
178
+ expect(loader).toBeInstanceOf(SkillLoader);
179
+ });
180
+
181
+ it('should create loader without project dir', () => {
182
+ const loader = createSkillLoader();
183
+ expect(loader).toBeInstanceOf(SkillLoader);
184
+ });
185
+ });
186
+ });