@cratis/pi 0.0.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -37
  3. package/package/corpus/agents/backend-developer.md +125 -0
  4. package/package/corpus/agents/code-reviewer.md +165 -0
  5. package/package/corpus/agents/coordinator.md +163 -0
  6. package/package/corpus/agents/frontend-developer.md +246 -0
  7. package/package/corpus/agents/orchestrator.md +196 -0
  8. package/package/corpus/agents/performance-reviewer.md +109 -0
  9. package/package/corpus/agents/planner.md +145 -0
  10. package/package/corpus/agents/repository-investigation-reviewer.md +45 -0
  11. package/package/corpus/agents/repository-investigator.md +50 -0
  12. package/package/corpus/agents/security-reviewer.md +118 -0
  13. package/package/corpus/agents/slice-implementer.md +59 -0
  14. package/package/corpus/agents/spec-writer.md +149 -0
  15. package/package/corpus/harnesses/pi/extensions/cratis-hooks/index.ts +213 -0
  16. package/package/corpus/harnesses/pi/extensions/cratis-rules/index.ts +27 -0
  17. package/package/corpus/harnesses/pi/extensions/package.json +4 -0
  18. package/package/corpus/harnesses/pi/extensions/subagent/agents.ts +167 -0
  19. package/package/corpus/harnesses/pi/extensions/subagent/index.ts +352 -0
  20. package/package/corpus/hooks/README.md +434 -0
  21. package/package/corpus/hooks/agent-stop.md +49 -0
  22. package/package/corpus/hooks/pre-commit.md +47 -0
  23. package/package/corpus/hooks/scripts/cratis-guard-writes.sh +87 -0
  24. package/package/corpus/hooks/scripts/cratis-nuget-pins.txt +11 -0
  25. package/package/corpus/hooks/scripts/cratis-pattern-scan.sh +197 -0
  26. package/package/corpus/hooks/scripts/cratis-patterns.json +84 -0
  27. package/package/corpus/hooks/scripts/cratis-quality-gate.sh +219 -0
  28. package/package/corpus/hooks/scripts/hook-lib.sh +152 -0
  29. package/package/corpus/hooks/scripts/quality-gates.json +261 -0
  30. package/package/corpus/hooks/scripts/type-references-allowlist.txt +71 -0
  31. package/package/corpus/hooks/scripts/validate-package-imports.sh +166 -0
  32. package/package/corpus/hooks/scripts/validate-package-subpaths.sh +120 -0
  33. package/package/corpus/hooks/scripts/validate-type-references.sh +308 -0
  34. package/package/corpus/hooks/settings.template.json +40 -0
  35. package/package/corpus/prompts/add-business-rule.prompt.md +22 -0
  36. package/package/corpus/prompts/add-concept.prompt.md +17 -0
  37. package/package/corpus/prompts/add-ef-migration.prompt.md +24 -0
  38. package/package/corpus/prompts/add-projection.prompt.md +20 -0
  39. package/package/corpus/prompts/add-reactor.prompt.md +22 -0
  40. package/package/corpus/prompts/add-reducer.prompt.md +20 -0
  41. package/package/corpus/prompts/audit-hooks.prompt.md +15 -0
  42. package/package/corpus/prompts/check-doc-drift.prompt.md +21 -0
  43. package/package/corpus/prompts/code-review.prompt.md +9 -0
  44. package/package/corpus/prompts/new-feature.prompt.md +9 -0
  45. package/package/corpus/prompts/new-vertical-slice.prompt.md +18 -0
  46. package/package/corpus/prompts/review-pr.prompt.md +35 -0
  47. package/package/corpus/prompts/review-skill.prompt.md +16 -0
  48. package/package/corpus/prompts/scaffold-feature.prompt.md +16 -0
  49. package/package/corpus/prompts/ship-changes.prompt.md +20 -0
  50. package/package/corpus/prompts/verify-ai-setup.prompt.md +19 -0
  51. package/package/corpus/prompts/write-documentation.prompt.md +21 -0
  52. package/package/corpus/prompts/write-specs.prompt.md +22 -0
  53. package/package/corpus/rules/capability-is-not-authority.md +31 -0
  54. package/package/corpus/rules/code-quality.csharp.md +91 -0
  55. package/package/corpus/rules/code-quality.md +82 -0
  56. package/package/corpus/rules/code-quality.typescript.md +89 -0
  57. package/package/corpus/rules/components.md +207 -0
  58. package/package/corpus/rules/concepts.md +115 -0
  59. package/package/corpus/rules/csharp.md +269 -0
  60. package/package/corpus/rules/dialogs.md +264 -0
  61. package/package/corpus/rules/documentation-structure-and-formatting.md +148 -0
  62. package/package/corpus/rules/documentation.md +90 -0
  63. package/package/corpus/rules/editing-cratis-docs.md +69 -0
  64. package/package/corpus/rules/efcore.md +235 -0
  65. package/package/corpus/rules/efcore.specs.md +44 -0
  66. package/package/corpus/rules/exit-codes-and-wrappers.md +33 -0
  67. package/package/corpus/rules/framework.md +52 -0
  68. package/package/corpus/rules/frontend-quality.md +59 -0
  69. package/package/corpus/rules/frontend-testing.md +126 -0
  70. package/package/corpus/rules/general.md +305 -0
  71. package/package/corpus/rules/git-commits.md +138 -0
  72. package/package/corpus/rules/github-actions.md +92 -0
  73. package/package/corpus/rules/glossary.md +61 -0
  74. package/package/corpus/rules/guards-and-fuses.md +45 -0
  75. package/package/corpus/rules/local-work-artifacts.md +33 -0
  76. package/package/corpus/rules/managing-ai-rules.md +40 -0
  77. package/package/corpus/rules/orleans.md +50 -0
  78. package/package/corpus/rules/pull-requests.md +78 -0
  79. package/package/corpus/rules/react.md +195 -0
  80. package/package/corpus/rules/reactors.md +238 -0
  81. package/package/corpus/rules/rtk.md +37 -0
  82. package/package/corpus/rules/specs.csharp.md +139 -0
  83. package/package/corpus/rules/specs.md +132 -0
  84. package/package/corpus/rules/specs.scenarios.csharp.md +172 -0
  85. package/package/corpus/rules/specs.typescript.md +139 -0
  86. package/package/corpus/rules/storybook.md +84 -0
  87. package/package/corpus/rules/terminal-commands.md +19 -0
  88. package/package/corpus/rules/typescript.md +149 -0
  89. package/package/corpus/rules/verification-discipline.md +21 -0
  90. package/package/corpus/rules/vertical-slices.md +338 -0
  91. package/package/corpus/rules/web-fetching.md +11 -0
  92. package/package/corpus/rules/writing-correct-examples.md +35 -0
  93. package/package/corpus/rules/writing-cratis-docs.md +70 -0
  94. package/package/corpus/skills/cratis-engineering-csharp-conventions/LICENSE +2 -0
  95. package/package/corpus/skills/cratis-engineering-csharp-conventions/SKILL.md +131 -0
  96. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/code-style.md +187 -0
  97. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/domain-philosophy.md +91 -0
  98. package/package/corpus/skills/cratis-engineering-csharp-conventions/references/exceptions-logging-and-di.md +223 -0
  99. package/package/corpus/skills/cratis-engineering-decision-record/LICENSE +2 -0
  100. package/package/corpus/skills/cratis-engineering-decision-record/SKILL.md +133 -0
  101. package/package/corpus/skills/cratis-engineering-decision-record/references/record-format.md +107 -0
  102. package/package/corpus/skills/cratis-engineering-docs-authoring/LICENSE +2 -0
  103. package/package/corpus/skills/cratis-engineering-docs-authoring/SKILL.md +86 -0
  104. package/package/corpus/skills/cratis-engineering-docs-authoring/references/site-format.md +46 -0
  105. package/package/corpus/skills/cratis-engineering-effect-boundaries/LICENSE +2 -0
  106. package/package/corpus/skills/cratis-engineering-effect-boundaries/SKILL.md +130 -0
  107. package/package/corpus/skills/cratis-engineering-effect-boundaries/references/failure-archetypes.md +133 -0
  108. package/package/corpus/skills/cratis-fundamentals-concept/verification.json +8 -0
  109. package/package/profile-catalog.json +821 -0
  110. package/package.json +35 -13
  111. package/src/index.ts +80 -0
  112. /package/{skills → package/corpus/skills}/cratis-application-react-specifications/LICENSE +0 -0
  113. /package/{skills → package/corpus/skills}/cratis-application-react-specifications/SKILL.md +0 -0
  114. /package/{skills → package/corpus/skills}/cratis-application-slice-specifications/LICENSE +0 -0
  115. /package/{skills → package/corpus/skills}/cratis-application-slice-specifications/SKILL.md +0 -0
  116. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/LICENSE +0 -0
  117. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/SKILL.md +0 -0
  118. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/authentication.md +0 -0
  119. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/authorization.md +0 -0
  120. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/frontend.md +0 -0
  121. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/local-development.md +0 -0
  122. /package/{skills → package/corpus/skills}/cratis-arc-authentication-authorization-and-identity/references/tenancy.md +0 -0
  123. /package/{skills → package/corpus/skills}/cratis-arc-command/LICENSE +0 -0
  124. /package/{skills → package/corpus/skills}/cratis-arc-command/SKILL.md +0 -0
  125. /package/{skills → package/corpus/skills}/cratis-arc-command/references/command-result.md +0 -0
  126. /package/{skills → package/corpus/skills}/cratis-arc-command/references/handler-shapes.md +0 -0
  127. /package/{skills → package/corpus/skills}/cratis-arc-command/references/proxy-generation.md +0 -0
  128. /package/{skills → package/corpus/skills}/cratis-arc-command/references/read-model-injection.md +0 -0
  129. /package/{skills → package/corpus/skills}/cratis-arc-command-execution/LICENSE +0 -0
  130. /package/{skills → package/corpus/skills}/cratis-arc-command-execution/SKILL.md +0 -0
  131. /package/{skills → package/corpus/skills}/cratis-arc-command-validation/LICENSE +0 -0
  132. /package/{skills → package/corpus/skills}/cratis-arc-command-validation/SKILL.md +0 -0
  133. /package/{skills → package/corpus/skills}/cratis-arc-ef-core-migration/LICENSE +0 -0
  134. /package/{skills → package/corpus/skills}/cratis-arc-ef-core-migration/SKILL.md +0 -0
  135. /package/{skills → package/corpus/skills}/cratis-arc-observable-query-http/LICENSE +0 -0
  136. /package/{skills → package/corpus/skills}/cratis-arc-observable-query-http/SKILL.md +0 -0
  137. /package/{skills → package/corpus/skills}/cratis-arc-query-paging/LICENSE +0 -0
  138. /package/{skills → package/corpus/skills}/cratis-arc-query-paging/SKILL.md +0 -0
  139. /package/{skills → package/corpus/skills}/cratis-arc-react-page/LICENSE +0 -0
  140. /package/{skills → package/corpus/skills}/cratis-arc-react-page/SKILL.md +0 -0
  141. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/data-page.md +0 -0
  142. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/data-tables.md +0 -0
  143. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/dialogs.md +0 -0
  144. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/mvvm.md +0 -0
  145. /package/{skills → package/corpus/skills}/cratis-arc-react-page/references/queries-and-commands.md +0 -0
  146. /package/{skills → package/corpus/skills}/cratis-chronicle-cli-operations/LICENSE +0 -0
  147. /package/{skills → package/corpus/skills}/cratis-chronicle-cli-operations/SKILL.md +0 -0
  148. /package/{skills → package/corpus/skills}/cratis-chronicle-client-dotnet/LICENSE +0 -0
  149. /package/{skills → package/corpus/skills}/cratis-chronicle-client-dotnet/SKILL.md +0 -0
  150. /package/{skills → package/corpus/skills}/cratis-chronicle-client-elixir/LICENSE +0 -0
  151. /package/{skills → package/corpus/skills}/cratis-chronicle-client-elixir/SKILL.md +0 -0
  152. /package/{skills → package/corpus/skills}/cratis-chronicle-client-kotlin/LICENSE +0 -0
  153. /package/{skills → package/corpus/skills}/cratis-chronicle-client-kotlin/SKILL.md +0 -0
  154. /package/{skills → package/corpus/skills}/cratis-chronicle-client-typescript/LICENSE +0 -0
  155. /package/{skills → package/corpus/skills}/cratis-chronicle-client-typescript/SKILL.md +0 -0
  156. /package/{skills → package/corpus/skills}/cratis-chronicle-compliance/LICENSE +0 -0
  157. /package/{skills → package/corpus/skills}/cratis-chronicle-compliance/SKILL.md +0 -0
  158. /package/{skills → package/corpus/skills}/cratis-chronicle-event-constraints/LICENSE +0 -0
  159. /package/{skills → package/corpus/skills}/cratis-chronicle-event-constraints/SKILL.md +0 -0
  160. /package/{skills → package/corpus/skills}/cratis-chronicle-event-modeling/LICENSE +0 -0
  161. /package/{skills → package/corpus/skills}/cratis-chronicle-event-modeling/SKILL.md +0 -0
  162. /package/{skills → package/corpus/skills}/cratis-chronicle-event-specifications/LICENSE +0 -0
  163. /package/{skills → package/corpus/skills}/cratis-chronicle-event-specifications/SKILL.md +0 -0
  164. /package/{skills → package/corpus/skills}/cratis-chronicle-event-type-migration/LICENSE +0 -0
  165. /package/{skills → package/corpus/skills}/cratis-chronicle-event-type-migration/SKILL.md +0 -0
  166. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/LICENSE +0 -0
  167. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/SKILL.md +0 -0
  168. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/references/blocked-tools.md +0 -0
  169. /package/{skills → package/corpus/skills}/cratis-chronicle-mcp-inspection/references/observational-tools.md +0 -0
  170. /package/{skills → package/corpus/skills}/cratis-chronicle-multi-tenancy/LICENSE +0 -0
  171. /package/{skills → package/corpus/skills}/cratis-chronicle-multi-tenancy/SKILL.md +0 -0
  172. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/LICENSE +0 -0
  173. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/SKILL.md +0 -0
  174. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/references/fluent-builder.md +0 -0
  175. /package/{skills → package/corpus/skills}/cratis-chronicle-projection/references/model-bound-attributes.md +0 -0
  176. /package/{skills → package/corpus/skills}/cratis-chronicle-reactor/LICENSE +0 -0
  177. /package/{skills → package/corpus/skills}/cratis-chronicle-reactor/SKILL.md +0 -0
  178. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model/LICENSE +0 -0
  179. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model/SKILL.md +0 -0
  180. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model/references/queries.md +0 -0
  181. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model-specifications/LICENSE +0 -0
  182. /package/{skills → package/corpus/skills}/cratis-chronicle-read-model-specifications/SKILL.md +0 -0
  183. /package/{skills → package/corpus/skills}/cratis-chronicle-reducer/LICENSE +0 -0
  184. /package/{skills → package/corpus/skills}/cratis-chronicle-reducer/SKILL.md +0 -0
  185. /package/{skills → package/corpus/skills}/cratis-chronicle-web-workbench/LICENSE +0 -0
  186. /package/{skills → package/corpus/skills}/cratis-chronicle-web-workbench/SKILL.md +0 -0
  187. /package/{skills → package/corpus/skills}/cratis-cli-terminal-workbench/LICENSE +0 -0
  188. /package/{skills → package/corpus/skills}/cratis-cli-terminal-workbench/SKILL.md +0 -0
  189. /package/{skills → package/corpus/skills}/cratis-code-review/LICENSE +0 -0
  190. /package/{skills → package/corpus/skills}/cratis-code-review/SKILL.md +0 -0
  191. /package/{skills → package/corpus/skills}/cratis-components-accessibility/LICENSE +0 -0
  192. /package/{skills → package/corpus/skills}/cratis-components-accessibility/SKILL.md +0 -0
  193. /package/{skills → package/corpus/skills}/cratis-components-schema-editor/LICENSE +0 -0
  194. /package/{skills → package/corpus/skills}/cratis-components-schema-editor/SKILL.md +0 -0
  195. /package/{skills → package/corpus/skills}/cratis-components-stepper-command-dialog/LICENSE +0 -0
  196. /package/{skills → package/corpus/skills}/cratis-components-stepper-command-dialog/SKILL.md +0 -0
  197. /package/{skills → package/corpus/skills}/cratis-components-styling/LICENSE +0 -0
  198. /package/{skills → package/corpus/skills}/cratis-components-styling/SKILL.md +0 -0
  199. /package/{skills → package/corpus/skills}/cratis-components-toolbar/LICENSE +0 -0
  200. /package/{skills → package/corpus/skills}/cratis-components-toolbar/SKILL.md +0 -0
  201. /package/{skills → package/corpus/skills}/cratis-documentation-writing/LICENSE +0 -0
  202. /package/{skills → package/corpus/skills}/cratis-documentation-writing/SKILL.md +0 -0
  203. /package/{skills → package/corpus/skills}/cratis-event-model-diagram/LICENSE +0 -0
  204. /package/{skills → package/corpus/skills}/cratis-event-model-diagram/SKILL.md +0 -0
  205. /package/{skills → package/corpus/skills}/cratis-fundamentals-concept/LICENSE +0 -0
  206. /package/{skills → package/corpus/skills}/cratis-fundamentals-concept/SKILL.md +0 -0
  207. /package/{skills → package/corpus/skills}/cratis-fundamentals-type-discovery/LICENSE +0 -0
  208. /package/{skills → package/corpus/skills}/cratis-fundamentals-type-discovery/SKILL.md +0 -0
  209. /package/{skills → package/corpus/skills}/cratis-governed-release-methodology/LICENSE +0 -0
  210. /package/{skills → package/corpus/skills}/cratis-governed-release-methodology/SKILL.md +0 -0
  211. /package/{skills → package/corpus/skills}/cratis-lens-browser-extension/LICENSE +0 -0
  212. /package/{skills → package/corpus/skills}/cratis-lens-browser-extension/SKILL.md +0 -0
  213. /package/{skills → package/corpus/skills}/cratis-performance-review/LICENSE +0 -0
  214. /package/{skills → package/corpus/skills}/cratis-performance-review/SKILL.md +0 -0
  215. /package/{skills → package/corpus/skills}/cratis-screenplay-model-authoring/LICENSE +0 -0
  216. /package/{skills → package/corpus/skills}/cratis-screenplay-model-authoring/SKILL.md +0 -0
  217. /package/{skills → package/corpus/skills}/cratis-security-review/LICENSE +0 -0
  218. /package/{skills → package/corpus/skills}/cratis-security-review/SKILL.md +0 -0
  219. /package/{skills → package/corpus/skills}/cratis-specification-by-example/LICENSE +0 -0
  220. /package/{skills → package/corpus/skills}/cratis-specification-by-example/SKILL.md +0 -0
  221. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/LICENSE +0 -0
  222. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/SKILL.md +0 -0
  223. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/references/application-scenarios.md +0 -0
  224. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/references/csharp-patterns.md +0 -0
  225. /package/{skills → package/corpus/skills}/cratis-specifications-csharp/references/integration-specs.md +0 -0
  226. /package/{skills → package/corpus/skills}/cratis-specifications-typescript/LICENSE +0 -0
  227. /package/{skills → package/corpus/skills}/cratis-specifications-typescript/SKILL.md +0 -0
  228. /package/{skills → package/corpus/skills}/cratis-specifications-typescript/references/typescript-patterns.md +0 -0
  229. /package/{skills → package/corpus/skills}/cratis-stage-rendering-and-sandbox/LICENSE +0 -0
  230. /package/{skills → package/corpus/skills}/cratis-stage-rendering-and-sandbox/SKILL.md +0 -0
  231. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/LICENSE +0 -0
  232. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/SKILL.md +0 -0
  233. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/references/blocked-tools.md +0 -0
  234. /package/{skills → package/corpus/skills}/cratis-studio-mcp-safety-guidance/references/observational-tools.md +0 -0
@@ -0,0 +1,434 @@
1
+ # Hooks — enforcement, not persuasion
2
+
3
+ Everything else in `.cratis/ai/` is text an agent may or may not follow. The files here are the part
4
+ that runs. They convert the mechanically-checkable Cratis invariants into deterministic checks
5
+ that fire whether or not the model remembered the rule.
6
+
7
+ Three layers:
8
+
9
+ | Layer | Event | Script | Cost | Effect |
10
+ |---|---|---|---|---|
11
+ | Pattern pass | `PostToolUse` on a write | `scripts/cratis-pattern-scan.sh` | zero tokens until a match | appends a one-line reminder to context, never blocks |
12
+ | Hard block | `PreToolUse` on a write | `scripts/cratis-guard-writes.sh` | zero | exits **2** — the write does not happen |
13
+ | Quality gate | `Stop` | `scripts/cratis-quality-gate.sh` | one build/test run, only when relevant files changed | exits **2** — the turn does not end |
14
+
15
+ The Claude Code wiring that fires them is tracked here, in
16
+ [`settings.template.json`](./settings.template.json). Claude reads `.claude/settings.json`, which is
17
+ per-machine and gitignored, so activate the hooks by copying the template once:
18
+
19
+ ```bash
20
+ cp .cratis/ai/hooks/settings.template.json .claude/settings.json
21
+ ```
22
+
23
+ If you already have a `.claude/settings.json`, merge the template's `hooks` block into it rather
24
+ than overwriting — the rest of that file is yours. Re-copy after the template changes; the copy is
25
+ not a symlink, so it does not update itself. **Edit the template, never the copy**: `.cratis/ai/` is the
26
+ source of truth (see [`../rules/managing-ai-rules.md`](../rules/managing-ai-rules.md)), and
27
+ `scripts/validate-ai-setup.sh` checks the template against the script names this page documents.
28
+
29
+ The markdown files in this folder (`agent-stop.md`, `pre-commit.md`) remain *lifecycle guidance* —
30
+ they describe what a hook should do for tools that have no wiring yet.
31
+
32
+ > Hooks are the one surface with no folder adapter: Claude reads `.claude/settings.json`,
33
+ > Copilot would read `.github/hooks/*.json`. Only the Claude wiring exists today.
34
+
35
+ ## What is enforced
36
+
37
+ Rule numbers refer to the numbered list in [`../rules/general.md`](../rules/general.md).
38
+
39
+ **Blocked outright** (`PreToolUse`, exit 2):
40
+
41
+ - Editing a file whose header marks it as Cratis-generated output — rule 15 `[contract]`
42
+ - Writing content that opens with such a header (hand-authoring a "generated" proxy)
43
+ - `Directory.Packages.props`, `global.json`, `NuGet.config`, `yarn.lock`, `package-lock.json`,
44
+ `pnpm-lock.yaml`, `packages.lock.json` — the Source-of-Truth Discipline rule
45
+ - `.env`, `.env.*`, `*.env` — secrets
46
+
47
+ The generated-file check is anchored: the marker must be a comment opener at the start of one of
48
+ the first five lines. A rule file or a document that merely *mentions* the marker is not blocked.
49
+
50
+ **Flagged** (`PostToolUse`, exit 0 + context):
51
+
52
+ | Pattern id | Rule | Detects |
53
+ |---|---|---|
54
+ | `cratis-automap-call` | 10 `[contract]` | `.AutoMap()` in a file that never calls `.NoAutoMap()` |
55
+ | `cratis-ieventlog-in-handle` | 14 `[contract]` | `IEventLog` in a `Handle(` signature, wrapping across up to 5 lines |
56
+ | `cratis-nullable-event-property` | 6 `[contract]` | a nullable property inside a type declared with `[EventType]` |
57
+ | `cratis-route-on-readmodel` | 12 `[contract]` | `[Route(` inside a type declared with `[ReadModel]` |
58
+ | `cratis-controller-base` | 1 `[contract]` | `: ControllerBase` in a file that imports `Microsoft.AspNetCore.Mvc` |
59
+ | `cratis-primereact-dialog-import` | 16 `[convention]` | `from 'primereact/dialog'` |
60
+
61
+ The two `within_type_attribute` patterns are not line greps — the scanner tracks C# attribute
62
+ blocks and type scope (positional record, multi-line declaration, or braced body), so a nullable
63
+ property is only reported when it really sits inside an `[EventType]`.
64
+
65
+ **Gated** (`Stop`, exit 2): the app-pinned commands from the Quality Gates table in
66
+ `general.md` and the steps in [`agent-stop.md`](./agent-stop.md) — Debug build, specs, Release
67
+ build (with `-p:CratisProxiesOutputPath=` per `general.md`, so the proxy generator does not
68
+ re-run and touch already-correct generated files), frontend lint / compile / compile-specs /
69
+ test, and `validate-ai-setup.sh` for corpus changes.
70
+
71
+ ## The corpus validator
72
+
73
+ `scripts/validate-ai-setup.sh` sits outside the three layers: it validates `.cratis/ai/` itself, and both
74
+ the `Stop` gate and the `ai-corpus` CI job run it. Structural, adapter and Codex checks are
75
+ **fatal**; the content drift guards **warn**.
76
+
77
+ ### Package subpath existence — `scripts/validate-package-subpaths.sh` (warn)
78
+
79
+ Every other drift guard asserts that a string should *not* appear. This one is the other direction,
80
+ and the only guard that knows what a package is. It extracts each `@cratis/<pkg>/<subpath>` the
81
+ corpus names — fenced blocks, inline spans and table cells alike — from `.cratis/ai/rules`, `.cratis/ai/skills`,
82
+ `.cratis/ai/agents` and `.cratis/ai/prompts`, then resolves it against the `exports` map of the package installed
83
+ in `node_modules`. The exports map is exact and machine-readable, so a miss is a genuine miss.
84
+ `.cratis/ai/hooks` is deliberately *not* one of the default roots — this page names bogus subpaths as
85
+ examples, and a guard that reports its own documentation is a guard people switch off.
86
+
87
+ It exists because nothing in the repository could catch documenting
88
+ `@cratis/components/Notifications` (a subpath that first ships in **3.0.0**) while the pin is
89
+ **2.6.1**. A prose-pattern matcher has no notion of a package, a version, or an exports map; a
90
+ developer following the corpus got a module-resolution failure.
91
+
92
+ **Warn, never fail — the tradeoff.** The observation is exact but the conclusion is not: "the corpus
93
+ names an API that does not exist" and "this repository is pinned behind the version the corpus
94
+ documents" look identical from the exports map. This script propagates to every Cratis repository,
95
+ and the `ai-corpus` CI job checks out the tree and installs nothing — so failing would be a
96
+ permanent no-op in CI while turning repos red locally for their own dependency pin. The warning
97
+ names the file, the line and the installed version, and leaves the judgement to a human.
98
+
99
+ > **What this repository is.** `Cratis/AI` is a corpus of markdown, JSON and a little
100
+ > JavaScript — it has no `Source/`, no `.slnx`, no `package.json` and no C# or TypeScript
101
+ > project of its own. Every `.cs` / `.ts` / `Source/**` reference below describes what the
102
+ > hooks do in a **consuming** repository. Here they are silent, which is the designed
103
+ > behavior, not a broken setup.
104
+
105
+ **Silent when it cannot judge.** No `jq`, no `node_modules`, a package this repository does not
106
+ depend on, or a package published without an `exports` map: skipped without a word. "Not installed"
107
+ is not a finding.
108
+
109
+ **Version-qualified lines are not drift.** The corpus deliberately documents some 3.0.0+ APIs
110
+ against a 2.x pin, marked inline as `(**≥ 3.0.0**)`. A reference is cleared when a line mentioning
111
+ it in the same file also carries a version — a dotted number, an `N.x`, or either inequality
112
+ spelling. Qualification is judged per *(file, reference)* rather than per line, because the corpus
113
+ states a requirement once and then mentions the subpath again unqualified nearby; per-line matching
114
+ would fire on exactly the lines someone had just fixed correctly. The check is deliberately generous
115
+ in the same direction: it would rather miss a stale line than warn about a correct one.
116
+
117
+ **What it deliberately does not check.** Named imports (`import { Toaster } from '…'`) are Tier 2's
118
+ job, below; .NET types named in prose or in a C# type position are Tier 3's. This tier checks module
119
+ specifiers, nothing else.
120
+
121
+ Run it standalone, optionally over other roots, and add `CRATIS_HOOKS_SUBPATH_REPORT=1` to see every
122
+ reference and how it resolved rather than only the failures. It invokes Tier 3 before its own gates
123
+ and Tier 2 after its own work, over the same roots, so the single call site in
124
+ `validate-ai-setup.sh` gets all three.
125
+
126
+ ### Named import existence — `scripts/validate-package-imports.sh` (warn)
127
+
128
+ Tier 2, and the reason it exists is that Tier 1's answer is not the whole question: a subpath that
129
+ resolves says nothing about the *names* imported through it. For every
130
+ `import { A, B } from '@cratis/<pkg>/<subpath>'` in the corpus — single-line, brace-on-its-own-line,
131
+ `import type`, `A as B` (the *imported* name is what has to exist), trailing `//` comments — it
132
+ checks each identifier against the `.d.ts` closure of the installed package and warns about the ones
133
+ that are not there. `Toaster`, `toastCommandResult`, `PasswordField`, `RatingField` and the rest are
134
+ real APIs of `@cratis/components` **3.0.0** and absent from **2.6.1**; Tier 1 caught the three
135
+ *subpaths* that moved with them, and the twelve *names* were found only by a human reading package
136
+ internals.
137
+
138
+ **Deliberately permissive, and here is the price.** A name passes when it appears as a *word
139
+ anywhere* in the package's `.d.ts` closure — not only in an export position, not only behind the
140
+ subpath it was imported from — and the closure follows `export … from '<other-package>'` re-exports
141
+ one level out to another installed package. Intra-package barrels (`export * from './X'`) need no
142
+ following, because the whole tree is read either way. That admits names the package merely
143
+ *references* (an imported PrimeReact symbol, a name in a doc comment) and it will not notice a name
144
+ imported from the wrong subpath of the right package. The trade is deliberate: a false warning
145
+ trains people to ignore the guard, a missed one costs a stale line. Measured over the corpus's 85
146
+ import statements / 134 bindings / 38 distinct *(package, name)* pairs plus a 36-pair all-valid
147
+ probe: **zero false positives**, and it still flags all twelve of the 3.0.0 names above when they are
148
+ written unqualified.
149
+
150
+ **Same warn-only, same silence, same version rule as Tier 1.** No `jq`, no `node_modules`, a package
151
+ this repository does not depend on, or a package that ships no `.d.ts`: skipped without a word. A
152
+ name is cleared when any line in the same file that mentions it also carries a version — judged per
153
+ *(file, name)*, for the same reason Tier 1 judges per *(file, reference)*.
154
+
155
+ **What it deliberately does not check.** Identifiers that never appear inside an `import { … }`:
156
+ prose mentions, JSX usages, and C# type positions are all invisible. It reads TypeScript import
157
+ statements, nothing else.
158
+
159
+ Run it standalone over any roots, and add `CRATIS_HOOKS_IMPORT_REPORT=1` to see every binding and how
160
+ it resolved rather than only the failures.
161
+
162
+ ### .NET type existence — `scripts/validate-type-references.sh` (warn)
163
+
164
+ Tier 3, and the only tier that reads .NET rather than TypeScript. Tiers 1 and 2 both start from an
165
+ `import` statement, so a type the corpus names *only* in prose and in C# type positions is invisible
166
+ to both. That is exactly how `ReactorSideEffect` survived: never a module specifier, never an import,
167
+ told readers to return it from a reactor, shown with object-initializer syntax — and never a type in
168
+ any Chronicle release. Someone following the corpus wrote code that does not compile.
169
+
170
+ **The index.** Every `Cratis*` version pinned in `Directory.Packages.props` — or, in the corpus
171
+ repository itself, in the tracked pin list `scripts/cratis-nuget-pins.txt`, which names the exact
172
+ product versions the skills verify against — plus the Cratis packages those pull in (`Cratis` is a
173
+ metapackage), resolved against the local NuGet cache. A pin moves only together with the skill
174
+ whose verified version moved.
175
+
176
+ **Exit codes.** `0` ran (warnings, if any, are on stderr); `1` a `--self-test` expectation failed;
177
+ `2` could not run — no pin source, no NuGet cache, or an index that came up empty — with the reason
178
+ on stderr. "Ran and found nothing" and "never looked" are different verdicts
179
+ (`exit-codes-and-wrappers.md`), and this guard spent its first lifetime erasing that difference by
180
+ exiting `0` at the `Directory.Packages.props` gate in a repository that has none (#287).
181
+
182
+ **Self-test.** `--self-test` seeds the motivating fabrication (`ReactorSideEffect`, in prose, in
183
+ attribute position, beside the real names it must be distinguished from) into a scratch corpus and
184
+ fails unless the guard names it and keeps the real types silent. Run it after any change to the
185
+ extraction rules, the pin list, or the allowlist — a guard that can pass vacuously is worse than no
186
+ guard (`guards-and-fuses.md`). Each package's
187
+ `lib/**/*.xml` carries `<member name="T:Full.Namespace.TypeName">` — a complete machine-readable type
188
+ list — and every other identifier the docs mention is kept as a second, permissive accept list, in
189
+ the same spirit as Tier 2's "a word anywhere in the `.d.ts` closure". Names the corpus itself
190
+ declares, and names declared in the consuming repository's own `Source/**/*.cs`, are accepted too: a worked
191
+ example that writes `public record AuthorRegistered(…)` before using it is not documenting a
192
+ framework API. A curated allowlist covers the rest — see below.
193
+
194
+ **Why it is narrow, and what that cost.** The naive version of this check is the reason the whole
195
+ tier nearly did not ship. Of the **1279** distinct PascalCase names it reads across 151 corpus files,
196
+ **599 — 47% — resolve nowhere**, because the corpus legitimately invents domain examples
197
+ (`AuthorRegistered`, `IAuthorService`), placeholders and prose nouns. A guard that cries wolf 599
198
+ times gets switched off, and then it protects nothing. So only two constructs are ever reported:
199
+
200
+ | Construct | Why it is safe | Measured |
201
+ |---|---|---|
202
+ | **Attribute position** — `[Name]`, `[Name<T>]`, `[Name(…)]` inside an inline code span or a fenced `csharp` block | attribute brackets are unambiguous C#, and a markdown link cannot live inside a code span, so the syntax alone identifies an API reference; `Name` and `NameAttribute` both count | 686 occurrences, 61 distinct names |
203
+ | **Framework-adjacent type token** — any other PascalCase token in a code span or a fenced `csharp` block that resolves nowhere **and** is a strict PascalCase-word-boundary *prefix* of a real Cratis type name | that is the fabrication signature: a half-remembered real family of names with a member coined that was never minted. `ReactorSideEffect` is a prefix of `ReactorSideEffectFailure`; `AuthorRegistered` is a prefix of nothing Cratis ships | takes the 599 unresolved down to **2** |
204
+
205
+ Both remaining names — `ICommand` and `IQuery`, which do not exist — are cleared by the absence rule
206
+ below, because the corpus's own point about them is exactly that. **Zero warnings on the real
207
+ corpus.**
208
+
209
+ **Constructs measured and rejected.** Each was extracted over the whole corpus and its unresolved
210
+ names counted before being dropped: `new TypeName` in a fenced `csharp` block (**17** false positives —
211
+ example events are constructed but never declared), `IInterfaceName` in a fenced `csharp` block (**17** —
212
+ invented example services like `IOrderRepository`), the same in an inline code span (**23** —
213
+ TypeScript interfaces and shouty prose such as `IMPORTANT`), and in bare prose (**2**, including the
214
+ plural `IDs`). None of them survives the "precision over recall" test on its own. They are all still
215
+ *read*; they simply have to earn a warning through framework-adjacency instead of through syntax.
216
+
217
+ **Three structural exclusions, no allowlist needed.** A token is skipped when it is preceded by `.`
218
+ (a member, not a type), when it is ALL-CAPS (`PII`, `IMPORTANT`), and when it is written as
219
+ `<Placeholder>` — the corpus's `<Module>/<Feature>/<Slice>` idiom, distinguished from a generic
220
+ argument list by the character before the `<`, which in C# is always an identifier character.
221
+
222
+ **Same warn-only and same version rule as Tiers 1 and 2, plus one of its own.** A name is cleared
223
+ when any line in the same file that mentions it carries a version, *or* says the thing does not
224
+ exist — `does not exist`, `no longer`, `never use`, `removed`, `deprecated`, `there is no` and
225
+ friends. Part of this corpus's job is naming APIs that are **not** real, and warning about a line
226
+ whose entire point is that the type is fictional would be the most annoying false positive of all.
227
+ The cost is stated plainly: reintroduce a fabrication into a sentence containing one of those
228
+ phrases and the guard stays quiet.
229
+
230
+ **Silent when it cannot judge.** No `Directory.Packages.props`, no local NuGet cache, or a cache
231
+ holding none of the pinned versions: skipped without a word. It needs no `jq` and no `node_modules`,
232
+ which is why Tier 1 invokes it *above* its own gates rather than beside the Tier 2 call — a backend-
233
+ only repository must still get this check. It adds about 1.4 s to `validate-ai-setup.sh`.
234
+
235
+ **The allowlist — `scripts/type-references-allowlist.txt`.** Thirteen entries, each with a written
236
+ justification: ASP.NET Core and BCL attributes that live in ref packs (which ship no XML docs at
237
+ all), Orleans and `Microsoft.Extensions.*` attributes from packages that ship none either, `[CliCommand]`
238
+ / `[CliExample]` from the separate `Cratis/cli` repository, the Chronicle **Kernel**'s `WellKnown`,
239
+ and `@cratis/fundamentals`' TypeScript `JsonSerializer`. Every one was verified real before being
240
+ listed. An entry is a small lie the guard tells itself, so prefer widening the index whenever that
241
+ is possible, and never add a name you have not confirmed exists.
242
+
243
+ **What it deliberately does not check.** TypeScript — that is Tiers 1 and 2. Members, methods and
244
+ properties: `Provide()`, `.AutoMap()` and `EventStoreName.NotSet` are all invisible, and a fabricated
245
+ *member* on a real type would pass. And a fabricated type that is not a prefix of any real Cratis
246
+ name is invisible too — the adjacency filter is what buys the precision, and it is also the ceiling
247
+ on the recall.
248
+
249
+ Run it standalone over any roots, and add `CRATIS_HOOKS_TYPE_REPORT=1` to see every distinct name and
250
+ how it resolved rather than only the failures.
251
+
252
+ ## Configuration is data, not code
253
+
254
+ Neither the pattern list nor the gate commands live in a script. A consuming repository
255
+ customises both without forking anything:
256
+
257
+ | File | Purpose |
258
+ |---|---|
259
+ | `scripts/cratis-patterns.json` | shipped pattern set; its header `$comment` documents every field |
260
+ | `scripts/cratis-patterns.local.json` | optional; merged over the above by `id` — add patterns, or set `"enabled": false` to silence one |
261
+ | `scripts/quality-gates.json` | shipped gates; `changed` globs decide when a gate runs, `requires` and `workingDirectoryFrom` decide whether it *can* |
262
+
263
+ A gate whose `requires.commands` are not on `PATH`, whose `requires.paths` do not exist, or whose
264
+ `workingDirectoryFrom` matches nothing in the repository, is a **no-op with a message on stderr**
265
+ rather than a failure — that is how a repository with no .NET solution or no frontend stays quiet.
266
+
267
+ **No shipped gate names a product's file.** A default that did would activate in exactly one
268
+ repository and silently no-op in every other, which is the worst of both: it looks configured and
269
+ checks nothing. So the .NET and frontend gates state *what kind of project* they build and let the
270
+ gate script find it — `workingDirectoryFrom: ["*.slnx", "*.sln", "**/*.slnx", "**/*.sln"]` runs
271
+ `dotnet build` in whichever directory holds the repository's own solution, preferring one at the
272
+ root because the globs are tried in order. The frontend gates discover `package.json` the same way.
273
+ The same shipped file therefore activates in an application repository, activates in a framework
274
+ repository, and stays quiet in a corpus-only repository like this one, which has no project at all.
275
+
276
+ **Overriding it, in order of increasing force.** Set `workingDirectory` on a gate to pin one of
277
+ several candidate projects; drop a `quality-gates.json` of your own in place of the shipped one; or
278
+ point `CRATIS_HOOKS_GATES` at a file anywhere. None of them requires forking the script.
279
+
280
+ **Profile note.** The C# patterns are application-profile and scoped to `Source/**/*.cs`, which is
281
+ the application source root [`../rules/general.md`](../rules/general.md) documents — not a path in
282
+ this repository, which has no C# at all. A framework-profile repository (Arc, Chronicle,
283
+ Fundamentals, Components — see [`../rules/framework.md`](../rules/framework.md)) has no vertical
284
+ slices and should disable them in its `cratis-patterns.local.json`; a repository whose application
285
+ source root is not `Source/` re-scopes the `paths` globs there too.
286
+
287
+ **One property gates the proxy generator.** The generator's MSBuild target is
288
+ `Condition="'$(CratisProxiesOutputPath)' != ''"`, so clearing that property with
289
+ `-p:CratisProxiesOutputPath=` is the *only* way to make it no-op. There is no
290
+ `DisableProxyGenerator` property — MSBuild silently accepts unknown `-p:` names, so passing one
291
+ looks like it works and changes nothing. A consuming repository's build workflow should split the
292
+ two configurations the way the shipped gates do: Release clears the path, Debug does not, because
293
+ `general.md` makes the Debug build the canonical trigger for regenerating the TypeScript proxies
294
+ the frontend phase depends on.
295
+
296
+ ## Escape hatches
297
+
298
+ Each is an explicit, auditable opt-out — none of them is a default.
299
+
300
+ | Variable | Effect |
301
+ |---|---|
302
+ | `CRATIS_HOOKS_ALLOW_PROTECTED_WRITES=1` | allows one protected write; this is the "unless explicitly asked" case for dependency manifests |
303
+ | `CRATIS_HOOKS_SKIP_SCAN=1` | disables the pattern pass |
304
+ | `CRATIS_HOOKS_SKIP_GATE=1` | disables the quality gate |
305
+ | `CRATIS_HOOKS_GATE_DRYRUN=1` | prints which gates would run, and why, then exits 0 |
306
+ | `CRATIS_HOOKS_PATTERNS=<path>` | replaces the pattern file |
307
+ | `CRATIS_HOOKS_GATES=<path>` | replaces the gate file |
308
+ | `CRATIS_HOOKS_SUBPATH_REPORT=1` | prints every `@cratis/*` subpath reference and how it resolved, not only the failures |
309
+ | `CRATIS_HOOKS_IMPORT_REPORT=1` | prints every `@cratis/*` named import binding and how it resolved, not only the failures |
310
+ | `CRATIS_HOOKS_TYPE_REPORT=1` | prints every .NET type/attribute name the corpus mentions and how it resolved, not only the failures |
311
+
312
+ ## Design constraints
313
+
314
+ - **POSIX-safe bash**, `set -euo pipefail`, quoted expansions, no `eval`. Verified on bash 3.2
315
+ (macOS system bash) — no `mapfile`, no associative arrays, no GNU-only flags, `LC_ALL=C` on
316
+ every sort and compare.
317
+ - **Gate commands are an argv array**, executed directly. They never pass through a shell.
318
+ - **`jq` is the only dependency.** Every script
319
+ degrades to a silent no-op when it is missing — a hook must never break a session.
320
+ - **Fail safe.** Malformed config, empty stdin, a missing file, a binary file, a file over 2 MB:
321
+ all exit 0 silently.
322
+ - **No secrets, no file dumps.** Gate output is capped at `maxOutputLines`; the pattern pass
323
+ prints a path, a line number and a fixed message — never file content.
324
+ - **No re-entry.** The `Stop` hook returns immediately when `stop_hook_active` is true, so a
325
+ blocked turn cannot loop.
326
+ - **Each pattern fires once per file per session**, tracked under
327
+ `${TMPDIR}/cratis-hooks/<session-id>/`, so a long edit loop cannot flood context.
328
+ - **The gate never edits code.** It builds, tests and lints. The one side effect is that a Debug
329
+ build regenerates TypeScript proxies, which is the documented purpose of that build.
330
+
331
+ ## Verifying a change
332
+
333
+ The scripts read hook JSON on stdin, so they are directly testable:
334
+
335
+ The pattern pass and the gate both read the repository they are pointed at, so testing them means
336
+ pointing them at a repository that *has* the thing under test. This corpus has no C# and no
337
+ project, so run those two against a consuming checkout (or a scratch tree), and expect silence here.
338
+
339
+ ```bash
340
+ # Pattern pass — expect exit 0, and JSON on stdout only when something matched.
341
+ # Run from an application checkout; <Module>/<Feature>/<Slice> is the layout general.md documents.
342
+ jq -nc '{session_id:"t", cwd:"'"$PWD"'", tool_name:"Edit",
343
+ tool_input:{file_path:"'"$PWD"'/Source/<Module>/<Feature>/<Slice>/<Slice>.cs"}}' \
344
+ | .cratis/ai/hooks/scripts/cratis-pattern-scan.sh; echo "exit=$?"
345
+
346
+ # Hard block — expect exit 2
347
+ jq -nc '{session_id:"t", cwd:"'"$PWD"'", tool_name:"Edit",
348
+ tool_input:{file_path:"'"$PWD"'/Directory.Packages.props", new_string:"x"}}' \
349
+ | .cratis/ai/hooks/scripts/cratis-guard-writes.sh; echo "exit=$?"
350
+
351
+ # Quality gate — show the dispatch plan without running anything
352
+ jq -nc '{session_id:"t", cwd:"'"$PWD"'", stop_hook_active:false}' \
353
+ | CRATIS_HOOKS_GATE_DRYRUN=1 .cratis/ai/hooks/scripts/cratis-quality-gate.sh
354
+ ```
355
+
356
+ The subpath guard takes corpus roots as arguments, so it is testable in both directions without
357
+ touching the corpus — point it at a scratch folder holding a known-bad reference, then at the real
358
+ roots. A one-sided test passes vacuously; run both.
359
+
360
+ ```bash
361
+ # Negative — expect a warning naming the file and line
362
+ mkdir -p /tmp/scratch-corpus
363
+ echo "import x from '@cratis/components/ThisDoesNotExist';" > /tmp/scratch-corpus/drift.md
364
+ .cratis/ai/hooks/scripts/validate-package-subpaths.sh .cratis/ai/rules /tmp/scratch-corpus
365
+
366
+ # Positive — expect silence, and the report to show every real reference resolving
367
+ CRATIS_HOOKS_SUBPATH_REPORT=1 .cratis/ai/hooks/scripts/validate-package-subpaths.sh
368
+ ```
369
+
370
+ Tier 2 is testable the same way, and wants a third run the subpath guard does not: a probe of names
371
+ that all genuinely exist. A guard that warns on everything passes the negative test just as well as
372
+ a correct one, so prove it stays quiet when it should.
373
+
374
+ ```bash
375
+ # Negative — a fabricated name behind a subpath that resolves
376
+ mkdir -p /tmp/scratch-corpus
377
+ echo "import { CommandDialog, ThisNameDoesNotExist } from '@cratis/components/CommandDialog';" \
378
+ > /tmp/scratch-corpus/drift.md
379
+ .cratis/ai/hooks/scripts/validate-package-imports.sh /tmp/scratch-corpus
380
+
381
+ # Discrimination — every name real, expect silence
382
+ echo "import { DataPage, MenuItem } from '@cratis/components/DataPage';" \
383
+ > /tmp/scratch-corpus/drift.md
384
+ .cratis/ai/hooks/scripts/validate-package-imports.sh /tmp/scratch-corpus
385
+
386
+ # Positive — the real corpus, with the report showing every binding resolving
387
+ CRATIS_HOOKS_IMPORT_REPORT=1 .cratis/ai/hooks/scripts/validate-package-imports.sh
388
+ ```
389
+
390
+ Tier 3 wants the same three runs, and its negative case is the one that motivated it. Put
391
+ `ReactorSideEffect` back into a scratch corpus and the guard must name it; a design that misses its
392
+ own motivating case is the wrong design.
393
+
394
+ ```bash
395
+ # Negative — the confirmed fabrication, in prose and in object-initializer syntax
396
+ mkdir -p /tmp/scratch-corpus
397
+ printf 'A reactor may return a `ReactorSideEffect` to control where the event is appended.\n' \
398
+ > /tmp/scratch-corpus/drift.md
399
+ .cratis/ai/hooks/scripts/validate-type-references.sh /tmp/scratch-corpus
400
+
401
+ # Discrimination — every name real, expect silence
402
+ printf 'Return `EventForEventSourceId`, or a `ReactorSideEffectFailure` from an `IReactor`.\n' \
403
+ > /tmp/scratch-corpus/drift.md
404
+ .cratis/ai/hooks/scripts/validate-type-references.sh /tmp/scratch-corpus
405
+
406
+ # Positive — the real corpus, expect silence, with the report showing how each name resolved
407
+ CRATIS_HOOKS_TYPE_REPORT=1 .cratis/ai/hooks/scripts/validate-type-references.sh
408
+ ```
409
+
410
+ Run `bash -n` on every script and `jq .` on every JSON file before committing. The hook scripts are
411
+ kept at **zero** `shellcheck --external-sources --severity=style` findings by the **Lint the hook
412
+ scripts** step of the `Verify AI Corpus` workflow (`.github/workflows/verify-ai-corpus.yml`), which
413
+ fails the run on any finding at that severity or above. Run the same command before committing:
414
+
415
+ ```bash
416
+ shellcheck --external-sources --severity=style .cratis/ai/hooks/scripts/*.sh
417
+ ```
418
+
419
+ The CI step counts the scripts it checked and refuses to pass on an empty population, so a glob that
420
+ stops matching is a failure rather than a silent green. A finding that is genuinely a false positive
421
+ is silenced with a `# shellcheck disable=SC…` directive carrying a comment that says why — never by
422
+ loosening the severity.
423
+
424
+ The step uses whatever shellcheck the runner image ships, and prints its version first. Different
425
+ versions genuinely disagree: 0.9.0 flags `A && B || C` (SC2015) where 0.11.0 does not, so a local
426
+ run can be green while CI is red. The scripts are currently clean under **both** 0.9.0 and 0.11.0.
427
+ If a runner image upgrade introduces a new finding, fix the script — the version line at the top of
428
+ the step log says which version changed its mind.
429
+
430
+ ## Note on `.claude/settings.local.json`
431
+
432
+ If that file carries `allow` entries for `Bash(git push *)` and `Bash(gh pr *)`, they win: local
433
+ settings take precedence over project settings, so they override the `ask` entries the template
434
+ puts in `.claude/settings.json`. Remove them there if you want the confirmation prompt back.
@@ -0,0 +1,49 @@
1
+ ---
2
+ lifecycle: session-stop
3
+ ---
4
+
5
+ # Agent Stop — Build, Specs, and Corpus Validation
6
+
7
+ > **This is lifecycle guidance, not a wired tool hook.** Markdown is not a hook format for Copilot or Claude Code. To *enforce* it, wire it per tool to run the repo's build/test command — Claude Code: a `Stop` hook in `.claude/settings.json`; GitHub Copilot: a `sessionEnd` entry in a `.github/hooks/*.json` file. The steps below are what that hook (or the agent) should do.
8
+
9
+ When the agent finishes a session, verify the work against **fresh signals** before stopping — never against self-assessment. Pick the path that matches the repository.
10
+
11
+ ## Pick the path for this repository
12
+
13
+ - **AI corpus repo** — the changes are only under `.cratis/ai/`, `.github/`, or `.claude/` and there is no .NET solution or frontend to build (e.g. this `cratis/AI` repo). Run the AI-setup validator instead of a code build:
14
+ ```
15
+ .cratis/ai/hooks/scripts/validate-ai-setup.sh
16
+ ```
17
+ Stop only when it passes (symlinks/adapters healthy, frontmatter present, no broken cross-links). Skip the application gates below.
18
+
19
+ - **Application repo** — there is a .NET solution and/or a frontend. Run the application gates below.
20
+
21
+ ## Application gates
22
+
23
+ 1. **Clean** from repository root:
24
+ ```
25
+ dotnet clean
26
+ ```
27
+ 2. **Build Debug** from repository root — validates `#if DEBUG` spec code and regenerates the TypeScript proxies:
28
+ ```
29
+ dotnet build
30
+ ```
31
+ 3. **Build Release** from repository root — build-only check; skip re-running proxy generation:
32
+ ```
33
+ dotnet build -c Release -p:CratisProxiesOutputPath=
34
+ ```
35
+ 4. **Run specs/tests for every affected project** — use the project's test command; if you cannot isolate the affected scope, run the repository-level test command.
36
+ 5. **Frontend** (when frontend files changed) — run lint, the type/build check, and frontend tests.
37
+
38
+ ## If any gate fails
39
+
40
+ - Report the full output.
41
+ - Fix all errors, warnings, and failing specs before considering the session complete.
42
+ - Re-run the gate that failed and confirm it passes *this time*.
43
+
44
+ ## Rules
45
+
46
+ - A session is not complete until both Debug and Release builds exit `0` with **zero** warnings, and the affected specs/tests exit `0`.
47
+ - Treat Release-only warnings (nullable annotations, analyzer findings) as errors — fix them.
48
+ - **Never** use `/clp:ErrorsOnly` or any flag that suppresses warning output — hidden warnings are warnings that never get fixed.
49
+ - A green build is not behavioral correctness — exercise the affected behavior (specs, or the running UI) and state plainly anything you could not verify.
@@ -0,0 +1,47 @@
1
+ ---
2
+ lifecycle: pre-commit
3
+ ---
4
+
5
+ # Pre-commit — Run Specs
6
+
7
+ > **This is lifecycle guidance, not a wired tool hook.** To *enforce* it, wire it per tool — Claude Code: a `PreToolUse` hook in `.claude/settings.json` with a matcher on `Bash` (or your terminal tool) gating `git commit` (and its rtk-rewritten `rtk git commit` form — see [rtk](../rules/rtk.md)); GitHub Copilot: a hook in a `.github/hooks/*.json` file. The steps below are what that hook (or the agent) should do.
8
+
9
+ Before an explicitly authorized commit, verify the staged scope with proportional checks. Reuse fresh passing results only when they cover the exact unchanged staged inputs; otherwise run the relevant checks. Never stage unrelated edits.
10
+
11
+ ## When this guidance applies
12
+
13
+ Apply before an authorized `git commit`, including `rtk git commit` or `rtk proxy git commit`. Do not interpret recognizing a command as authorization. History rewriting (`commit --amend`, rebase, squash, or force-push) remains prohibited.
14
+
15
+ ## Steps
16
+
17
+ 1. **Confirm authorization and scope** — this guidance does not authorize a commit or create executable hook wiring. Select documentation/corpus checks for rule-only edits; do not run application tests without affected application code.
18
+
19
+ 2. **Identify affected projects** from the staged changes:
20
+ ```
21
+ git diff --name-only --cached
22
+ ```
23
+ Collect unique affected project roots:
24
+ - `.cs` files → walk up to the nearest `.csproj`.
25
+ - `.ts` / `.tsx` files → walk up to the nearest `package.json` with a `"test"` script.
26
+
27
+ 3. **Run specs for each affected .NET project**:
28
+ ```
29
+ dotnet test <specs-project-path> --no-build
30
+ ```
31
+ Use `--no-build` only when matching build outputs are current; otherwise incrementally build the affected specs project first. If the owning specs project cannot be identified, inspect project references or report the uncertainty; do not default to a root-wide test run.
32
+
33
+ 4. **Run specs for each affected TypeScript project**:
34
+ ```
35
+ yarn test
36
+ ```
37
+ Run from the package root that owns the changed files.
38
+
39
+ 5. **If a relevant check fails** — diagnose within a bounded attempt, fix only in-scope causes, and re-run the failed gate. Report unrelated/environmental failures as blockers instead of repeated retries or broad edits. Do not claim completion or bypass required gates.
40
+
41
+ 6. **When relevant required checks pass** — proceed only with the originally authorized commit and staged scope. Report the exact verification and any checks not run.
42
+
43
+ ## Rules
44
+
45
+ - Documentation/rule-only commits run relevant content, link, frontmatter, and corpus checks, not application builds/tests.
46
+ - Code changes run affected-project incremental checks and targeted regression specs after coherent changes. Wider suites and clean/Release builds require cross-cutting scope or repository merge/release gates.
47
+ - Do not bypass required failures, suppress diagnostics, or expand into unrelated cleanup. Missing prerequisites and pre-existing failures must be reported honestly.
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env bash
2
+ # PreToolUse hook — hard block on writes that must never happen.
3
+ #
4
+ # Exits 2 (block the tool call, stderr goes back to the model) for:
5
+ # 1. Generated files — anything whose header marks it as Cratis-generated output
6
+ # (.cratis/ai/rules/general.md rule 15 [contract])
7
+ # 2. Dependency manifests — Directory.Packages.props, global.json, lockfiles, NuGet config
8
+ # 3. Environment files — .env and friends (secrets)
9
+ #
10
+ # 2 and 3 come from the Source-of-Truth Discipline rule: "Don't change dependency manifests /
11
+ # lockfiles / global.json / NuGet config unless explicitly asked."
12
+ #
13
+ # Escape hatch for the "unless explicitly asked" case — the user asks, you set it for the call:
14
+ # CRATIS_HOOKS_ALLOW_PROTECTED_WRITES=1
15
+ set -euo pipefail
16
+
17
+ # SCRIPTDIR, not a path relative to the caller: shellcheck resolves a plain relative `source=`
18
+ # against the current working directory, and these hooks are linted from wherever CI happens to run.
19
+ # shellcheck source=SCRIPTDIR/hook-lib.sh
20
+ . "$(dirname "${BASH_SOURCE[0]}")/hook-lib.sh"
21
+
22
+ [ "${CRATIS_HOOKS_ALLOW_PROTECTED_WRITES:-0}" = "1" ] && exit 0
23
+
24
+ input="$(hook_read_stdin)"
25
+ [ -n "$input" ] || exit 0
26
+ hook_have jq || exit 0
27
+
28
+ root="$(hook_repo_root)"
29
+ cwd="$(hook_json "$input" '.cwd')"
30
+ [ -n "$cwd" ] || cwd="$root"
31
+
32
+ file="$(hook_json "$input" '.tool_input.file_path')"
33
+ [ -n "$file" ] || file="$(hook_json "$input" '.tool_input.notebook_path')"
34
+ [ -n "$file" ] || exit 0
35
+
36
+ file="$(hook_abspath "$file" "$cwd")"
37
+ rel="$(hook_relpath "$file" "$root")"
38
+ base="$(basename "$file")"
39
+
40
+ block() {
41
+ printf 'BLOCKED by cratis-guard-writes: %s\n\n%s\n\n%s\n' "$rel" "$1" "$2" >&2
42
+ exit 2
43
+ }
44
+
45
+ # ── 1. Generated files ────────────────────────────────────────────────────────
46
+ # The marker must be a real header: a comment opener at the start of one of the first few lines.
47
+ # Merely *mentioning* the string — documentation, a rule file, this corpus — is not a match.
48
+ marker='^[[:space:]]*(//|/\*|#|<!--)[[:space:]]*@generated by Cratis'
49
+
50
+ if [ -f "$file" ] \
51
+ && LC_ALL=C grep -Iq . "$file" 2>/dev/null \
52
+ && head -n 5 "$file" 2>/dev/null | LC_ALL=C grep -Eq "$marker"; then
53
+ block \
54
+ "This file is generated output (its header marks it as generated by Cratis). Rule 15 [contract] in .cratis/ai/rules/general.md: never edit a generated file — the next build overwrites it." \
55
+ "Instead: change the C# source it is generated from (the [Command] / [ReadModel] record) and rebuild with 'dotnet build -c Debug' to regenerate the proxy."
56
+ fi
57
+
58
+ # The same guard for authoring a *new* file that pretends to be generated output.
59
+ new_content="$(hook_json "$input" '.tool_input.content')"
60
+ [ -n "$new_content" ] || new_content="$(hook_json "$input" '.tool_input.new_string')"
61
+ if [ -n "$new_content" ] \
62
+ && printf '%s\n' "$new_content" | head -n 5 | LC_ALL=C grep -Eq "$marker"; then
63
+ block \
64
+ "The content being written opens with a Cratis generated-file header. Proxies are produced by the Cratis proxy generator, never hand-written." \
65
+ "Instead: write the C# source and run 'dotnet build -c Debug' to generate the proxy."
66
+ fi
67
+
68
+ # ── 2. Dependency manifests, lockfiles, SDK and NuGet configuration ───────────
69
+ case "$base" in
70
+ Directory.Packages.props | global.json | NuGet.config | NuGet.Config | nuget.config \
71
+ | yarn.lock | package-lock.json | pnpm-lock.yaml | packages.lock.json)
72
+ block \
73
+ "'$base' is a dependency manifest / lockfile / SDK or NuGet configuration file. .cratis/ai/rules/general.md (Source-of-Truth Discipline): don't change these unless explicitly asked." \
74
+ "If the user explicitly asked for this change, re-run the tool call with CRATIS_HOOKS_ALLOW_PROTECTED_WRITES=1 exported. Otherwise take the dependency as given and solve the problem in source."
75
+ ;;
76
+ esac
77
+
78
+ # ── 3. Environment files ──────────────────────────────────────────────────────
79
+ case "$base" in
80
+ .env | .env.* | *.env)
81
+ block \
82
+ "'$base' is an environment file and normally holds secrets. Hooks block writing it so credentials are never authored, rewritten, or echoed by an agent." \
83
+ "Instead: tell the user exactly which variable to set and let them edit the file. If they explicitly asked you to write it, re-run with CRATIS_HOOKS_ALLOW_PROTECTED_WRITES=1 exported."
84
+ ;;
85
+ esac
86
+
87
+ exit 0
@@ -0,0 +1,11 @@
1
+ # Cratis NuGet packages and exact versions the canonical corpus verifies against.
2
+ # One `Id Version` pair per line; the type-reference guard's transitive walk expands
3
+ # each into the full Cratis closure found in the local NuGet cache. Sources: the
4
+ # "Verified product sources" sections of the skills under skills/.
5
+ # Update a pin only together with the skill whose verified version moved.
6
+ Cratis.Arc 22.10.4
7
+ Cratis.Arc.Chronicle 22.10.4
8
+ Cratis.Chronicle 16.45.2
9
+ Cratis.Chronicle 17.0.1
10
+ Cratis.Chronicle.CodeAnalysis 16.45.3
11
+ Cratis.Fundamentals 7.18.2