ultimate-pi 0.18.1 → 0.19.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 (284) hide show
  1. package/.agents/skills/harness-debate-plan/SKILL.md +1 -1
  2. package/.agents/skills/harness-decisions/SKILL.md +1 -2
  3. package/.agents/skills/harness-governor/SKILL.md +6 -5
  4. package/.pi/PACKAGING.md +4 -4
  5. package/.pi/SYSTEM.md +54 -120
  6. package/.pi/agents/harness/incident-recorder.md +0 -1
  7. package/.pi/agents/harness/planning/decompose.md +0 -2
  8. package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
  9. package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
  10. package/.pi/agents/harness/planning/hypothesis.md +0 -2
  11. package/.pi/agents/harness/planning/implementation-researcher.md +0 -2
  12. package/.pi/agents/harness/planning/plan-adversary.md +0 -2
  13. package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
  14. package/.pi/agents/harness/planning/planning-context.md +0 -2
  15. package/.pi/agents/harness/planning/review-integrator.md +0 -2
  16. package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
  17. package/.pi/agents/harness/planning/stack-researcher.md +0 -2
  18. package/.pi/agents/harness/reviewing/adversary.md +0 -2
  19. package/.pi/agents/harness/reviewing/evaluator.md +0 -2
  20. package/.pi/agents/harness/reviewing/tie-breaker.md +0 -2
  21. package/.pi/agents/harness/running/executor.md +0 -2
  22. package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
  23. package/.pi/agents/harness/sentrux-steward.md +0 -2
  24. package/.pi/agents/harness/trace-librarian.md +0 -1
  25. package/.pi/extensions/00-posthog-network-bootstrap.ts +1 -1
  26. package/.pi/extensions/agt-kill-switch.ts +57 -0
  27. package/.pi/extensions/agt-prompt-guard.ts +32 -0
  28. package/.pi/extensions/custom-footer.ts +46 -145
  29. package/.pi/extensions/custom-header.ts +1 -1
  30. package/.pi/extensions/custom-system-prompt.ts +1 -1
  31. package/.pi/extensions/debate-orchestrator.ts +6 -6
  32. package/.pi/extensions/harness-ask-user.ts +7 -7
  33. package/.pi/extensions/harness-debate-tools.ts +26 -42
  34. package/.pi/extensions/harness-lens.ts +94 -0
  35. package/.pi/extensions/harness-plan-approval.ts +11 -11
  36. package/.pi/extensions/harness-run-context.ts +1070 -876
  37. package/.pi/extensions/harness-subagent-governance.ts +8 -0
  38. package/.pi/extensions/harness-subagent-submit.ts +34 -163
  39. package/.pi/extensions/harness-subagents.ts +3 -3
  40. package/.pi/extensions/harness-telemetry.ts +2 -2
  41. package/.pi/extensions/harness-web-tools.ts +2 -2
  42. package/.pi/extensions/policy-gate.ts +25 -5
  43. package/.pi/extensions/sentrux-rules-sync.ts +1 -1
  44. package/.pi/extensions/subagent-governance.ts +92 -0
  45. package/.pi/extensions/trace-recorder.ts +1 -1
  46. package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
  47. package/.pi/harness/README.md +6 -2
  48. package/.pi/harness/agents.manifest.json +22 -25
  49. package/.pi/harness/agents.policy.yaml +275 -0
  50. package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
  51. package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
  52. package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
  53. package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
  54. package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
  55. package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
  56. package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
  57. package/.pi/harness/docs/adrs/README.md +5 -0
  58. package/.pi/harness/evolution/README.md +1 -2
  59. package/.pi/harness/examples/agents.policy.project.yaml +19 -0
  60. package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
  61. package/.pi/harness/policies/bash-denylists.yaml +5 -0
  62. package/.pi/harness/policies/defaults.yaml +51 -0
  63. package/.pi/harness/policies/orchestrator.yaml +18 -0
  64. package/.pi/harness/policies/phases.yaml +10 -0
  65. package/.pi/harness/policies/roles.yaml +5 -0
  66. package/.pi/harness/policies/web-guard.yaml +5 -0
  67. package/.pi/harness/policies/workflow-sequences.yaml +9 -0
  68. package/.pi/harness/sentrux/architecture.manifest.json +26 -4
  69. package/.pi/harness/specs/observation.schema.json +2 -1
  70. package/.pi/lib/agents-policy.d.mts +70 -0
  71. package/.pi/lib/agents-policy.mjs +325 -0
  72. package/.pi/lib/agents-policy.ts +19 -0
  73. package/.pi/lib/agt/audit-run-sink.ts +52 -0
  74. package/.pi/lib/agt/build-evaluation-context.ts +285 -0
  75. package/.pi/lib/agt/config.ts +28 -0
  76. package/.pi/lib/agt/delegation.ts +69 -0
  77. package/.pi/lib/agt/evaluate-policy.ts +56 -0
  78. package/.pi/lib/agt/identity-registry.ts +41 -0
  79. package/.pi/lib/agt/index.ts +55 -0
  80. package/.pi/lib/agt/kill-switch-state.ts +11 -0
  81. package/.pi/lib/agt/legacy-evaluate.ts +101 -0
  82. package/.pi/lib/agt/policy-engine.ts +154 -0
  83. package/.pi/lib/agt/rings.ts +21 -0
  84. package/.pi/lib/agt/sre-hooks.ts +45 -0
  85. package/.pi/lib/agt/trust-run-store.ts +26 -0
  86. package/.pi/lib/agt/workflow-history.ts +29 -0
  87. package/.pi/lib/agt-governance-active.ts +14 -0
  88. package/.pi/lib/agt-tool-guard.ts +78 -0
  89. package/.pi/lib/ask-user/dialog.ts +314 -0
  90. package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
  91. package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
  92. package/.pi/{extensions/lib → lib}/extension-load-guard.ts +13 -2
  93. package/.pi/lib/harness-agt-tool-guard.ts +5 -0
  94. package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +1 -1
  95. package/.pi/lib/harness-debate-core-deps.ts +14 -0
  96. package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
  97. package/.pi/lib/harness-lens/.gitattributes +1 -0
  98. package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
  99. package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
  100. package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
  101. package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
  102. package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
  103. package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
  104. package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
  105. package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
  106. package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
  107. package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
  108. package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
  109. package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
  110. package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
  111. package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
  112. package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
  113. package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
  114. package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
  115. package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
  116. package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
  117. package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
  118. package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
  119. package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
  120. package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
  121. package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
  122. package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
  123. package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
  124. package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
  125. package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
  126. package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
  127. package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
  128. package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
  129. package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
  130. package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
  131. package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
  132. package/.pi/lib/harness-lens/clients/types.ts +59 -0
  133. package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
  134. package/.pi/lib/harness-lens/index.ts +532 -0
  135. package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
  136. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
  137. package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
  138. package/.pi/lib/harness-run-context-responses.ts +9 -0
  139. package/.pi/lib/harness-run-context.ts +0 -2
  140. package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +1 -0
  141. package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +1 -1
  142. package/.pi/lib/harness-subagent-auth.ts +51 -0
  143. package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +10 -7
  144. package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
  145. package/.pi/lib/harness-subagent-submit-register.ts +163 -0
  146. package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -37
  147. package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +53 -14
  148. package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
  149. package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
  150. package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
  151. package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
  152. package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
  153. package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
  154. package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
  155. package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
  156. package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
  157. package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
  158. package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
  159. package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
  160. package/.pi/prompts/harness-plan.md +1 -1
  161. package/.pi/prompts/harness-setup.md +37 -64
  162. package/.pi/scripts/README.md +2 -5
  163. package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
  164. package/.pi/scripts/harness-agents-manifest.mjs +60 -3
  165. package/.pi/scripts/harness-agt-doctor.ts +36 -0
  166. package/.pi/scripts/harness-cli-verify.sh +9 -2
  167. package/.pi/scripts/harness-verify.mjs +113 -39
  168. package/.pi/scripts/harness-web-policy-guard.mjs +2 -2
  169. package/.pi/scripts/validate-plan-dag.mjs +65 -74
  170. package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
  171. package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
  172. package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
  173. package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
  174. package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
  175. package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
  176. package/.pi/skills/architecture/layered/SKILL.md +68 -0
  177. package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
  178. package/.pi/skills/architecture/microservices/SKILL.md +64 -0
  179. package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
  180. package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
  181. package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
  182. package/.pi/skills/architecture/service-based/SKILL.md +64 -0
  183. package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
  184. package/.pi/skills/architecture/space-based/SKILL.md +60 -0
  185. package/.pi/skills/ast-grep/SKILL.md +40 -321
  186. package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
  187. package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
  188. package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
  189. package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
  190. package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
  191. package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
  192. package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
  193. package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
  194. package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
  195. package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
  196. package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
  197. package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
  198. package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
  199. package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
  200. package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
  201. package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
  202. package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
  203. package/.pi/skills/lsp-navigation/SKILL.md +89 -0
  204. package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
  205. package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
  206. package/.pi/skills/quality/security-review/SKILL.md +34 -0
  207. package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
  208. package/.pi/skills/quality/testability-design/SKILL.md +33 -0
  209. package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
  210. package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
  211. package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
  212. package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
  213. package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
  214. package/.sentrux/rules.toml +20 -4
  215. package/AGENTS.md +5 -0
  216. package/CHANGELOG.md +14 -0
  217. package/README.md +3 -12
  218. package/THIRD_PARTY_NOTICES.md +12 -21
  219. package/package.json +15 -7
  220. package/vendor/pi-subagents/src/agents.ts +45 -1
  221. package/vendor/pi-subagents/src/subagents.ts +866 -811
  222. package/vendor/pi-vcc/src/core/brief.ts +68 -99
  223. package/vendor/pi-vcc/src/core/settings.ts +2 -2
  224. package/.agents/skills/caveman/SKILL.md +0 -67
  225. package/.pi/agents/harness/meta-optimizer.md +0 -36
  226. package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
  227. package/.pi/extensions/lib/harness-subagent-auth.ts +0 -207
  228. package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
  229. package/.pi/extensions/pi-model-router-harness.ts +0 -42
  230. package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
  231. package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
  232. package/.pi/model-router.example.json +0 -36
  233. package/.pi/prompts/harness-critic.md +0 -10
  234. package/.pi/prompts/harness-eval.md +0 -10
  235. package/.pi/prompts/harness-router-tune.md +0 -52
  236. package/.pi/scripts/harness-generate-model-router.mjs +0 -327
  237. package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
  238. package/.pi/scripts/harness-sync-model-router.mjs +0 -97
  239. package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
  240. package/vendor/pi-model-router/.prettierignore +0 -4
  241. package/vendor/pi-model-router/.prettierrc +0 -5
  242. package/vendor/pi-model-router/AGENTS.md +0 -39
  243. package/vendor/pi-model-router/LICENSE +0 -21
  244. package/vendor/pi-model-router/README.md +0 -99
  245. package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
  246. package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
  247. package/vendor/pi-model-router/extensions/commands.ts +0 -720
  248. package/vendor/pi-model-router/extensions/config.ts +0 -348
  249. package/vendor/pi-model-router/extensions/constants.ts +0 -1
  250. package/vendor/pi-model-router/extensions/index.ts +0 -478
  251. package/vendor/pi-model-router/extensions/provider.ts +0 -580
  252. package/vendor/pi-model-router/extensions/routing.ts +0 -564
  253. package/vendor/pi-model-router/extensions/state.ts +0 -52
  254. package/vendor/pi-model-router/extensions/types.ts +0 -95
  255. package/vendor/pi-model-router/extensions/ui.ts +0 -144
  256. package/vendor/pi-model-router/model-router.example.json +0 -48
  257. package/vendor/pi-model-router/package.json +0 -48
  258. package/vendor/pi-model-router/tsconfig.json +0 -16
  259. /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
  260. /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
  261. /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
  262. /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
  263. /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
  264. /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
  265. /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
  266. /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
  267. /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
  268. /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
  269. /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
  270. /package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +0 -0
  271. /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
  272. /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
  273. /package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +0 -0
  274. /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
  275. /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
  276. /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
  277. /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
  278. /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
  279. /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
  280. /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
  281. /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
  282. /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
  283. /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
  284. /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
@@ -5,7 +5,7 @@
5
5
 
6
6
  import { readFile, access } from "node:fs/promises";
7
7
  import { constants } from "node:fs";
8
- import { join, dirname, resolve } from "node:path";
8
+ import { join, dirname } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
10
  import { spawn } from "node:child_process";
11
11
 
@@ -42,6 +42,10 @@ const REQUIRED_ADRS = [
42
42
  "0037-subagent-submit-tools.md",
43
43
  "0038-budget-telemetry-only.md",
44
44
  "0040-practice-grounded-orchestration.md",
45
+ "0045-harness-lens-minimal-contract.md",
46
+ "0046-agt-policy-engine.md",
47
+ "0047-agt-layered-security.md",
48
+ "0048-tool-call-hook-order.md",
45
49
  ];
46
50
 
47
51
  const REQUIRED_EXTENSIONS = [
@@ -148,32 +152,59 @@ async function checkSentruxRules() {
148
152
  ok(".sentrux/rules.toml present");
149
153
  }
150
154
 
151
- async function checkModelRouterThinkingOnly() {
152
- const path = join(ROOT, ".pi", "model-router.json");
153
- if (!(await fileExists(path))) {
154
- ok("model-router.json absent (skip thinking-only tier check)");
155
- return;
155
+ async function checkHarnessLens(pkgJson) {
156
+ if (!pkgJson.files?.includes(".pi/lib/harness-lens")) {
157
+ fail(
158
+ 'package.json "files" must include .pi/lib/harness-lens (npm publish ships harness lens extension)',
159
+ );
156
160
  }
157
- let raw;
158
- try {
159
- raw = JSON.parse(await readFile(path, "utf-8"));
160
- } catch {
161
- fail("invalid .pi/model-router.json");
162
- }
163
- const profiles = raw.profiles ?? {};
164
- for (const [name, profile] of Object.entries(profiles)) {
165
- const high = profile?.high?.model;
166
- const medium = profile?.medium?.model;
167
- const low = profile?.low?.model;
168
- if (
169
- !(high && medium && low && high === medium && medium === low)
170
- ) {
171
- fail(
172
- `model-router profile "${name}" must use the same model on high/medium/low (thinking-only tiers)`,
173
- );
174
- }
161
+ ok('package.json files includes .pi/lib/harness-lens');
162
+
163
+ const piExtensions = pkgJson.pi?.extensions ?? [];
164
+ if (!piExtensions.includes("./.pi/extensions")) {
165
+ fail('package.json pi.extensions must include ./.pi/extensions');
166
+ }
167
+ if (piExtensions.includes("./vendor/pi-lens/index.ts")) {
168
+ fail('package.json pi.extensions must not load vendor/pi-lens directly');
169
+ }
170
+ ok("package.json loads harness extension directory");
171
+
172
+ const harnessLens = join(ROOT, ".pi", "extensions", "harness-lens.ts");
173
+ if (!(await fileExists(harnessLens))) fail("missing .pi/extensions/harness-lens.ts");
174
+ ok("harness lens extension wrapper");
175
+
176
+ const lensIndex = join(ROOT, ".pi", "lib", "harness-lens", "index.ts");
177
+ if (!(await fileExists(lensIndex))) {
178
+ fail("missing .pi/lib/harness-lens/index.ts");
179
+ }
180
+ ok(".pi/lib/harness-lens/index.ts");
181
+
182
+ const legacyExtLib = join(ROOT, ".pi", "extensions", "lib");
183
+ if (await fileExists(legacyExtLib)) {
184
+ fail(".pi/extensions/lib must not exist (shared code lives in .pi/lib/)");
185
+ }
186
+ ok("no legacy .pi/extensions/lib directory");
187
+
188
+ const lensIndexSource = await readFile(lensIndex, "utf8");
189
+ if (lensIndexSource.includes("ast_grep_search")) {
190
+ fail("harness-lens index must not register ast_grep_search");
191
+ }
192
+ if (lensIndexSource.includes("lib/lens")) {
193
+ fail("harness-lens index must not import lib/lens");
194
+ }
195
+ ok("harness-lens index contract (no ast_grep, no lib/lens imports)");
196
+
197
+ const rulesDir = join(ROOT, ".pi", "lib", "harness-lens", "rules");
198
+ if (await fileExists(rulesDir)) {
199
+ fail("harness-lens bundled rules/ directory must not exist");
200
+ }
201
+ ok("no bundled harness-lens rules/ directory");
202
+
203
+ const upstreamPin = join(ROOT, ".pi", "lib", "harness-lens", "UPSTREAM_PIN.md");
204
+ if (await fileExists(upstreamPin)) {
205
+ fail("harness-lens UPSTREAM_PIN.md must not exist (harness-native, no upstream sync)");
175
206
  }
176
- ok("model-router.json thinking-only (same model per profile)");
207
+ ok("no harness-lens UPSTREAM_PIN.md");
177
208
  }
178
209
 
179
210
  async function checkSentruxGate() {
@@ -235,7 +266,7 @@ async function main() {
235
266
  ok(`extension ${name}`);
236
267
  }
237
268
 
238
- const libPath = join(ROOT, ".pi", "extensions", "lib", "harness-posthog.ts");
269
+ const libPath = join(ROOT, ".pi", "lib", "harness-posthog.ts");
239
270
  if (!(await fileExists(libPath))) fail("missing lib/harness-posthog.ts");
240
271
  ok("lib/harness-posthog.ts");
241
272
 
@@ -246,6 +277,8 @@ async function main() {
246
277
  const pkgJson = JSON.parse(
247
278
  await readFile(join(ROOT, "package.json"), "utf-8"),
248
279
  );
280
+ await checkHarnessLens(pkgJson);
281
+
249
282
  if (!pkgJson.files?.includes("vendor/pi-subagents")) {
250
283
  fail(
251
284
  'package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)',
@@ -263,13 +296,7 @@ async function main() {
263
296
  if (!(await fileExists(subagentsVendor))) {
264
297
  fail("missing vendor/pi-subagents/src/subagents.ts");
265
298
  }
266
- const bridgePath = join(
267
- ROOT,
268
- ".pi",
269
- "extensions",
270
- "lib",
271
- "harness-subagents-bridge.ts",
272
- );
299
+ const bridgePath = join(ROOT, ".pi", "lib", "harness-subagents-bridge.ts");
273
300
  if (!(await fileExists(bridgePath))) {
274
301
  fail("missing harness-subagents-bridge.ts");
275
302
  }
@@ -280,6 +307,14 @@ async function main() {
280
307
  if (!bridgeSrc.includes("packageRoot")) {
281
308
  fail("harness-subagents-bridge must pass packageRoot for agent discovery");
282
309
  }
310
+ if (
311
+ !bridgeSrc.includes("subprocessGovernanceExtensionPath") &&
312
+ !bridgeSrc.includes("subagentGovernanceExtensionPath")
313
+ ) {
314
+ fail(
315
+ "harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents",
316
+ );
317
+ }
283
318
  const subagentsSrc = await readFile(subagentsVendor, "utf-8");
284
319
  if (!subagentsSrc.includes("discoverAgents")) {
285
320
  fail("vendor subagents.ts must implement discoverAgents");
@@ -301,12 +336,46 @@ async function main() {
301
336
  if (!policyGateSrc.includes('pi.on("tool_call", async (event, ctx)')) {
302
337
  fail("policy-gate tool_call must receive ctx for run context");
303
338
  }
304
- if (!policyGateSrc.includes("evaluateContextModeMutation")) {
305
- fail(
306
- "policy-gate.ts must evaluate context-mode execute tools via evaluateContextModeMutation",
307
- );
339
+ if (!policyGateSrc.includes("evaluateAgtHarnessToolCall")) {
340
+ fail("policy-gate.ts must delegate tool_call to AGT evaluateAgtHarnessToolCall");
308
341
  }
309
- ok("policy-gate plan-phase writes");
342
+ const govPath = join(ROOT, ".pi", "extensions", "subagent-governance.ts");
343
+ const govAlias = join(
344
+ ROOT,
345
+ ".pi",
346
+ "extensions",
347
+ "harness-subagent-governance.ts",
348
+ );
349
+ if (!(await fileExists(govPath))) {
350
+ fail("missing subagent-governance.ts subprocess bundle");
351
+ }
352
+ if (!(await fileExists(govAlias))) {
353
+ fail("missing harness-subagent-governance.ts re-export alias");
354
+ }
355
+ ok("policy-gate + subprocess governance");
356
+
357
+ const agtDoctorPath = join(ROOT, ".pi", "scripts", "harness-agt-doctor.ts");
358
+ const { code: agtDoctorCode, out: agtDoctorOut } = await new Promise(
359
+ (resolve) => {
360
+ const child = spawn(
361
+ "npx",
362
+ ["-y", "tsx", agtDoctorPath],
363
+ { cwd: ROOT, stdio: ["ignore", "pipe", "pipe"], shell: true },
364
+ );
365
+ let out = "";
366
+ child.stdout?.on("data", (d) => {
367
+ out += d.toString();
368
+ });
369
+ child.stderr?.on("data", (d) => {
370
+ out += d.toString();
371
+ });
372
+ child.on("close", (code) => resolve({ code: code ?? 1, out }));
373
+ },
374
+ );
375
+ if (agtDoctorCode !== 0) {
376
+ fail(agtDoctorOut.trim() || "AGT policy doctor failed");
377
+ }
378
+ ok("AGT policy doctor");
310
379
 
311
380
  const runCtxFixture = join(SMOKE, "run-context.fixture.json");
312
381
  if (!(await fileExists(runCtxFixture))) {
@@ -332,7 +401,12 @@ async function main() {
332
401
  ok("test-diff-golden.json");
333
402
 
334
403
  await checkSentruxGate();
335
- await checkModelRouterThinkingOnly();
404
+
405
+ const AGENTS_POLICY = join(ROOT, ".pi", "harness", "agents.policy.yaml");
406
+ if (!(await fileExists(AGENTS_POLICY))) {
407
+ fail("missing .pi/harness/agents.policy.yaml");
408
+ }
409
+ ok("agents.policy.yaml present");
336
410
 
337
411
  if (!(await fileExists(AGENTS_MANIFEST))) {
338
412
  fail(
@@ -12,9 +12,9 @@ const ROOT = resolve(new URL("../..", import.meta.url).pathname);
12
12
  const ALLOWED_FILES = new Set([
13
13
  ".pi/extensions/harness-web-guard.ts",
14
14
  ".pi/extensions/harness-web-tools.ts",
15
- ".pi/extensions/lib/harness-web/run-cli.ts",
15
+ ".pi/lib/harness-web/run-cli.ts",
16
16
  ".pi/extensions/harness-run-context.ts",
17
- ".pi/extensions/lib/ask-user/schema.ts",
17
+ ".pi/lib/ask-user/schema.ts",
18
18
  ".pi/scripts/harness-web.py",
19
19
  ".pi/scripts/harness-web-search.md",
20
20
  ".pi/scripts/harness-web-policy-guard.mjs",
@@ -88,49 +88,31 @@ function computeCriticalPath(workItems) {
88
88
  return path;
89
89
  }
90
90
 
91
- export function validateExecutionPlan(packet, projectRoot = ROOT) {
91
+ function validateMinimumShape(packet, ep, phases, workItems) {
92
92
  const errors = [];
93
- const ep = packet.execution_plan;
94
- if (!ep) {
95
- errors.push("execution_plan required");
96
- return { status: "fail", errors, report: null };
97
- }
98
-
99
93
  const risk = packet.risk_level ?? "med";
100
94
  const min = MINIMUMS[risk] ?? MINIMUMS.med;
101
- const phases = ep.phases ?? [];
102
- const workItems = ep.work_items ?? [];
103
- const conflicts = [];
104
-
105
- if (phases.length < min.phases) {
106
- errors.push(`need >= ${min.phases} phases for risk ${risk}`);
107
- }
108
- if (workItems.length < min.work_items) {
109
- errors.push(`need >= ${min.work_items} work_items for risk ${risk}`);
110
- }
111
95
  const ac = packet.acceptance_checks ?? [];
112
- if (ac.length < min.acceptance_checks) {
113
- errors.push(`need >= ${min.acceptance_checks} acceptance_checks`);
114
- }
115
- if ((ep.risk_register ?? []).length < min.risks) {
116
- errors.push(`need >= ${min.risks} risks for risk ${risk}`);
117
- }
96
+ if (phases.length < min.phases) errors.push(`need >= ${min.phases} phases for risk ${risk}`);
97
+ if (workItems.length < min.work_items) errors.push(`need >= ${min.work_items} work_items for risk ${risk}`);
98
+ if (ac.length < min.acceptance_checks) errors.push(`need >= ${min.acceptance_checks} acceptance_checks`);
99
+ if ((ep.risk_register ?? []).length < min.risks) errors.push(`need >= ${min.risks} risks for risk ${risk}`);
100
+ return errors;
101
+ }
118
102
 
103
+ function validatePhaseAndWorkItemLinks(phases, workItems) {
104
+ const errors = [];
119
105
  const phaseIds = new Set(phases.map((p) => p.phase_id));
120
- const phaseIndex = new Map(phases.map((p, i) => [p.phase_id, i]));
121
106
  const wiIds = new Set(workItems.map((w) => w.work_item_id));
122
-
123
107
  for (const p of phases) {
124
108
  if (!p.exit_criteria?.length) errors.push(`phase ${p.phase_id} missing exit_criteria`);
125
109
  if (!p.work_item_ids?.length) errors.push(`phase ${p.phase_id} has no work items`);
110
+ for (const wid of p.work_item_ids ?? []) {
111
+ if (!wiIds.has(wid)) errors.push(`phase ${p.phase_id} references missing ${wid}`);
112
+ }
126
113
  }
127
-
128
- const wiInPhase = new Set();
129
114
  for (const w of workItems) {
130
- if (!phaseIds.has(w.phase_id)) {
131
- errors.push(`work_item ${w.work_item_id} unknown phase_id`);
132
- }
133
- wiInPhase.add(w.work_item_id);
115
+ if (!phaseIds.has(w.phase_id)) errors.push(`work_item ${w.work_item_id} unknown phase_id`);
134
116
  for (const d of w.depends_on ?? []) {
135
117
  if (!wiIds.has(d)) errors.push(`work_item ${w.work_item_id} depends_on missing ${d}`);
136
118
  }
@@ -138,17 +120,23 @@ export function validateExecutionPlan(packet, projectRoot = ROOT) {
138
120
  errors.push(`work_item ${w.work_item_id} needs files[] or non_code: true`);
139
121
  }
140
122
  }
123
+ return errors;
124
+ }
141
125
 
142
- for (const p of phases) {
143
- for (const wid of p.work_item_ids ?? []) {
144
- if (!wiIds.has(wid)) errors.push(`phase ${p.phase_id} references missing ${wid}`);
145
- }
126
+ function isReachable(workItems, from, to, seen = new Set()) {
127
+ if (from === to) return true;
128
+ if (seen.has(from)) return false;
129
+ seen.add(from);
130
+ const w = workItems.find((x) => x.work_item_id === from);
131
+ for (const d of w?.depends_on ?? []) {
132
+ if (isReachable(workItems, d, to, seen)) return true;
146
133
  }
134
+ return false;
135
+ }
147
136
 
148
- const { order, cycles } = topoSort(workItems);
149
- if (cycles.length) errors.push(`cycle detected: ${JSON.stringify(cycles[0])}`);
150
-
151
- // File conflicts
137
+ function findFileConflicts(phases, workItems) {
138
+ const conflicts = [];
139
+ const phaseIndex = new Map(phases.map((p, i) => [p.phase_id, i]));
152
140
  for (let i = 0; i < workItems.length; i++) {
153
141
  for (let j = i + 1; j < workItems.length; j++) {
154
142
  const a = workItems[i];
@@ -156,42 +144,34 @@ export function validateExecutionPlan(packet, projectRoot = ROOT) {
156
144
  const filesA = new Set(a.files ?? []);
157
145
  const overlap = (b.files ?? []).filter((f) => filesA.has(f));
158
146
  if (overlap.length === 0) continue;
159
- const reachable = (from, to, seen = new Set()) => {
160
- if (from === to) return true;
161
- if (seen.has(from)) return false;
162
- seen.add(from);
163
- const w = workItems.find((x) => x.work_item_id === from);
164
- for (const d of w?.depends_on ?? []) {
165
- if (reachable(d, to, seen)) return true;
166
- }
167
- return false;
168
- };
169
- if (!reachable(a.work_item_id, b.work_item_id) && !reachable(b.work_item_id, a.work_item_id)) {
170
- if ((phaseIndex.get(a.phase_id) ?? 0) === (phaseIndex.get(b.phase_id) ?? 0)) {
171
- conflicts.push(
172
- `file overlap ${overlap.join(",")} between ${a.work_item_id} and ${b.work_item_id} without dependency`,
173
- );
174
- }
147
+ const ordered =
148
+ isReachable(workItems, a.work_item_id, b.work_item_id) ||
149
+ isReachable(workItems, b.work_item_id, a.work_item_id);
150
+ const samePhase = (phaseIndex.get(a.phase_id) ?? 0) === (phaseIndex.get(b.phase_id) ?? 0);
151
+ if (!ordered && samePhase) {
152
+ conflicts.push(
153
+ `file overlap ${overlap.join(",")} between ${a.work_item_id} and ${b.work_item_id} without dependency`,
154
+ );
175
155
  }
176
156
  }
177
157
  }
158
+ return conflicts;
159
+ }
178
160
 
161
+ function validateCriticalPath(ep, workItems) {
179
162
  const computedCp = computeCriticalPath(workItems);
180
163
  const authorCp = ep.schedule_metadata?.critical_path_work_item_ids ?? [];
181
- if (computedCp.length >= 3 && authorCp.length) {
182
- const same =
183
- authorCp.length === computedCp.length &&
184
- authorCp.every((id, i) => id === computedCp[i]);
185
- if (!same) {
186
- errors.push(
187
- `critical_path mismatch author=${authorCp.join("→")} computed=${computedCp.join("→")}`,
188
- );
189
- }
190
- }
164
+ const same =
165
+ authorCp.length === computedCp.length &&
166
+ authorCp.every((id, i) => id === computedCp[i]);
167
+ if (computedCp.length < 3 || !authorCp.length || same) return [];
168
+ return [`critical_path mismatch author=${authorCp.join("→")} computed=${computedCp.join("→")}`];
169
+ }
191
170
 
192
- const acIds = new Set(
193
- ac.map((c) => (typeof c === "string" ? c : c.id)).filter(Boolean),
194
- );
171
+ function validateAcceptanceCheckLinks(packet, workItems) {
172
+ const errors = [];
173
+ const ac = packet.acceptance_checks ?? [];
174
+ const acIds = new Set(ac.map((c) => (typeof c === "string" ? c : c.id)).filter(Boolean));
195
175
  for (const w of workItems) {
196
176
  for (const acid of w.acceptance_check_ids ?? []) {
197
177
  if (!acIds.has(acid)) errors.push(`${w.work_item_id} references orphan ${acid}`);
@@ -201,14 +181,25 @@ export function validateExecutionPlan(packet, projectRoot = ROOT) {
201
181
  const used = workItems.some((w) => (w.acceptance_check_ids ?? []).includes(acid));
202
182
  if (!used) errors.push(`orphan acceptance check ${acid}`);
203
183
  }
184
+ return errors;
185
+ }
204
186
 
187
+ export function validateExecutionPlan(packet, projectRoot = ROOT) {
188
+ const ep = packet.execution_plan;
189
+ if (!ep) return { status: "fail", errors: ["execution_plan required"], report: null };
190
+ const phases = ep.phases ?? [];
191
+ const workItems = ep.work_items ?? [];
192
+ const { order, cycles } = topoSort(workItems);
193
+ const errors = [
194
+ ...validateMinimumShape(packet, ep, phases, workItems),
195
+ ...validatePhaseAndWorkItemLinks(phases, workItems),
196
+ ...(cycles.length ? [`cycle detected: ${JSON.stringify(cycles[0])}`] : []),
197
+ ...validateCriticalPath(ep, workItems),
198
+ ...validateAcceptanceCheckLinks(packet, workItems),
199
+ ];
200
+ const conflicts = findFileConflicts(phases, workItems);
205
201
  const status = errors.length === 0 && conflicts.length === 0 ? "pass" : "fail";
206
- const report = {
207
- status,
208
- topological_order: order,
209
- cycles,
210
- conflicts: [...conflicts, ...errors],
211
- };
202
+ const report = { status, topological_order: order, cycles, conflicts: [...conflicts, ...errors] };
212
203
  return { status, errors: [...errors, ...conflicts], report };
213
204
  }
214
205
 
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * ultimate-pi harness settings (env-only). Re-exported for vendored pi-vcc layout.
3
3
  */
4
- export type { PiVccSettings } from "../../../../.pi/extensions/lib/harness-vcc-settings.js";
4
+ export type { PiVccSettings } from "../../../../.pi/lib/harness-vcc-settings.js";
5
5
  export {
6
6
  loadSettings,
7
7
  scaffoldSettings,
8
- } from "../../../../.pi/extensions/lib/harness-vcc-settings.js";
8
+ } from "../../../../.pi/lib/harness-vcc-settings.js";
@@ -29,7 +29,7 @@ cat >"$VEND/UPSTREAM_PIN.md" <<EOF
29
29
  - **License:** MIT (see upstream repository)
30
30
  - **Pinned upstream commit:** \`$COMMIT\`
31
31
  - **Local changes:**
32
- - \`src/core/settings.ts\` re-exports env-only [\`harness-vcc-settings\`](../../.pi/extensions/lib/harness-vcc-settings.ts) (\`HARNESS_VCC_COMPACTION\`, \`HARNESS_VCC_DEBUG\`)
32
+ - \`src/core/settings.ts\` re-exports env-only [\`harness-vcc-settings\`](../../.pi/lib/harness-vcc-settings.ts) (\`HARNESS_VCC_COMPACTION\`, \`HARNESS_VCC_DEBUG\`)
33
33
  - No \`scaffoldSettings\` / no \`PI_VCC_CONFIG_PATH\`
34
34
  - Compaction \`details.compactor\` is \`ultimate-pi-vcc\`
35
35
 
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: broker-domain
3
+ description: "Use when implementing the Broker-Domain pattern: event-brokered integration around cohesive domain services, separating broker concerns from domain behavior, with explicit event contracts and bounded context ownership."
4
+ ---
5
+
6
+ # Broker-Domain Pattern
7
+
8
+ Use this skill when event-driven integration is needed but domain logic must stay inside cohesive domain boundaries.
9
+
10
+ ## Fit
11
+
12
+ Use when multiple domains coordinate through events and you need to avoid both a god orchestrator and an anemic event-bus blob.
13
+ Avoid when a simple in-process call or single workflow orchestrator is clearer.
14
+
15
+ ## Agent Workflow
16
+
17
+ 1. Read `graphify-out/GRAPH_REPORT.md`.
18
+ 2. Run `graphify query "Broker-Domain pattern event broker domain services bounded contexts"`.
19
+ 3. Identify domain services and event broker responsibilities separately.
20
+ 4. Keep event routing/transport out of domain decisions.
21
+ 5. Implement one domain event flow end to end.
22
+ 6. Add contract, idempotency, and ownership checks.
23
+
24
+ ## Target Shape
25
+
26
+ ```text
27
+ codebase/domains/
28
+ orders/
29
+ domain/
30
+ application/
31
+ events # domain-owned event contracts
32
+ billing/
33
+ ...
34
+ codebase/broker/
35
+ bus
36
+ subscriptions
37
+ serializers
38
+ ```
39
+
40
+ ## Implementation Rules
41
+
42
+ - Domains publish and consume meaningful business events.
43
+ - Broker code owns transport, serialization, subscription wiring, retries, and delivery mechanics.
44
+ - Domain code must not depend on broker vendor APIs.
45
+ - Event contracts belong to the producing domain and are versioned.
46
+ - Consumers translate external events into local application actions.
47
+
48
+ ## Migration Steps
49
+
50
+ 1. Select one cross-domain interaction.
51
+ 2. Define producer-owned event contract.
52
+ 3. Add broker adapter that maps domain event to transport message.
53
+ 4. Add consumer subscription that calls local application service.
54
+ 5. Add idempotency and dead-letter handling.
55
+ 6. Remove direct cross-domain call if the event path replaces it safely.
56
+
57
+ ## Verification
58
+
59
+ - `graphify explain "broker"` should show broker-to-domain adapter edges, not domain-to-vendor lock-in.
60
+ - Test event contract compatibility and duplicate delivery.
61
+ - Check no domain imports broker implementation packages.
62
+
63
+ ## Output Contract
64
+
65
+ Return: domains, broker responsibilities, event contracts, migrated interaction, failure handling, and verification.
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: cqrs
3
+ description: "Use when implementing CQRS: separate command and query models, explicit write invariants, optimized read projections, consistency boundaries, projection rebuilds, and tests for command/query behavior."
4
+ ---
5
+
6
+ # CQRS
7
+
8
+ Use this skill when reads and writes need different models, scaling, permissions, or performance profiles.
9
+
10
+ ## Fit
11
+
12
+ Use for complex domains with strong write invariants and read-heavy/query-specific views.
13
+ Avoid when simple CRUD is adequate; CQRS can double code paths and consistency work.
14
+
15
+ ## Agent Workflow
16
+
17
+ 1. Read `graphify-out/GRAPH_REPORT.md`.
18
+ 2. Run `graphify query "CQRS command query responsibility segregation projections events"`.
19
+ 3. Identify commands that change state and queries that read views.
20
+ 4. Separate write model from read model incrementally.
21
+ 5. Define consistency and projection rebuild rules.
22
+ 6. Add tests for commands, projections, and stale reads.
23
+
24
+ ## Target Shape
25
+
26
+ ```text
27
+ codebase/<bounded-context>/
28
+ commands/
29
+ handlers/
30
+ model/
31
+ queries/
32
+ handlers/
33
+ projections/
34
+ events/ # optional but common
35
+ ```
36
+
37
+ ## Implementation Rules
38
+
39
+ - Commands express intent and enforce invariants through the write model.
40
+ - Queries never mutate state.
41
+ - Read models are optimized for consumers and may be denormalized.
42
+ - Projection lag and stale-read behavior must be explicit.
43
+ - Do not expose write entities directly as read DTOs.
44
+ - Keep command and query contracts versioned if external.
45
+
46
+ ## Migration Steps
47
+
48
+ 1. Pick one painful read or one complex command.
49
+ 2. Create command handler and query handler folders.
50
+ 3. Move invariant logic into command handler/write model.
51
+ 4. Create a read projection or query representation.
52
+ 5. Wire projection update from transaction, event, or scheduled rebuild.
53
+ 6. Add tests for stale/missing projection behavior.
54
+
55
+ ## Verification
56
+
57
+ - Use graphify to confirm query handlers do not call command mutation paths.
58
+ - Test command invariants independently from query projection shape.
59
+ - Test projection rebuild from durable source.
60
+
61
+ ## Output Contract
62
+
63
+ Return: command/query split, consistency model, projection path, migration patch, and verification evidence.
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: event-driven
3
+ description: "Use when implementing event-driven architecture: domain/integration events, producers, consumers, broker or mediator topology, eventual consistency, idempotency, ordering, retries, and observable asynchronous flows."
4
+ ---
5
+
6
+ # Event-Driven Architecture
7
+
8
+ Use this skill when agents need to decouple producers and consumers with events safely.
9
+
10
+ ## Fit
11
+
12
+ Use for asynchronous workflows, multi-consumer reactions, integration boundaries, audit trails, and high-throughput decoupling.
13
+ Avoid when immediate consistency and simple request/response are enough.
14
+
15
+ ## Agent Workflow
16
+
17
+ 1. Read `graphify-out/GRAPH_REPORT.md`.
18
+ 2. Run `graphify query "event-driven architecture broker mediator topology eventual consistency"`.
19
+ 3. Identify facts worth publishing, not commands disguised as events.
20
+ 4. Choose broker/choreography or mediator/orchestration intentionally.
21
+ 5. Implement one event path end to end.
22
+ 6. Add idempotency, retry, and observability before expanding.
23
+
24
+ ## Target Shape
25
+
26
+ ```text
27
+ codebase/events/
28
+ contracts/ # event schemas and versions
29
+ event-bus # publish/subscribe port
30
+ handlers/ # consumers
31
+ outbox/ # reliable publish if DB-backed
32
+ ```
33
+
34
+ ## Implementation Rules
35
+
36
+ - Events are past-tense facts: `OrderPlaced`, not `PlaceOrder`.
37
+ - Event schemas are versioned and backward-compatible.
38
+ - Consumers are idempotent and handle duplicate/out-of-order delivery.
39
+ - Use an outbox/inbox pattern when database state and publishing must be reliable.
40
+ - Prefer correlation IDs and causation IDs for traceability.
41
+ - Do not hide core business invariants in eventually consistent consumers.
42
+
43
+ ## Migration Steps
44
+
45
+ 1. Name one event from a real business fact.
46
+ 2. Define schema, version, producer, and consumers.
47
+ 3. Publish after the owning transaction commits.
48
+ 4. Add one consumer with idempotency storage.
49
+ 5. Add retry/dead-letter behavior.
50
+ 6. Add tests for duplicate, missing, and stale events.
51
+
52
+ ## Verification
53
+
54
+ - `graphify explain "<event name>"` should show producer and consumers.
55
+ - Contract-test event schema compatibility.
56
+ - Test retry and idempotency paths.
57
+
58
+ ## Output Contract
59
+
60
+ Return: event catalog, topology choice, producer/consumer patch, consistency guarantees, failure handling, and verification.