popeye-cli 2.2.0 → 2.7.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 (323) hide show
  1. package/dist/adapters/gemini.d.ts +14 -0
  2. package/dist/adapters/gemini.d.ts.map +1 -1
  3. package/dist/adapters/gemini.js +41 -6
  4. package/dist/adapters/gemini.js.map +1 -1
  5. package/dist/adapters/grok.d.ts +14 -0
  6. package/dist/adapters/grok.d.ts.map +1 -1
  7. package/dist/adapters/grok.js +42 -6
  8. package/dist/adapters/grok.js.map +1 -1
  9. package/dist/adapters/openai.d.ts +10 -0
  10. package/dist/adapters/openai.d.ts.map +1 -1
  11. package/dist/adapters/openai.js +44 -5
  12. package/dist/adapters/openai.js.map +1 -1
  13. package/dist/cli/commands/create.js +1 -1
  14. package/dist/cli/commands/create.js.map +1 -1
  15. package/dist/cli/interactive.d.ts.map +1 -1
  16. package/dist/cli/interactive.js +324 -20
  17. package/dist/cli/interactive.js.map +1 -1
  18. package/dist/generators/all.d.ts.map +1 -1
  19. package/dist/generators/all.js +3 -2
  20. package/dist/generators/all.js.map +1 -1
  21. package/dist/generators/doc-parser.d.ts +21 -6
  22. package/dist/generators/doc-parser.d.ts.map +1 -1
  23. package/dist/generators/doc-parser.js +55 -4
  24. package/dist/generators/doc-parser.js.map +1 -1
  25. package/dist/generators/templates/fullstack.js +1 -1
  26. package/dist/generators/templates/website-components.js +1 -1
  27. package/dist/generators/templates/website-components.js.map +1 -1
  28. package/dist/generators/templates/website-config.d.ts +4 -1
  29. package/dist/generators/templates/website-config.d.ts.map +1 -1
  30. package/dist/generators/templates/website-config.js +17 -11
  31. package/dist/generators/templates/website-config.js.map +1 -1
  32. package/dist/generators/templates/website-conversion.js +1 -1
  33. package/dist/generators/templates/website-conversion.js.map +1 -1
  34. package/dist/generators/templates/website-landing.js +1 -1
  35. package/dist/generators/templates/website-landing.js.map +1 -1
  36. package/dist/generators/templates/website-layout.d.ts +36 -4
  37. package/dist/generators/templates/website-layout.d.ts.map +1 -1
  38. package/dist/generators/templates/website-layout.js +466 -23
  39. package/dist/generators/templates/website-layout.js.map +1 -1
  40. package/dist/generators/templates/website-pricing.js +1 -1
  41. package/dist/generators/templates/website-pricing.js.map +1 -1
  42. package/dist/generators/templates/website-sections.js +1 -1
  43. package/dist/generators/templates/website-sections.js.map +1 -1
  44. package/dist/generators/templates/website-seo.d.ts.map +1 -1
  45. package/dist/generators/templates/website-seo.js +4 -1
  46. package/dist/generators/templates/website-seo.js.map +1 -1
  47. package/dist/generators/templates/website.d.ts +1 -1
  48. package/dist/generators/templates/website.d.ts.map +1 -1
  49. package/dist/generators/templates/website.js +1 -1
  50. package/dist/generators/templates/website.js.map +1 -1
  51. package/dist/generators/website-content-ai.d.ts +52 -0
  52. package/dist/generators/website-content-ai.d.ts.map +1 -0
  53. package/dist/generators/website-content-ai.js +141 -0
  54. package/dist/generators/website-content-ai.js.map +1 -0
  55. package/dist/generators/website-content-scanner.d.ts +1 -1
  56. package/dist/generators/website-content-scanner.d.ts.map +1 -1
  57. package/dist/generators/website-content-scanner.js +98 -1
  58. package/dist/generators/website-content-scanner.js.map +1 -1
  59. package/dist/generators/website-context.d.ts +34 -1
  60. package/dist/generators/website-context.d.ts.map +1 -1
  61. package/dist/generators/website-context.js +131 -9
  62. package/dist/generators/website-context.js.map +1 -1
  63. package/dist/generators/website-debug.d.ts +12 -0
  64. package/dist/generators/website-debug.d.ts.map +1 -1
  65. package/dist/generators/website-debug.js +16 -0
  66. package/dist/generators/website-debug.js.map +1 -1
  67. package/dist/generators/website.d.ts.map +1 -1
  68. package/dist/generators/website.js +26 -4
  69. package/dist/generators/website.js.map +1 -1
  70. package/dist/pipeline/auto-recovery.d.ts +56 -0
  71. package/dist/pipeline/auto-recovery.d.ts.map +1 -0
  72. package/dist/pipeline/auto-recovery.js +185 -0
  73. package/dist/pipeline/auto-recovery.js.map +1 -0
  74. package/dist/pipeline/change-request.d.ts +39 -0
  75. package/dist/pipeline/change-request.d.ts.map +1 -1
  76. package/dist/pipeline/change-request.js +40 -1
  77. package/dist/pipeline/change-request.js.map +1 -1
  78. package/dist/pipeline/check-runner.d.ts +30 -1
  79. package/dist/pipeline/check-runner.d.ts.map +1 -1
  80. package/dist/pipeline/check-runner.js +122 -1
  81. package/dist/pipeline/check-runner.js.map +1 -1
  82. package/dist/pipeline/command-resolver.d.ts.map +1 -1
  83. package/dist/pipeline/command-resolver.js +33 -2
  84. package/dist/pipeline/command-resolver.js.map +1 -1
  85. package/dist/pipeline/consensus/arbitrator-query.d.ts +22 -0
  86. package/dist/pipeline/consensus/arbitrator-query.d.ts.map +1 -0
  87. package/dist/pipeline/consensus/arbitrator-query.js +70 -0
  88. package/dist/pipeline/consensus/arbitrator-query.js.map +1 -0
  89. package/dist/pipeline/consensus/consensus-runner.d.ts +131 -7
  90. package/dist/pipeline/consensus/consensus-runner.d.ts.map +1 -1
  91. package/dist/pipeline/consensus/consensus-runner.js +809 -35
  92. package/dist/pipeline/consensus/consensus-runner.js.map +1 -1
  93. package/dist/pipeline/cr-lifecycle.d.ts +42 -0
  94. package/dist/pipeline/cr-lifecycle.d.ts.map +1 -0
  95. package/dist/pipeline/cr-lifecycle.js +89 -0
  96. package/dist/pipeline/cr-lifecycle.js.map +1 -0
  97. package/dist/pipeline/gate-engine.d.ts +1 -0
  98. package/dist/pipeline/gate-engine.d.ts.map +1 -1
  99. package/dist/pipeline/gate-engine.js +26 -7
  100. package/dist/pipeline/gate-engine.js.map +1 -1
  101. package/dist/pipeline/orchestrator.d.ts +1 -1
  102. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  103. package/dist/pipeline/orchestrator.js +306 -16
  104. package/dist/pipeline/orchestrator.js.map +1 -1
  105. package/dist/pipeline/packets/consensus-packet-builder.d.ts +15 -4
  106. package/dist/pipeline/packets/consensus-packet-builder.d.ts.map +1 -1
  107. package/dist/pipeline/packets/consensus-packet-builder.js +29 -17
  108. package/dist/pipeline/packets/consensus-packet-builder.js.map +1 -1
  109. package/dist/pipeline/phases/architecture.d.ts.map +1 -1
  110. package/dist/pipeline/phases/architecture.js +5 -3
  111. package/dist/pipeline/phases/architecture.js.map +1 -1
  112. package/dist/pipeline/phases/audit.d.ts.map +1 -1
  113. package/dist/pipeline/phases/audit.js +5 -3
  114. package/dist/pipeline/phases/audit.js.map +1 -1
  115. package/dist/pipeline/phases/consensus-architecture.d.ts.map +1 -1
  116. package/dist/pipeline/phases/consensus-architecture.js +10 -1
  117. package/dist/pipeline/phases/consensus-architecture.js.map +1 -1
  118. package/dist/pipeline/phases/consensus-master-plan.d.ts.map +1 -1
  119. package/dist/pipeline/phases/consensus-master-plan.js +10 -3
  120. package/dist/pipeline/phases/consensus-master-plan.js.map +1 -1
  121. package/dist/pipeline/phases/consensus-role-plans.d.ts.map +1 -1
  122. package/dist/pipeline/phases/consensus-role-plans.js +10 -1
  123. package/dist/pipeline/phases/consensus-role-plans.js.map +1 -1
  124. package/dist/pipeline/phases/done.d.ts.map +1 -1
  125. package/dist/pipeline/phases/done.js +9 -4
  126. package/dist/pipeline/phases/done.js.map +1 -1
  127. package/dist/pipeline/phases/intake.d.ts.map +1 -1
  128. package/dist/pipeline/phases/intake.js +7 -3
  129. package/dist/pipeline/phases/intake.js.map +1 -1
  130. package/dist/pipeline/phases/phase-context.d.ts +2 -0
  131. package/dist/pipeline/phases/phase-context.d.ts.map +1 -1
  132. package/dist/pipeline/phases/phase-context.js +3 -1
  133. package/dist/pipeline/phases/phase-context.js.map +1 -1
  134. package/dist/pipeline/phases/production-gate.d.ts.map +1 -1
  135. package/dist/pipeline/phases/production-gate.js +28 -3
  136. package/dist/pipeline/phases/production-gate.js.map +1 -1
  137. package/dist/pipeline/phases/qa-validation.d.ts.map +1 -1
  138. package/dist/pipeline/phases/qa-validation.js +38 -5
  139. package/dist/pipeline/phases/qa-validation.js.map +1 -1
  140. package/dist/pipeline/phases/recovery-loop.d.ts +2 -0
  141. package/dist/pipeline/phases/recovery-loop.d.ts.map +1 -1
  142. package/dist/pipeline/phases/recovery-loop.js +200 -6
  143. package/dist/pipeline/phases/recovery-loop.js.map +1 -1
  144. package/dist/pipeline/phases/review.d.ts.map +1 -1
  145. package/dist/pipeline/phases/review.js +58 -28
  146. package/dist/pipeline/phases/review.js.map +1 -1
  147. package/dist/pipeline/phases/role-planning.d.ts.map +1 -1
  148. package/dist/pipeline/phases/role-planning.js +18 -2
  149. package/dist/pipeline/phases/role-planning.js.map +1 -1
  150. package/dist/pipeline/phases/stuck.d.ts.map +1 -1
  151. package/dist/pipeline/phases/stuck.js +10 -0
  152. package/dist/pipeline/phases/stuck.js.map +1 -1
  153. package/dist/pipeline/repo-snapshot.d.ts.map +1 -1
  154. package/dist/pipeline/repo-snapshot.js +3 -0
  155. package/dist/pipeline/repo-snapshot.js.map +1 -1
  156. package/dist/pipeline/role-execution-adapter.d.ts +2 -1
  157. package/dist/pipeline/role-execution-adapter.d.ts.map +1 -1
  158. package/dist/pipeline/role-execution-adapter.js +22 -7
  159. package/dist/pipeline/role-execution-adapter.js.map +1 -1
  160. package/dist/pipeline/skill-loader.d.ts +19 -0
  161. package/dist/pipeline/skill-loader.d.ts.map +1 -1
  162. package/dist/pipeline/skill-loader.js +22 -0
  163. package/dist/pipeline/skill-loader.js.map +1 -1
  164. package/dist/pipeline/skills/coverage-gate.d.ts +44 -0
  165. package/dist/pipeline/skills/coverage-gate.d.ts.map +1 -0
  166. package/dist/pipeline/skills/coverage-gate.js +143 -0
  167. package/dist/pipeline/skills/coverage-gate.js.map +1 -0
  168. package/dist/pipeline/skills/usage-registry.d.ts +48 -0
  169. package/dist/pipeline/skills/usage-registry.d.ts.map +1 -0
  170. package/dist/pipeline/skills/usage-registry.js +55 -0
  171. package/dist/pipeline/skills/usage-registry.js.map +1 -0
  172. package/dist/pipeline/strategy-context.d.ts +20 -0
  173. package/dist/pipeline/strategy-context.d.ts.map +1 -0
  174. package/dist/pipeline/strategy-context.js +55 -0
  175. package/dist/pipeline/strategy-context.js.map +1 -0
  176. package/dist/pipeline/type-defs/artifacts.d.ts +25 -5
  177. package/dist/pipeline/type-defs/artifacts.d.ts.map +1 -1
  178. package/dist/pipeline/type-defs/artifacts.js +4 -0
  179. package/dist/pipeline/type-defs/artifacts.js.map +1 -1
  180. package/dist/pipeline/type-defs/audit.d.ts +25 -13
  181. package/dist/pipeline/type-defs/audit.d.ts.map +1 -1
  182. package/dist/pipeline/type-defs/checks.d.ts +18 -8
  183. package/dist/pipeline/type-defs/checks.d.ts.map +1 -1
  184. package/dist/pipeline/type-defs/checks.js +4 -0
  185. package/dist/pipeline/type-defs/checks.js.map +1 -1
  186. package/dist/pipeline/type-defs/packets.d.ts +104 -18
  187. package/dist/pipeline/type-defs/packets.d.ts.map +1 -1
  188. package/dist/pipeline/type-defs/packets.js +17 -1
  189. package/dist/pipeline/type-defs/packets.js.map +1 -1
  190. package/dist/pipeline/type-defs/state.d.ts +160 -16
  191. package/dist/pipeline/type-defs/state.d.ts.map +1 -1
  192. package/dist/pipeline/type-defs/state.js +26 -1
  193. package/dist/pipeline/type-defs/state.js.map +1 -1
  194. package/dist/shared/text-utils.d.ts +23 -0
  195. package/dist/shared/text-utils.d.ts.map +1 -0
  196. package/dist/shared/text-utils.js +66 -0
  197. package/dist/shared/text-utils.js.map +1 -0
  198. package/dist/shared/website-strategy-format.d.ts +18 -0
  199. package/dist/shared/website-strategy-format.d.ts.map +1 -0
  200. package/dist/shared/website-strategy-format.js +47 -0
  201. package/dist/shared/website-strategy-format.js.map +1 -0
  202. package/dist/state/index.d.ts +2 -0
  203. package/dist/state/index.d.ts.map +1 -1
  204. package/dist/state/index.js +57 -8
  205. package/dist/state/index.js.map +1 -1
  206. package/dist/types/consensus.d.ts +1 -0
  207. package/dist/types/consensus.d.ts.map +1 -1
  208. package/dist/types/consensus.js.map +1 -1
  209. package/dist/types/website-strategy.d.ts +1 -1
  210. package/dist/types/workflow.d.ts +447 -0
  211. package/dist/types/workflow.d.ts.map +1 -1
  212. package/dist/types/workflow.js +3 -0
  213. package/dist/types/workflow.js.map +1 -1
  214. package/dist/upgrade/handlers.d.ts.map +1 -1
  215. package/dist/upgrade/handlers.js +6 -3
  216. package/dist/upgrade/handlers.js.map +1 -1
  217. package/dist/workflow/consensus.d.ts.map +1 -1
  218. package/dist/workflow/consensus.js +1 -0
  219. package/dist/workflow/consensus.js.map +1 -1
  220. package/dist/workflow/website-strategy.d.ts.map +1 -1
  221. package/dist/workflow/website-strategy.js +2 -29
  222. package/dist/workflow/website-strategy.js.map +1 -1
  223. package/dist/workflow/website-updater.d.ts.map +1 -1
  224. package/dist/workflow/website-updater.js +3 -2
  225. package/dist/workflow/website-updater.js.map +1 -1
  226. package/package.json +1 -1
  227. package/src/adapters/gemini.ts +51 -6
  228. package/src/adapters/grok.ts +51 -6
  229. package/src/adapters/openai.ts +53 -5
  230. package/src/cli/commands/create.ts +1 -1
  231. package/src/cli/interactive.ts +333 -19
  232. package/src/generators/all.ts +3 -2
  233. package/src/generators/doc-parser.ts +75 -15
  234. package/src/generators/templates/fullstack.ts +1 -1
  235. package/src/generators/templates/website-components.ts +1 -1
  236. package/src/generators/templates/website-config.ts +23 -11
  237. package/src/generators/templates/website-conversion.ts +1 -1
  238. package/src/generators/templates/website-landing.ts +1 -1
  239. package/src/generators/templates/website-layout.ts +491 -23
  240. package/src/generators/templates/website-pricing.ts +1 -1
  241. package/src/generators/templates/website-sections.ts +1 -1
  242. package/src/generators/templates/website-seo.ts +4 -1
  243. package/src/generators/templates/website.ts +3 -0
  244. package/src/generators/website-content-ai.ts +186 -0
  245. package/src/generators/website-content-scanner.ts +113 -1
  246. package/src/generators/website-context.ts +151 -12
  247. package/src/generators/website-debug.ts +26 -0
  248. package/src/generators/website.ts +28 -3
  249. package/src/pipeline/auto-recovery.ts +283 -0
  250. package/src/pipeline/change-request.ts +63 -1
  251. package/src/pipeline/check-runner.ts +141 -2
  252. package/src/pipeline/command-resolver.ts +34 -2
  253. package/src/pipeline/consensus/arbitrator-query.ts +101 -0
  254. package/src/pipeline/consensus/consensus-runner.ts +1099 -42
  255. package/src/pipeline/cr-lifecycle.ts +103 -0
  256. package/src/pipeline/gate-engine.ts +35 -7
  257. package/src/pipeline/orchestrator.ts +361 -16
  258. package/src/pipeline/packets/consensus-packet-builder.ts +44 -18
  259. package/src/pipeline/phases/architecture.ts +6 -3
  260. package/src/pipeline/phases/audit.ts +6 -3
  261. package/src/pipeline/phases/consensus-architecture.ts +10 -1
  262. package/src/pipeline/phases/consensus-master-plan.ts +10 -3
  263. package/src/pipeline/phases/consensus-role-plans.ts +10 -1
  264. package/src/pipeline/phases/done.ts +15 -4
  265. package/src/pipeline/phases/intake.ts +7 -3
  266. package/src/pipeline/phases/phase-context.ts +6 -1
  267. package/src/pipeline/phases/production-gate.ts +41 -3
  268. package/src/pipeline/phases/qa-validation.ts +51 -5
  269. package/src/pipeline/phases/recovery-loop.ts +229 -7
  270. package/src/pipeline/phases/review.ts +73 -30
  271. package/src/pipeline/phases/role-planning.ts +21 -2
  272. package/src/pipeline/phases/stuck.ts +10 -0
  273. package/src/pipeline/repo-snapshot.ts +3 -0
  274. package/src/pipeline/role-execution-adapter.ts +30 -4
  275. package/src/pipeline/skill-loader.ts +33 -0
  276. package/src/pipeline/skills/coverage-gate.ts +199 -0
  277. package/src/pipeline/skills/usage-registry.ts +87 -0
  278. package/src/pipeline/strategy-context.ts +60 -0
  279. package/src/pipeline/type-defs/artifacts.ts +4 -0
  280. package/src/pipeline/type-defs/checks.ts +4 -0
  281. package/src/pipeline/type-defs/packets.ts +18 -1
  282. package/src/pipeline/type-defs/state.ts +26 -1
  283. package/src/shared/text-utils.ts +70 -0
  284. package/src/shared/website-strategy-format.ts +56 -0
  285. package/src/state/index.ts +60 -8
  286. package/src/types/consensus.ts +1 -0
  287. package/src/types/workflow.ts +6 -0
  288. package/src/upgrade/handlers.ts +9 -3
  289. package/src/workflow/consensus.ts +1 -0
  290. package/src/workflow/website-strategy.ts +2 -36
  291. package/src/workflow/website-updater.ts +4 -2
  292. package/tests/adapters/gemini.test.ts +165 -0
  293. package/tests/adapters/grok.test.ts +137 -0
  294. package/tests/adapters/openai.test.ts +128 -0
  295. package/tests/generators/doc-parser.test.ts +88 -9
  296. package/tests/generators/quality-gate.test.ts +19 -3
  297. package/tests/generators/website-components.test.ts +34 -0
  298. package/tests/generators/website-content-ai.test.ts +308 -0
  299. package/tests/generators/website-content-scanner.test.ts +86 -0
  300. package/tests/generators/website-context.test.ts +3 -2
  301. package/tests/integration/smokestack-scaffold.test.ts +385 -0
  302. package/tests/pipeline/auto-recovery.test.ts +337 -0
  303. package/tests/pipeline/change-request.test.ts +70 -0
  304. package/tests/pipeline/command-resolver.test.ts +42 -0
  305. package/tests/pipeline/consensus/arbitrator-query.test.ts +107 -0
  306. package/tests/pipeline/consensus-runner.test.ts +1333 -10
  307. package/tests/pipeline/consensus-scoring.test.ts +602 -18
  308. package/tests/pipeline/gate-engine.test.ts +34 -0
  309. package/tests/pipeline/install-check.test.ts +261 -0
  310. package/tests/pipeline/orchestrator.test.ts +1506 -15
  311. package/tests/pipeline/packets/builders.test.ts +29 -6
  312. package/tests/pipeline/phases/role-planning.strategy.test.ts +204 -0
  313. package/tests/pipeline/pipeline-persistence.test.ts +230 -0
  314. package/tests/pipeline/recovery-loop-guidance.test.ts +280 -0
  315. package/tests/pipeline/role-execution-adapter.test.ts +88 -0
  316. package/tests/pipeline/skills/coverage-gate.test.ts +370 -0
  317. package/tests/pipeline/skills/usage-registry.test.ts +114 -0
  318. package/tests/pipeline/strategy-context.test.ts +148 -0
  319. package/tests/shared/text-utils.test.ts +155 -0
  320. package/tests/state/progress-analysis.test.ts +375 -0
  321. package/tests/upgrade/handlers.test.ts +33 -2
  322. package/tests/workflow/consensus.test.ts +6 -0
  323. package/tsconfig.json +1 -1
@@ -1,26 +1,45 @@
1
1
  /**
2
2
  * RECOVERY_LOOP phase — self-heal using RCA, not guesswork.
3
3
  * Routes via requires_phase_rewind_to (P1-3). Max 5 iterations.
4
+ * v2.1: Extracts reviewer feedback from consensus failures and
5
+ * builds structured revision directive for sessionGuidance.
4
6
  */
5
7
 
6
- import type { PipelinePhase } from '../types.js';
8
+ import { existsSync, unlinkSync } from 'node:fs';
9
+ import { join } from 'node:path';
10
+
11
+ import type { PipelinePhase, ConsensusPacket, GateCheckResult } from '../types.js';
7
12
  import type { PhaseContext, PhaseResult } from './phase-context.js';
8
13
  import { successResult, failureResult, triggerJournalist } from './phase-context.js';
9
14
  import { buildRCAPacket } from '../packets/rca-packet-builder.js';
10
15
 
11
16
  export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResult> {
12
- const { pipeline, artifactManager, skillLoader } = context;
17
+ const { pipeline, artifactManager, skillLoader, skillUsageRegistry } = context;
13
18
  const artifacts = [];
14
19
 
15
20
  try {
16
- // 1. Load debugger skill
17
- const debuggerSkill = skillLoader.loadSkill('DEBUGGER');
21
+ // 1. Load debugger skill with metadata
22
+ const { definition: debuggerSkill, meta: debuggerMeta } = skillLoader.loadSkillWithMeta('DEBUGGER');
18
23
 
19
24
  // 2. Gather failure evidence
20
25
  const failedPhase = pipeline.failedPhase;
21
26
  const failedGateResult = failedPhase ? pipeline.gateResults[failedPhase] : undefined;
22
27
  const failedChecks = failedPhase ? pipeline.gateChecks[failedPhase] ?? [] : [];
23
28
 
29
+ // 2a. Detect missing module errors in stderr — invalidate install marker
30
+ const combinedStderr = failedChecks
31
+ .filter((c) => c.status === 'fail')
32
+ .map((c) => c.stderr_summary ?? '')
33
+ .join('\n');
34
+ const missingModule = /Cannot find module|ModuleNotFoundError|Failed to resolve import/
35
+ .test(combinedStderr);
36
+ if (missingModule) {
37
+ try {
38
+ const markerPath = join(context.projectDir, '.popeye', 'install-marker.json');
39
+ if (existsSync(markerPath)) unlinkSync(markerPath);
40
+ } catch { /* non-fatal */ }
41
+ }
42
+
24
43
  const failureEvidence = [
25
44
  `Failed phase: ${failedPhase ?? 'unknown'}`,
26
45
  failedGateResult
@@ -31,6 +50,50 @@ export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResul
31
50
  : 'No check failures',
32
51
  ].join('\n');
33
52
 
53
+ // 2b. For consensus failures, build revision directive from reviewer feedback
54
+ if (failedPhase?.startsWith('CONSENSUS_')) {
55
+ const directive = buildRevisionDirective(pipeline, failedPhase);
56
+ if (directive) {
57
+ const existing = pipeline.sessionGuidance ?? '';
58
+ const marker = '--- REVISION DIRECTIVE ---';
59
+ const base = existing.includes(marker)
60
+ ? existing.slice(0, existing.indexOf(marker)).trim()
61
+ : existing;
62
+ pipeline.sessionGuidance = [base, '', marker, directive.slice(0, 3000)].join('\n').trim();
63
+ }
64
+ }
65
+
66
+ // 2c. For QA/build failures, build a targeted fix directive from test stderr
67
+ // so the implementation phase knows exactly what to fix on rewind.
68
+ if (failedPhase === 'QA_VALIDATION' || failedPhase === 'PRODUCTION_GATE'
69
+ || failedPhase === 'IMPLEMENTATION') {
70
+ const failedCheckDetails = failedChecks
71
+ .filter((c) => c.status === 'fail')
72
+ .map((c) => [
73
+ `**${c.check_type}** (exit code ${c.exit_code}):`,
74
+ `Command: \`${c.command}\``,
75
+ c.stderr_summary ? c.stderr_summary.slice(0, 500) : 'No stderr captured',
76
+ ].join('\n'))
77
+ .join('\n\n');
78
+
79
+ if (failedCheckDetails) {
80
+ const existing = pipeline.sessionGuidance ?? '';
81
+ const marker = '--- QA FIX DIRECTIVE ---';
82
+ const base = existing.includes(marker)
83
+ ? existing.slice(0, existing.indexOf(marker)).trim()
84
+ : existing;
85
+ const directive = [
86
+ `Fix the following failures (recovery iteration ${pipeline.recoveryCount}):`,
87
+ '',
88
+ failedCheckDetails,
89
+ '',
90
+ 'Apply targeted fixes only. Do not rewrite code that already works.',
91
+ ].join('\n');
92
+ pipeline.sessionGuidance = [base, '', marker, directive.slice(0, 3000)]
93
+ .join('\n').trim();
94
+ }
95
+ }
96
+
34
97
  // 3. Generate RCA via Claude with Debugger skill
35
98
  const { executePrompt } = await import('../../adapters/claude.js');
36
99
  const guidance = pipeline.sessionGuidance;
@@ -54,6 +117,33 @@ export async function runRecoveryLoop(context: PhaseContext): Promise<PhaseResul
54
117
  const rcaResult = await executePrompt(rcaPrompt);
55
118
  const rcaResponse = rcaResult.response;
56
119
 
120
+ // Record skill usage — debugger skill injected into RCA prompt
121
+ skillUsageRegistry.record('DEBUGGER', 'RECOVERY_LOOP', 'system_prompt', debuggerMeta.source, debuggerMeta.version);
122
+
123
+ // 3b. Build recovery micro-plan from RCA + stderr
124
+ const microPlan = buildRecoveryMicroPlan(rcaResponse, failedChecks, pipeline.recoveryCount);
125
+
126
+ // 3c. Store micro-plan as artifact for traceability
127
+ const microPlanEntry = artifactManager.createAndStoreText(
128
+ 'recovery_fix_plan',
129
+ microPlan.plan,
130
+ 'RECOVERY_LOOP',
131
+ );
132
+ artifacts.push(microPlanEntry);
133
+
134
+ // 3d. Inject micro-plan into sessionGuidance (replaces any prior fix directive)
135
+ {
136
+ const existing = pipeline.sessionGuidance ?? '';
137
+ const markers = ['--- QA FIX DIRECTIVE ---', '--- RECOVERY FIX PLAN ---'];
138
+ let base = existing;
139
+ for (const m of markers) {
140
+ if (base.includes(m)) base = base.slice(0, base.indexOf(m)).trim();
141
+ }
142
+ pipeline.sessionGuidance = [
143
+ base, '', '--- RECOVERY FIX PLAN ---', microPlan.plan.slice(0, 3000),
144
+ ].join('\n').trim();
145
+ }
146
+
57
147
  // 4. Build RCA packet
58
148
  const rcaPacket = buildRCAPacket({
59
149
  incidentSummary: `Gate failure at ${failedPhase ?? 'unknown'} (recovery iteration ${pipeline.recoveryCount})`,
@@ -103,18 +193,150 @@ function determineRewindTarget(
103
193
  _rcaResponse: string,
104
194
  failedPhase: PipelinePhase | undefined,
105
195
  ): PipelinePhase | undefined {
106
- // If the failure was in production gate or audit, rewind to implementation
107
196
  if (failedPhase === 'PRODUCTION_GATE' || failedPhase === 'AUDIT') {
108
197
  return 'IMPLEMENTATION';
109
198
  }
110
- // If in QA, rewind to implementation
111
199
  if (failedPhase === 'QA_VALIDATION') {
112
200
  return 'IMPLEMENTATION';
113
201
  }
114
- // For consensus failures, rewind to the phase being validated
115
202
  if (failedPhase === 'CONSENSUS_MASTER_PLAN') return 'INTAKE';
116
203
  if (failedPhase === 'CONSENSUS_ARCHITECTURE') return 'ARCHITECTURE';
117
204
  if (failedPhase === 'CONSENSUS_ROLE_PLANS') return 'ROLE_PLANNING';
118
205
 
119
206
  return undefined;
120
207
  }
208
+
209
+ /**
210
+ * Extract reviewer feedback from the latest consensus artifact and
211
+ * build a structured revision directive for the planner.
212
+ *
213
+ * Uses Set<string> for dedup. Output is capped at 3000 chars by caller.
214
+ */
215
+ function buildRevisionDirective(
216
+ pipeline: { artifacts: Array<{ type: string; content?: unknown }> },
217
+ failedPhase: string,
218
+ ): string | null {
219
+ // Find the latest consensus artifact for this phase
220
+ const consensusArtifacts = pipeline.artifacts.filter(
221
+ (a) => a.type === 'consensus' && a.content,
222
+ );
223
+ if (consensusArtifacts.length === 0) return null;
224
+
225
+ const latest = consensusArtifacts[consensusArtifacts.length - 1];
226
+ const packet = latest.content as ConsensusPacket | undefined;
227
+ if (!packet?.reviewer_votes) return null;
228
+
229
+ const blockers = new Set<string>();
230
+ const required = new Set<string>();
231
+ const suggestions = new Set<string>();
232
+
233
+ for (const vote of packet.reviewer_votes) {
234
+ for (const issue of vote.blocking_issues) {
235
+ const trimmed = issue.trim();
236
+ if (trimmed) blockers.add(trimmed);
237
+ }
238
+ for (const change of (vote.required_changes ?? [])) {
239
+ const trimmed = change.trim();
240
+ if (trimmed) required.add(trimmed);
241
+ }
242
+ for (const suggestion of vote.suggestions) {
243
+ const trimmed = suggestion.trim();
244
+ if (trimmed) suggestions.add(trimmed);
245
+ }
246
+ }
247
+
248
+ if (blockers.size === 0 && required.size === 0 && suggestions.size === 0) return null;
249
+
250
+ const lines: string[] = [
251
+ `Revise the plan to address reviewer feedback from ${failedPhase}:`,
252
+ '',
253
+ ];
254
+
255
+ if (blockers.size > 0) {
256
+ lines.push('BLOCKING (must fix):');
257
+ for (const b of blockers) lines.push(`- ${b}`);
258
+ lines.push('');
259
+ }
260
+
261
+ if (required.size > 0) {
262
+ lines.push('REQUIRED CHANGES:');
263
+ for (const r of required) lines.push(`- ${r}`);
264
+ lines.push('');
265
+ }
266
+
267
+ if (suggestions.size > 0) {
268
+ lines.push('SUGGESTIONS:');
269
+ for (const s of suggestions) lines.push(`- ${s}`);
270
+ lines.push('');
271
+ }
272
+
273
+ lines.push('Keep existing plan structure. Apply targeted revisions only.');
274
+
275
+ return lines.join('\n');
276
+ }
277
+
278
+ /**
279
+ * Build a structured micro-fix plan from RCA + test failures.
280
+ * Returns the plan text and a risk assessment for consensus gating.
281
+ *
282
+ * Heuristics:
283
+ * - Low risk (skip consensus): <=3 files, no schema/dep/config changes
284
+ * - Medium risk (consensus): schema, dependency, or build config changes
285
+ * - High risk (consensus): API changes, >5 files
286
+ */
287
+ function buildRecoveryMicroPlan(
288
+ rcaResponse: string,
289
+ failedChecks: GateCheckResult[],
290
+ recoveryCount: number,
291
+ ): { plan: string; needsConsensus: boolean; riskLevel: 'low' | 'medium' | 'high' } {
292
+ const stderrLines = failedChecks
293
+ .filter((c) => c.status === 'fail')
294
+ .map((c) => c.stderr_summary ?? '')
295
+ .join('\n');
296
+
297
+ // Combine RCA + stderr for signal extraction
298
+ const combined = rcaResponse + '\n' + stderrLines;
299
+ const combinedLower = combined.toLowerCase();
300
+
301
+ const touchesSchema = /schema|migration|prisma|drizzle|typeorm|knex/.test(combinedLower);
302
+ const touchesDeps = /package\.json|requirements\.txt|dependency|npm install|pip install/
303
+ .test(combinedLower);
304
+ const touchesConfig =
305
+ /tsconfig\.json|vite\.config|jest\.config|vitest\.config|docker-compose|\.github\/workflows/
306
+ .test(combinedLower);
307
+ const touchesApi = /api route|endpoint|public api|breaking change/.test(combinedLower);
308
+
309
+ // Extract file paths from BOTH RCA and stderr
310
+ const mentionedFiles = combined.match(/[\w/.-]+\.(ts|tsx|js|jsx|py|sql|prisma)/gi) ?? [];
311
+ const uniqueFiles = [...new Set(mentionedFiles)];
312
+
313
+ let riskLevel: 'low' | 'medium' | 'high' = 'low';
314
+ if (touchesApi || uniqueFiles.length > 5) riskLevel = 'high';
315
+ else if (touchesSchema || touchesDeps || touchesConfig) riskLevel = 'medium';
316
+
317
+ // Only require consensus for medium+ risk on second+ attempt
318
+ const needsConsensus = riskLevel !== 'low' && recoveryCount >= 2;
319
+
320
+ const plan = [
321
+ `# Recovery Fix Plan (iteration ${recoveryCount})`,
322
+ '',
323
+ `**Risk level:** ${riskLevel}`,
324
+ `**Files likely affected:** ${uniqueFiles.length > 0 ? uniqueFiles.join(', ') : 'unknown'}`,
325
+ `**Consensus required:** ${needsConsensus ? 'yes' : 'no'}`,
326
+ '',
327
+ '## Root Cause Summary',
328
+ rcaResponse.slice(0, 800),
329
+ '',
330
+ '## Test Failures',
331
+ stderrLines.slice(0, 1000) || 'No stderr captured',
332
+ '',
333
+ '## Fix Checklist',
334
+ '- [ ] Address root cause identified above',
335
+ '- [ ] Fix failing test assertions',
336
+ '- [ ] Verify no regressions in passing tests',
337
+ touchesSchema ? '- [ ] Review schema/migration changes for correctness' : '',
338
+ touchesDeps ? '- [ ] Verify dependency changes are necessary and compatible' : '',
339
+ ].filter(Boolean).join('\n');
340
+
341
+ return { plan, needsConsensus, riskLevel };
342
+ }
@@ -8,12 +8,12 @@ import type { PhaseContext, PhaseResult } from './phase-context.js';
8
8
  import { successResult, failureResult } from './phase-context.js';
9
9
  import { generateRepoSnapshot, createSnapshotArtifact, diffSnapshots } from '../repo-snapshot.js';
10
10
  import type { RepoSnapshot, ChangeRequest } from '../types.js';
11
- import { buildChangeRequest, formatChangeRequest, routeChangeRequest } from '../change-request.js';
11
+ import { buildChangeRequest, formatChangeRequest, routeChangeRequest, computeDriftKey, isDuplicateCR } from '../change-request.js';
12
12
  import { existsSync, readFileSync } from 'node:fs';
13
13
  import { join } from 'node:path';
14
14
 
15
15
  export async function runReview(context: PhaseContext): Promise<PhaseResult> {
16
- const { pipeline, artifactManager, projectDir } = context;
16
+ const { pipeline, artifactManager, skillLoader, skillUsageRegistry, projectDir } = context;
17
17
  const artifacts = [];
18
18
  const changeRequests: ChangeRequest[] = [];
19
19
 
@@ -24,11 +24,18 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
24
24
  artifacts.push(snapshotEntry);
25
25
  pipeline.latestRepoSnapshot = artifactManager.toArtifactRef(snapshotEntry);
26
26
 
27
- // 2. Find role-plan-approval snapshot for drift detection
28
- const rolePlanSnapshots = pipeline.artifacts.filter(
29
- (a) => a.type === 'repo_snapshot' && a.phase === 'CONSENSUS_ROLE_PLANS',
30
- );
31
- const baselineSnapshot = rolePlanSnapshots[rolePlanSnapshots.length - 1];
27
+ // 2. Find baseline snapshot for drift detection
28
+ // v2.4.9: Check baselineSnapshotOverride first (set after config CR resolved)
29
+ let baselineSnapshot = pipeline.baselineSnapshotOverride
30
+ ? pipeline.artifacts.find((a) => a.id === pipeline.baselineSnapshotOverride!.artifact_id)
31
+ : undefined;
32
+ // Fallback to CONSENSUS_ROLE_PLANS snapshot
33
+ if (!baselineSnapshot) {
34
+ const rolePlanSnapshots = pipeline.artifacts.filter(
35
+ (a) => a.type === 'repo_snapshot' && a.phase === 'CONSENSUS_ROLE_PLANS',
36
+ );
37
+ baselineSnapshot = rolePlanSnapshots[rolePlanSnapshots.length - 1];
38
+ }
32
39
 
33
40
  let driftReport = 'No baseline snapshot found for drift detection.';
34
41
  let hasDrift = false;
@@ -53,32 +60,62 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
53
60
  ].filter(Boolean).join('\n');
54
61
 
55
62
  // v1.1: Create change requests for detected drift
63
+ // v2.4.9: Deduplicate — skip if an active CR with the same drift_key exists
56
64
  if (diff.changed_configs.length > 0) {
57
- const cr = buildChangeRequest({
58
- originPhase: 'REVIEW',
59
- requestedBy: 'REVIEWER',
60
- changeType: 'config',
61
- description: `Config files changed during implementation: ${diff.changed_configs.join(', ')}`,
62
- justification: 'Detected by snapshot diff during review phase',
63
- affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
64
- affectedPhases: ['IMPLEMENTATION', 'QA_VALIDATION'],
65
- riskLevel: diff.changed_configs.length > 3 ? 'high' : 'medium',
65
+ const configHashPairs = diff.changed_configs.map((cfgPath) => {
66
+ const before = baselineData.config_files?.find((f) => f.path === cfgPath)?.content_hash ?? 'unknown';
67
+ const after = currentSnapshot.config_files?.find((f) => f.path === cfgPath)?.content_hash ?? 'unknown';
68
+ return `${cfgPath}:${before}->${after}`;
66
69
  });
67
- changeRequests.push(cr);
70
+ const configDriftKey = computeDriftKey(
71
+ 'config',
72
+ baselineSnapshot!.id,
73
+ diff.changed_configs,
74
+ configHashPairs,
75
+ );
76
+
77
+ if (!isDuplicateCR(pipeline.pendingChangeRequests, configDriftKey)) {
78
+ const cr = buildChangeRequest({
79
+ originPhase: 'REVIEW',
80
+ requestedBy: 'REVIEWER',
81
+ changeType: 'config',
82
+ description: `Config files changed during implementation: ${diff.changed_configs.join(', ')}`,
83
+ justification: 'Detected by snapshot diff during review phase',
84
+ affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
85
+ affectedPhases: ['IMPLEMENTATION', 'QA_VALIDATION'],
86
+ riskLevel: diff.changed_configs.length > 3 ? 'high' : 'medium',
87
+ driftKey: configDriftKey,
88
+ });
89
+ changeRequests.push(cr);
90
+ }
68
91
  }
69
92
 
70
- if (Math.abs(diff.lines_delta) > 1000) {
71
- const cr = buildChangeRequest({
72
- originPhase: 'REVIEW',
73
- requestedBy: 'REVIEWER',
74
- changeType: 'scope',
75
- description: `Significant scope drift detected: ${diff.lines_delta > 0 ? '+' : ''}${diff.lines_delta} lines`,
76
- justification: 'Large line delta suggests scope changes beyond approved plans',
77
- affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
78
- affectedPhases: ['CONSENSUS_MASTER_PLAN', 'IMPLEMENTATION'],
79
- riskLevel: 'high',
80
- });
81
- changeRequests.push(cr);
93
+ // v2.5.1: Only check scope drift on revision-over-revision passes.
94
+ // On first pass, baseline = CONSENSUS_ROLE_PLANS (pre-implementation),
95
+ // so large line deltas are expected from implementation — not scope drift.
96
+ const isRevisionComparison = !!pipeline.baselineSnapshotOverride;
97
+ if (isRevisionComparison && Math.abs(diff.lines_delta) > 1000) {
98
+ const scopeDriftKey = computeDriftKey(
99
+ 'scope',
100
+ baselineSnapshot!.id,
101
+ [`lines_delta:${diff.lines_delta}`],
102
+ [],
103
+ );
104
+
105
+ if (!isDuplicateCR(pipeline.pendingChangeRequests, scopeDriftKey)) {
106
+ const cr = buildChangeRequest({
107
+ originPhase: 'REVIEW',
108
+ requestedBy: 'REVIEWER',
109
+ changeType: 'scope',
110
+ description: `Significant scope drift detected: ${diff.lines_delta > 0 ? '+' : ''}${diff.lines_delta} lines`,
111
+ justification: 'Large line delta suggests scope changes beyond approved plans',
112
+ affectedArtifacts: [artifactManager.toArtifactRef(snapshotEntry)],
113
+ affectedPhases: ['CONSENSUS_MASTER_PLAN', 'IMPLEMENTATION'],
114
+ riskLevel: 'high',
115
+ driftKey: scopeDriftKey,
116
+ });
117
+ changeRequests.push(cr);
118
+ }
82
119
  }
83
120
  } else {
84
121
  driftReport = 'No drift detected between approved plans and implementation.';
@@ -107,10 +144,15 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
107
144
  change_type: cr.change_type,
108
145
  target_phase: routeChangeRequest(cr),
109
146
  status: 'proposed',
147
+ drift_key: cr.drift_key,
110
148
  });
111
149
  }
112
150
 
113
- // 4. Create review decision artifact
151
+ // 3b. Load REVIEWER skill and record usage for skill-guided review
152
+ const { definition: reviewerSkill, meta: reviewerMeta } = skillLoader.loadSkillWithMeta('REVIEWER');
153
+ skillUsageRegistry.record('REVIEWER', 'REVIEW', 'review_prompt', reviewerMeta.source, reviewerMeta.version);
154
+
155
+ // 4. Create review decision artifact (skill-guided)
114
156
  const reviewDoc = [
115
157
  '# Review Decision',
116
158
  '',
@@ -125,6 +167,7 @@ export async function runReview(context: PhaseContext): Promise<PhaseResult> {
125
167
  changeRequests.length > 0 ? '## Change Requests\n' + changeRequests.map((cr) => `- ${cr.cr_id}: ${cr.description}`).join('\n') : '',
126
168
  '',
127
169
  '## Plan Alignment',
170
+ `Reviewed using ${reviewerSkill.role} skill (v${reviewerSkill.version}).`,
128
171
  'Implementation reviewed against approved role plans.',
129
172
  '',
130
173
  '## Decision',
@@ -9,6 +9,7 @@ import { join } from 'node:path';
9
9
  import type { PipelineRole } from '../types.js';
10
10
  import type { PhaseContext, PhaseResult } from './phase-context.js';
11
11
  import { successResult, failureResult } from './phase-context.js';
12
+ import { STRATEGY_ROLES, loadStrategyForRole } from '../strategy-context.js';
12
13
 
13
14
  /** Roles that produce implementation plans */
14
15
  const PLANNING_ROLES: PipelineRole[] = [
@@ -16,11 +17,14 @@ const PLANNING_ROLES: PipelineRole[] = [
16
17
  'BACKEND_PROGRAMMER',
17
18
  'FRONTEND_PROGRAMMER',
18
19
  'WEBSITE_PROGRAMMER',
20
+ 'UI_UX_SPECIALIST',
21
+ 'MARKETING_EXPERT',
22
+ 'SOCIAL_EXPERT',
19
23
  'QA_TESTER',
20
24
  ];
21
25
 
22
26
  export async function runRolePlanning(context: PhaseContext): Promise<PhaseResult> {
23
- const { pipeline, artifactManager, skillLoader, projectDir } = context;
27
+ const { pipeline, artifactManager, skillLoader, skillUsageRegistry, projectDir } = context;
24
28
  const artifacts = [];
25
29
 
26
30
  try {
@@ -46,13 +50,16 @@ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResul
46
50
 
47
51
  const { executePrompt } = await import('../../adapters/claude.js');
48
52
 
53
+ // Load strategy once for all roles
54
+ const strategyBlock = loadStrategyForRole(projectDir);
55
+
49
56
  // Generate plan for each role (skip roles not in activeRoles)
50
57
  for (const role of PLANNING_ROLES) {
51
58
  if (!pipeline.activeRoles.includes(role)) {
52
59
  continue;
53
60
  }
54
61
 
55
- const skill = skillLoader.loadSkill(role);
62
+ const { definition: skill, meta } = skillLoader.loadSkillWithMeta(role);
56
63
 
57
64
  const planPrompt = [
58
65
  skill.systemPrompt,
@@ -63,6 +70,10 @@ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResul
63
70
  '## Architecture',
64
71
  architectureContent.slice(0, 5000),
65
72
  '',
73
+ // Conditionally inject strategy for website/marketing/social roles
74
+ ...(strategyBlock && STRATEGY_ROLES.includes(role)
75
+ ? ['## Website Marketing Strategy (authoritative reference)', strategyBlock, '']
76
+ : []),
66
77
  '## Instructions',
67
78
  `Create your ${role} implementation plan. Include:`,
68
79
  '- Deterministic file-level outputs',
@@ -74,6 +85,14 @@ export async function runRolePlanning(context: PhaseContext): Promise<PhaseResul
74
85
  const planResult = await executePrompt(planPrompt);
75
86
  const plan = planResult.response;
76
87
 
88
+ // Record skill usage — role skill injected into planning prompt
89
+ skillUsageRegistry.record(role, 'ROLE_PLANNING', 'planning_prompt', meta.source, meta.version);
90
+
91
+ // Record strategy context usage when injected
92
+ if (strategyBlock && STRATEGY_ROLES.includes(role)) {
93
+ skillUsageRegistry.record(role, 'ROLE_PLANNING', 'strategy_context', 'disk', '1');
94
+ }
95
+
77
96
  const entry = artifactManager.createAndStoreText(
78
97
  'role_plan',
79
98
  `# ${role} Plan\n\n${plan}`,
@@ -36,6 +36,16 @@ export async function runStuck(context: PhaseContext): Promise<PhaseResult> {
36
36
  '- Determine if scope changes are needed',
37
37
  '- Decide whether to restart pipeline from a specific phase',
38
38
  '',
39
+ // v2.6.0: Auto-recovery attempt details
40
+ ...(pipeline.autoRecoveryResult ? [
41
+ '## Auto-Recovery Attempt',
42
+ `Result: ${pipeline.autoRecoveryResult}`,
43
+ (() => {
44
+ const autoArtifact = pipeline.artifacts.find(a => a.type === 'auto_recovery_guidance');
45
+ return autoArtifact ? `Guidance: ${autoArtifact.path}` : 'No guidance artifact generated.';
46
+ })(),
47
+ '',
48
+ ] : []),
39
49
  '## Artifacts That May Need Update',
40
50
  ...pipeline.artifacts
41
51
  .filter((a) => a.phase === pipeline.failedPhase)
@@ -37,6 +37,8 @@ const CONFIG_FILES = new Set([
37
37
  'prisma/schema.prisma', 'alembic.ini',
38
38
  'requirements.txt', 'setup.py', 'setup.cfg',
39
39
  'Makefile', 'Procfile',
40
+ 'poetry.lock', 'package-lock.json', 'pnpm-lock.yaml',
41
+ 'yarn.lock', 'pnpm-workspace.yaml',
40
42
  ]);
41
43
 
42
44
  const CODE_EXTENSIONS = new Set([
@@ -223,6 +225,7 @@ function extractKeyFields(configName: string, content: string): Record<string, u
223
225
  scripts: pkg.scripts,
224
226
  dependencies: pkg.dependencies ? Object.keys(pkg.dependencies) : [],
225
227
  devDependencies: pkg.devDependencies ? Object.keys(pkg.devDependencies) : [],
228
+ workspaces: pkg.workspaces,
226
229
  };
227
230
  }
228
231
 
@@ -16,6 +16,8 @@ import type {
16
16
  ArtifactEntry,
17
17
  } from './types.js';
18
18
  import type { SkillLoader, SkillDefinition } from './skill-loader.js';
19
+ import type { SkillUsageRegistry } from './skills/usage-registry.js';
20
+ import { STRATEGY_ROLES, loadStrategyForRole } from './strategy-context.js';
19
21
 
20
22
  // ─── Types ───────────────────────────────────────────────
21
23
 
@@ -65,8 +67,12 @@ export function buildRoleExecutionContext(
65
67
  const allowedPaths = extractAllowedPaths(planContent, role);
66
68
  const forbiddenPatterns = extractForbiddenPatterns(role);
67
69
 
68
- // Build system prompt combining skill + role constraints
69
- const systemPrompt = buildRoleSystemPrompt(role, skill, planContent, forbiddenPatterns);
70
+ // Load strategy for roles that need it
71
+ const strategyBlock = STRATEGY_ROLES.includes(role)
72
+ ? loadStrategyForRole(projectDir) : undefined;
73
+
74
+ // Build system prompt combining skill + role constraints + optional strategy
75
+ const systemPrompt = buildRoleSystemPrompt(role, skill, planContent, forbiddenPatterns, strategyBlock);
70
76
 
71
77
  return {
72
78
  role,
@@ -114,6 +120,7 @@ export function buildAllRoleContexts(
114
120
  pipeline: PipelineState,
115
121
  skillLoader: SkillLoader,
116
122
  projectDir: string,
123
+ skillUsageRegistry?: SkillUsageRegistry,
117
124
  ): Map<PipelineRole, RoleExecutionContext> {
118
125
  const contexts = new Map<PipelineRole, RoleExecutionContext>();
119
126
 
@@ -129,8 +136,18 @@ export function buildAllRoleContexts(
129
136
  const role = detectRoleFromPlan(content, pipeline.activeRoles);
130
137
  if (!role) continue;
131
138
 
132
- const skill = skillLoader.loadSkill(role);
139
+ const { definition: skill, meta } = skillLoader.loadSkillWithMeta(role);
133
140
  contexts.set(role, buildRoleExecutionContext(role, skill, rolePlan, projectDir));
141
+
142
+ // Record skill usage — role skill injected into execution context
143
+ if (skillUsageRegistry) {
144
+ skillUsageRegistry.record(role, 'IMPLEMENTATION', 'role_context', meta.source, meta.version);
145
+
146
+ // Record strategy context usage for roles that receive it
147
+ if (STRATEGY_ROLES.includes(role) && loadStrategyForRole(projectDir)) {
148
+ skillUsageRegistry.record(role, 'IMPLEMENTATION', 'strategy_context', 'disk', '1');
149
+ }
150
+ }
134
151
  }
135
152
 
136
153
  return contexts;
@@ -189,6 +206,7 @@ function buildRoleSystemPrompt(
189
206
  skill: SkillDefinition,
190
207
  planContent: string,
191
208
  forbiddenPatterns: string[],
209
+ strategyContext?: string,
192
210
  ): string {
193
211
  const lines = [
194
212
  `# Role: ${role}`,
@@ -199,9 +217,17 @@ function buildRoleSystemPrompt(
199
217
  '## Your Approved Role Plan',
200
218
  planContent.slice(0, 4000),
201
219
  '',
220
+ ];
221
+
222
+ // Inject strategy after plan, before constraints
223
+ if (strategyContext) {
224
+ lines.push('## Website Marketing Strategy', strategyContext, '');
225
+ }
226
+
227
+ lines.push(
202
228
  '## Constraints',
203
229
  ...skill.constraints.map((c) => `- ${c}`),
204
- ];
230
+ );
205
231
 
206
232
  if (forbiddenPatterns.length > 0) {
207
233
  lines.push(
@@ -9,6 +9,7 @@ import { join } from 'node:path';
9
9
 
10
10
  import type { PipelineRole } from './types.js';
11
11
  import { DEFAULT_SKILLS } from './skills/defaults.js';
12
+ import type { SkillSource } from './skills/usage-registry.js';
12
13
 
13
14
  // ─── Types ───────────────────────────────────────────────
14
15
 
@@ -21,6 +22,14 @@ export interface SkillDefinition {
21
22
  depends_on?: PipelineRole[];
22
23
  }
23
24
 
25
+ export interface SkillLoadResult {
26
+ definition: SkillDefinition;
27
+ meta: {
28
+ source: SkillSource;
29
+ version?: string;
30
+ };
31
+ }
32
+
24
33
  // ─── Skill Loader ────────────────────────────────────────
25
34
 
26
35
  export class SkillLoader {
@@ -54,6 +63,30 @@ export class SkillLoader {
54
63
  return merged;
55
64
  }
56
65
 
66
+ /**
67
+ * Load skill definition with metadata about the source.
68
+ * Returns both the merged skill and info about where it came from.
69
+ *
70
+ * Args:
71
+ * role: The pipeline role to load a skill for.
72
+ *
73
+ * Returns:
74
+ * SkillLoadResult with definition and source metadata.
75
+ */
76
+ loadSkillWithMeta(role: PipelineRole): SkillLoadResult {
77
+ const definition = this.loadSkill(role);
78
+ const override = this.loadMarkdownOverride(role);
79
+ const source: SkillSource = override?.systemPrompt ? 'project_override' : 'defaults';
80
+
81
+ return {
82
+ definition,
83
+ meta: {
84
+ source,
85
+ version: definition.version,
86
+ },
87
+ };
88
+ }
89
+
57
90
  /** Load all skills for the given roles */
58
91
  loadAllSkills(roles: PipelineRole[]): Map<PipelineRole, SkillDefinition> {
59
92
  const result = new Map<PipelineRole, SkillDefinition>();