@cratis/pi 0.0.1 → 2.0.2

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,261 @@
1
+ {
2
+ "$comment": [
3
+ "Quality gates run by the Stop hook (cratis-quality-gate.sh).",
4
+ "Data, not code: a consuming repository overrides this file (or points CRATIS_HOOKS_GATES at",
5
+ "its own) without touching the script. Gates run in order and stop at the first failure.",
6
+ "",
7
+ "A gate runs only when the working tree contains a change matching one of its \"changed\" globs,",
8
+ "AND every entry in \"requires\" is satisfied, AND its project was discovered. Any of those",
9
+ "unsatisfied makes the gate a NO-OP with a message on stderr \u2014 that is how a repo that has no",
10
+ ".NET, no frontend, or no source at all stays quiet.",
11
+ "",
12
+ "Gate fields:",
13
+ " id stable identifier, shown in failure output",
14
+ " description one line, shown in the dry-run plan",
15
+ " changed globs (repo-relative) that must match a changed file for the gate to run",
16
+ " excludeChanged globs that never count as a relevant change",
17
+ " requires.commands executables that must be on PATH",
18
+ " requires.paths repo-relative literal paths that must all exist (no globbing)",
19
+ " workingDirectory repo-relative directory the command runs in",
20
+ " workingDirectoryFrom globs identifying the project file this gate builds; the command runs",
21
+ " in that file's directory, and a repository holding none makes the gate a",
22
+ " NO-OP. Globs are tried in order, so the list states a preference. Ignored",
23
+ " when workingDirectory is set.",
24
+ " command argv array \u2014 executed directly, never through a shell (no eval)",
25
+ "",
26
+ "No gate names a product's solution, package or source root \u2014 a default that does would",
27
+ "silently no-op in every repository except the one it was written for. The .NET and frontend",
28
+ "gates discover the repository's own solution/package instead, so they activate unchanged in",
29
+ "an application repository, a framework repository, and (as nothing to run) in a corpus-only",
30
+ "repository such as this one, which carries no .NET or Node project at all.",
31
+ "",
32
+ "The override model, in order of increasing force: a repository sets workingDirectory to pin",
33
+ "one of several candidate projects; or it drops its own quality-gates.json in place of this",
34
+ "file; or it points CRATIS_HOOKS_GATES at a file anywhere. None of them requires a script fork.",
35
+ "",
36
+ "Commands are pinned to .cratis/ai/rules/general.md 'Quality Gates' and .cratis/ai/hooks/agent-stop.md.",
37
+ "Note the Release build passes -p:CratisProxiesOutputPath= per general.md so the proxy",
38
+ "generator does not re-run and touch already-correct generated files. That property is the",
39
+ "only gate: the generator's target is Condition=\"'$(CratisProxiesOutputPath)' != ''\", and",
40
+ "there is no DisableProxyGenerator property \u2014 MSBuild accepts unknown -p: names silently,",
41
+ "so passing one looks effective and does nothing. The Debug gate deliberately omits the",
42
+ "override because general.md makes Debug the canonical proxy-regeneration trigger. A",
43
+ "consuming repository's own CI should split Debug and Release the same way."
44
+ ],
45
+ "enabled": true,
46
+ "failFast": true,
47
+ "maxOutputLines": 60,
48
+ "gates": [
49
+ {
50
+ "id": "backend-build-debug",
51
+ "description": "dotnet build (Debug) \u2014 compiles #if DEBUG spec code and regenerates the TypeScript proxies",
52
+ "changed": [
53
+ "**/*.cs",
54
+ "**/*.csproj",
55
+ "**/*.slnx",
56
+ "**/*.sln",
57
+ "**/*.props",
58
+ "**/*.targets"
59
+ ],
60
+ "excludeChanged": [
61
+ "**/obj/**",
62
+ "**/bin/**"
63
+ ],
64
+ "requires": {
65
+ "commands": [
66
+ "dotnet"
67
+ ]
68
+ },
69
+ "workingDirectoryFrom": [
70
+ "*.slnx",
71
+ "*.sln",
72
+ "**/*.slnx",
73
+ "**/*.sln"
74
+ ],
75
+ "command": [
76
+ "dotnet",
77
+ "build",
78
+ "--configuration",
79
+ "Debug"
80
+ ]
81
+ },
82
+ {
83
+ "id": "backend-specs",
84
+ "description": "dotnet test (Debug, --no-build) \u2014 zero failures",
85
+ "changed": [
86
+ "**/*.cs",
87
+ "**/*.csproj",
88
+ "**/*.slnx",
89
+ "**/*.sln"
90
+ ],
91
+ "excludeChanged": [
92
+ "**/obj/**",
93
+ "**/bin/**"
94
+ ],
95
+ "requires": {
96
+ "commands": [
97
+ "dotnet"
98
+ ]
99
+ },
100
+ "workingDirectoryFrom": [
101
+ "*.slnx",
102
+ "*.sln",
103
+ "**/*.slnx",
104
+ "**/*.sln"
105
+ ],
106
+ "command": [
107
+ "dotnet",
108
+ "test",
109
+ "--configuration",
110
+ "Debug",
111
+ "--no-build"
112
+ ]
113
+ },
114
+ {
115
+ "id": "backend-build-release",
116
+ "description": "dotnet build (Release) \u2014 build-only check, proxy generation skipped (general.md)",
117
+ "changed": [
118
+ "**/*.cs",
119
+ "**/*.csproj",
120
+ "**/*.slnx",
121
+ "**/*.sln",
122
+ "**/*.props",
123
+ "**/*.targets"
124
+ ],
125
+ "excludeChanged": [
126
+ "**/obj/**",
127
+ "**/bin/**"
128
+ ],
129
+ "requires": {
130
+ "commands": [
131
+ "dotnet"
132
+ ]
133
+ },
134
+ "workingDirectoryFrom": [
135
+ "*.slnx",
136
+ "*.sln",
137
+ "**/*.slnx",
138
+ "**/*.sln"
139
+ ],
140
+ "command": [
141
+ "dotnet",
142
+ "build",
143
+ "--configuration",
144
+ "Release",
145
+ "-p:CratisProxiesOutputPath="
146
+ ]
147
+ },
148
+ {
149
+ "id": "frontend-lint",
150
+ "description": "yarn lint:ci \u2014 zero errors",
151
+ "changed": [
152
+ "**/*.ts",
153
+ "**/*.tsx"
154
+ ],
155
+ "excludeChanged": [
156
+ "**/node_modules/**",
157
+ "**/wwwroot/**",
158
+ "**/dist/**",
159
+ "**/obj/**",
160
+ "**/bin/**"
161
+ ],
162
+ "requires": {
163
+ "commands": [
164
+ "yarn"
165
+ ]
166
+ },
167
+ "workingDirectoryFrom": [
168
+ "package.json",
169
+ "**/package.json"
170
+ ],
171
+ "command": [
172
+ "yarn",
173
+ "lint:ci"
174
+ ]
175
+ },
176
+ {
177
+ "id": "frontend-compile",
178
+ "description": "yarn g:compile \u2014 zero TypeScript errors",
179
+ "changed": [
180
+ "**/*.ts",
181
+ "**/*.tsx"
182
+ ],
183
+ "excludeChanged": [
184
+ "**/node_modules/**",
185
+ "**/wwwroot/**",
186
+ "**/dist/**",
187
+ "**/obj/**",
188
+ "**/bin/**"
189
+ ],
190
+ "requires": {
191
+ "commands": [
192
+ "yarn"
193
+ ]
194
+ },
195
+ "workingDirectoryFrom": [
196
+ "package.json",
197
+ "**/package.json"
198
+ ],
199
+ "command": [
200
+ "yarn",
201
+ "g:compile"
202
+ ]
203
+ },
204
+ {
205
+ "id": "frontend-compile-specs",
206
+ "description": "yarn g:compile:specs \u2014 zero TypeScript errors in specs",
207
+ "changed": [
208
+ "**/*.ts",
209
+ "**/*.tsx"
210
+ ],
211
+ "excludeChanged": [
212
+ "**/node_modules/**",
213
+ "**/wwwroot/**",
214
+ "**/dist/**",
215
+ "**/obj/**",
216
+ "**/bin/**"
217
+ ],
218
+ "requires": {
219
+ "commands": [
220
+ "yarn"
221
+ ]
222
+ },
223
+ "workingDirectoryFrom": [
224
+ "package.json",
225
+ "**/package.json"
226
+ ],
227
+ "command": [
228
+ "yarn",
229
+ "g:compile:specs"
230
+ ]
231
+ },
232
+ {
233
+ "id": "frontend-specs",
234
+ "description": "yarn test \u2014 zero failures",
235
+ "changed": [
236
+ "**/*.ts",
237
+ "**/*.tsx"
238
+ ],
239
+ "excludeChanged": [
240
+ "**/node_modules/**",
241
+ "**/wwwroot/**",
242
+ "**/dist/**",
243
+ "**/obj/**",
244
+ "**/bin/**"
245
+ ],
246
+ "requires": {
247
+ "commands": [
248
+ "yarn"
249
+ ]
250
+ },
251
+ "workingDirectoryFrom": [
252
+ "package.json",
253
+ "**/package.json"
254
+ ],
255
+ "command": [
256
+ "yarn",
257
+ "test"
258
+ ]
259
+ }
260
+ ]
261
+ }
@@ -0,0 +1,71 @@
1
+ # Names validate-type-references.sh must never warn about.
2
+ #
3
+ # The Tier 3 index is built from the XML documentation of the Cratis NuGet packages the consuming
4
+ # repository pins, and from what the corpus and that repository's own `Source/**/*.cs` declare for
5
+ # themselves. (`Cratis/AI` itself is a corpus with no C# source, so only the corpus half
6
+ # contributes here and the package half is skipped in silence.) Everything below is a
7
+ # name that is genuinely real and genuinely outside that index — a .NET attribute from an ecosystem
8
+ # whose package ships no XML docs, a Chronicle *Kernel* internal that is in no client package, or a
9
+ # TypeScript API in a repository whose index only covers .NET assemblies.
10
+ #
11
+ # One name per line, `#` starts a comment, blank lines ignored. Every entry states *why* it is here:
12
+ # an entry nobody can justify is an entry that should have been a fix instead. An entry is wrong the
13
+ # moment its name becomes resolvable — the guard silently stops checking it — so prefer widening the
14
+ # index over adding a line here whenever that is possible.
15
+ #
16
+ # Add the bare name, not the `Attribute` suffix; the guard resolves both spellings.
17
+
18
+ # --- ASP.NET Core, from the Microsoft.AspNetCore.App shared framework ------------------------------
19
+ # The shared framework is a ref pack, and ref packs carry no XML documentation, so no amount of
20
+ # indexing NuGet reaches these. The corpus names them to say what NOT to write (`[Route]` on a
21
+ # model-bound query) or in non-Cratis illustrations.
22
+ FromServices
23
+ HttpGet
24
+ HttpPost
25
+ FromBody
26
+
27
+ # --- System.ComponentModel.DataAnnotations, from the base class library ---------------------------
28
+ # Same reason: BCL, ref pack, no XML docs. Cited when contrasting DataAnnotations validation with
29
+ # Arc's `CommandValidator<T>` / `ConceptValidator<T>`.
30
+ Required
31
+ Range
32
+ MaxLength
33
+
34
+ # --- Microsoft.Extensions.* -----------------------------------------------------------------------
35
+ # `[LoggerMessage]` is the logging source generator's attribute; `[FromKeyedServices]` is keyed DI.
36
+ # Both are real; the packages that carry them are transitive here, so their XML docs are not indexed.
37
+ LoggerMessage
38
+ FromKeyedServices
39
+
40
+ # --- Microsoft Orleans ----------------------------------------------------------------------------
41
+ # `orleans.md` is framework-profile guidance for the Chronicle Kernel's grains. The pinned Orleans
42
+ # packages ship no XML documentation, so `[Alias]` and `[StorageProvider]` cannot be resolved.
43
+ Alias
44
+ StorageProvider
45
+
46
+ # --- Cratis Chronicle Kernel ----------------------------------------------------------------------
47
+ # `WellKnown` is a Kernel static class (`WellKnown.MeterName`), used by the framework-profile
48
+ # add-traces skill. Kernel internals are not published in any client package, so nothing this guard
49
+ # can read knows about them.
50
+ WellKnown
51
+
52
+ # --- xUnit -----------------------------------------------------------------------------------------
53
+ # `[Fact]` is xUnit's test attribute. The pinned Cratis packages never reference the test framework,
54
+ # so its attributes cannot appear in this guard's index no matter how wide the pin list grows.
55
+ Fact
56
+
57
+ # --- The `cratis` CLI -----------------------------------------------------------------------------
58
+ # `[CliCommand]` and `[CliExample]` are `Cratis.Cli.Registration` attributes — verified present as
59
+ # `T:Cratis.Cli.Registration.CliCommandAttribute` / `...CliExampleAttribute` in the Cratis/cli build
60
+ # output. The CLI is a dotnet tool from a separate repository, not a package any application pins,
61
+ # so no Directory.Packages.props entry can ever bring it into the index.
62
+ CliCommand
63
+ CliExample
64
+
65
+ # --- Cratis TypeScript ----------------------------------------------------------------------------
66
+ # `JsonSerializer` is `@cratis/fundamentals`' TypeScript serializer, referenced from the frontend
67
+ # identity guidance. `ObservableQuery` is the TypeScript class the Arc proxy generator emits for
68
+ # `ISubject<T>` query return types. This guard indexes .NET assemblies only; named TypeScript
69
+ # imports are Tier 2's question, and a bare TypeScript class name is nobody's.
70
+ JsonSerializer
71
+ ObservableQuery
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # Tier 2 of the package drift guard. Where validate-package-subpaths.sh asks whether a module
3
+ # specifier resolves, this asks whether the *names* imported through it exist: for every
4
+ # `import { A, B } from '@cratis/<pkg>/<subpath>'` the AI corpus writes, it WARNS about each
5
+ # identifier that appears nowhere in the installed package's `.d.ts` tree.
6
+ #
7
+ # It exists because a subpath that resolves says nothing about what is behind it. `Toaster`,
8
+ # `toastCommandResult`, `PasswordField`, `RatingField` and friends are real APIs of
9
+ # @cratis/components 3.0.0 and absent from 2.6.1; Tier 1 caught the three *subpaths* that moved with
10
+ # them, and the names themselves were only ever found by a human reading package internals.
11
+ #
12
+ # WARN, never fail, and silent when it cannot judge — for the identical reasons spelled out at the
13
+ # top of validate-package-subpaths.sh. A miss is exact; the conclusion drawn from it is not.
14
+ #
15
+ # Deliberately permissive, because a false warning is worse than a missed one. A name counts as
16
+ # present when it appears as a *word anywhere* in the package's `.d.ts` closure — not only in an
17
+ # export position — and the closure follows `export ... from '<other-package>'` re-exports one level
18
+ # out. That admits a name that is merely referenced by the types (an imported PrimeReact symbol, a
19
+ # name in a doc comment) and it still flags every one of the twelve 3.0.0 names above.
20
+ #
21
+ # Portable: bash 3.2 + grep + sed + awk, with `jq` as the one accepted dependency (absent -> silent
22
+ # no-op, per the hook design constraints in ../README.md).
23
+ #
24
+ # Usage: validate-package-imports.sh [root ...] # default roots: .cratis/ai/rules .cratis/ai/skills .cratis/ai/agents .cratis/ai/prompts
25
+ # CRATIS_HOOKS_IMPORT_REPORT=1 ... # also print every binding and its resolved status
26
+ set -euo pipefail
27
+
28
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
29
+ cd "$root"
30
+
31
+ warn() { printf 'ai-corpus warn: %s\n' "$1" >&2; }
32
+ # `if`, not `A && B || C`: the trailing `|| true` was there to keep a disabled report
33
+ # from failing the caller, but it also swallowed a real printf failure, and shellcheck
34
+ # flags the shape (SC2015) for exactly that reason.
35
+ report() {
36
+ if [[ "${CRATIS_HOOKS_IMPORT_REPORT:-0}" == "1" ]]; then printf 'ai-corpus import: %s\n' "$1" >&2; fi
37
+ }
38
+
39
+ command -v jq >/dev/null 2>&1 || exit 0
40
+ [[ -d node_modules/@cratis ]] || exit 0
41
+
42
+ # `.cratis/ai/hooks` is deliberately not a default root: this file and ../README.md name deliberately-bogus
43
+ # identifiers as examples, and a guard that reports its own documentation is a guard people switch off.
44
+ if [[ $# -gt 0 ]]; then roots=("$@"); else roots=(.cratis/ai/rules .cratis/ai/skills .cratis/ai/agents .cratis/ai/prompts); fi
45
+ scan=()
46
+ for d in "${roots[@]}"; do [[ -d "$d" ]] && scan+=("$d"); done
47
+ [[ "${#scan[@]}" -gt 0 ]] || exit 0
48
+
49
+ # Same clearing rule, and the same generosity, as Tier 1: a line that carries a version alongside the
50
+ # name has declared the skew on purpose (`(**≥ 3.0.0**)`), so it is not drift.
51
+ version_re='[0-9]+\.[0-9x]+|≥|>='
52
+
53
+ q="'"
54
+
55
+ # One awk pass over the corpus emits `file <TAB> line <TAB> package <TAB> specifier <TAB> name` for
56
+ # every named binding of an `@cratis/*` import — single-line and brace-on-its-own-line forms alike,
57
+ # `import type`, `A as B` (the *imported* name is what has to exist), and trailing `//` comments.
58
+ # Anything that does not lex as a plain identifier is dropped rather than guessed at. The quote
59
+ # character arrives as `-v q` so the program itself never has to contain one.
60
+ extract="$(cat <<'AWK'
61
+ FNR == 1 { n = 0; buf = "" }
62
+ {
63
+ # A blank line, or a second `import`, ends an unterminated buffer rather than being glued onto
64
+ # it. Without that, a stray `import {` in prose swallows the real statement below it and the
65
+ # names get read off one block while the specifier is read off another — which invents an
66
+ # identifier that resolves nowhere, i.e. exactly the false positive this guard must not produce.
67
+ if (n > 0 && ($0 ~ /^[[:space:]]*$/ || $0 ~ /^[[:space:]]*import[[:space:]]/)) { n = 0; buf = "" }
68
+ if (n == 0) {
69
+ if ($0 !~ /^[[:space:]]*import[[:space:]]/) next
70
+ if (index($0, "{") == 0) next
71
+ start = FNR; buf = ""
72
+ }
73
+ line = $0
74
+ sub(/\/\/.*$/, "", line)
75
+ buf = buf " " line
76
+ n++
77
+ if (line ~ ("from[[:space:]]*[" q "\"]") || n > 40) { emit(); n = 0; buf = "" }
78
+ }
79
+ function emit( spec, pkg, names, parts, count, i, name) {
80
+ if (match(buf, "from[[:space:]]*[" q "\"][^" q "\"]+[" q "\"]") == 0) return
81
+ spec = substr(buf, RSTART, RLENGTH)
82
+ sub("^from[[:space:]]*[" q "\"]", "", spec)
83
+ sub("[" q "\"]$", "", spec)
84
+ if (spec !~ /^@cratis\//) return
85
+ pkg = substr(spec, 9)
86
+ sub(/\/.*$/, "", pkg)
87
+ if (match(buf, /\{[^}]*\}/) == 0) return
88
+ names = substr(buf, RSTART + 1, RLENGTH - 2)
89
+ count = split(names, parts, ",")
90
+ for (i = 1; i <= count; i++) {
91
+ name = parts[i]
92
+ sub(/^[[:space:]]+/, "", name); sub(/[[:space:]]+$/, "", name)
93
+ sub(/^type[[:space:]]+/, "", name)
94
+ sub(/[[:space:]]+as[[:space:]].*$/, "", name)
95
+ sub(/[[:space:]]+$/, "", name)
96
+ if (name !~ /^[A-Za-z_$][A-Za-z0-9_$]*$/) continue
97
+ printf "%s\t%d\t%s\t%s\t%s\n", FILENAME, start, pkg, spec, name
98
+ }
99
+ }
100
+ AWK
101
+ )"
102
+
103
+ files=()
104
+ while IFS= read -r f; do files+=("$f"); done < <(find "${scan[@]}" -type f 2>/dev/null | LC_ALL=C sort)
105
+ [[ "${#files[@]}" -gt 0 ]] || exit 0
106
+
107
+ bindings="$(awk -v q="$q" "$extract" "${files[@]}" 2>/dev/null || true)"
108
+ [[ -n "$bindings" ]] || exit 0
109
+
110
+ # Every `.d.ts` reachable from a package: its own tree, plus one level out through re-exports to
111
+ # another installed package (`export { Messenger } from '@cratis/arc/messaging'`). Intra-package
112
+ # barrels (`export * from './X'`) need no following — the whole tree is read either way.
113
+ closure_dirs() {
114
+ local pkgdir="node_modules/@cratis/$1" dep
115
+ printf '%s\n' "$pkgdir"
116
+ grep -rhE "^[[:space:]]*export[[:space:]][^;]*[[:space:]]from[[:space:]]*[\"$q]" "$pkgdir" --include='*.d.ts' 2>/dev/null \
117
+ | sed -E "s/.*[\"$q]([^\"$q]*)[\"$q].*/\1/" \
118
+ | grep -v '^\.' \
119
+ | sed -E 's#^(@[^/]+/[^/]+|[^@/][^/]*).*#\1#' \
120
+ | LC_ALL=C sort -u \
121
+ | while IFS= read -r dep; do
122
+ [[ -n "$dep" && -d "node_modules/$dep" && "node_modules/$dep" != "$pkgdir" ]] && printf 'node_modules/%s\n' "$dep"
123
+ done
124
+ }
125
+
126
+ # Sorted by package so each closure is read exactly once.
127
+ printf '%s\n' "$bindings" | cut -f3,5 | LC_ALL=C sort -u | while IFS="$(printf '\t')" read -r pkg name; do
128
+ [[ -n "$pkg" && -n "$name" ]] || continue
129
+
130
+ if [[ "${current:-}" != "$pkg" ]]; then
131
+ current="$pkg"; tokens=""; version="?"
132
+ if [[ -f "node_modules/@cratis/$pkg/package.json" ]]; then
133
+ version="$(jq -r '.version // "?"' "node_modules/@cratis/$pkg/package.json" 2>/dev/null || printf '?')"
134
+ dirs=()
135
+ while IFS= read -r d; do [[ -n "$d" ]] && dirs+=("$d"); done < <(closure_dirs "$pkg")
136
+ if [[ "${#dirs[@]}" -gt 0 ]]; then
137
+ tokens="$(grep -rhoE '[A-Za-z_$][A-Za-z0-9_$]*' "${dirs[@]}" --include='*.d.ts' 2>/dev/null | LC_ALL=C sort -u || true)"
138
+ fi
139
+ fi
140
+ fi
141
+
142
+ # No package, or a package that ships no type declarations: nothing authoritative to compare
143
+ # against, so it is not a finding.
144
+ if [[ -z "$tokens" ]]; then report "$name — skipped (@cratis/$pkg has no installed type declarations)"; continue; fi
145
+ # A here-string, never `printf … | grep -q`: under `pipefail` an early-exiting `grep -q` closes
146
+ # the pipe, `printf` dies of SIGPIPE with 141, and the pipeline reports failure even though the
147
+ # name matched. On a token list this size that is not a rare race — it is every time, and it
148
+ # manufactures false positives, which is the one thing this guard must not do.
149
+ if LC_ALL=C grep -qxF -- "$name" <<<"$tokens"; then report "$name — yes (@cratis/$pkg $version)"; continue; fi
150
+
151
+ # Qualification is judged per (file, name) exactly as in Tier 1: the corpus states a version
152
+ # requirement once in prose and then writes the import unqualified in a fenced block below it,
153
+ # so any line in the file that mentions the name and carries a version clears the file.
154
+ # One warning per file — the first binding — so a page that repeats an example is not a flood.
155
+ printf '%s\n' "$bindings" | awk -F'\t' -v p="$pkg" -v n="$name" \
156
+ '$3 == p && $5 == n { print $1 "\t" $2 "\t" $4 }' \
157
+ | LC_ALL=C sort -t"$(printf '\t')" -k1,1 -k2,2n -u | awk -F'\t' '!seen[$1]++' \
158
+ | while IFS="$(printf '\t')" read -r file line spec; do
159
+ hits="$(grep -nwF -- "$name" "$file" 2>/dev/null || true)"
160
+ if [[ -n "$hits" ]] && grep -qE "$version_re" <<<"$hits"; then
161
+ report "$name — missing from @cratis/$pkg $version but version-qualified in $file"
162
+ continue
163
+ fi
164
+ warn "$file:$line: '$name' imported from '$spec' is not declared anywhere in the installed @cratis/$pkg $version — fix the name, or mark the line with the version it needs (e.g. '(≥ 3.0.0)')"
165
+ done
166
+ done
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env bash
2
+ # Resolves every `@cratis/<package>/<subpath>` the AI corpus references against the `exports` map of
3
+ # the package actually installed under node_modules, and WARNS about each one that cannot resolve.
4
+ #
5
+ # WARN, never fail — deliberately. The exports map is exact, but the *conclusion* drawn from a miss
6
+ # is not: "the corpus documents an API that does not exist" and "this repository is pinned behind the
7
+ # version the corpus documents" produce the identical observation. This script is propagated to every
8
+ # Cratis repository and runs in the `ai-corpus` CI job, which checks out the tree and installs
9
+ # nothing — so a fatal verdict would either be a permanent no-op there or turn a repo red for its own
10
+ # dependency pin. A warning is the honest signal: look at this line, decide which of the two it is.
11
+ #
12
+ # Not installed is not a finding. A missing node_modules, a missing @cratis scope, a package this
13
+ # repository does not depend on, and a package published without an `exports` map are all skipped
14
+ # silently — there is nothing authoritative to compare against.
15
+ #
16
+ # Portable: bash 3.2 + grep + sed, with `jq` as the one accepted dependency (absent -> silent no-op,
17
+ # per the hook design constraints in ../README.md).
18
+ #
19
+ # Usage: validate-package-subpaths.sh [root ...] # default roots: .cratis/ai/rules .cratis/ai/skills .cratis/ai/agents .cratis/ai/prompts
20
+ # CRATIS_HOOKS_SUBPATH_REPORT=1 ... # also print every reference and its resolved status
21
+ set -euo pipefail
22
+
23
+ root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
24
+ cd "$root"
25
+
26
+ warn() { printf 'ai-corpus warn: %s\n' "$1" >&2; }
27
+ # `if`, not `A && B || C`: the trailing `|| true` was there to keep a disabled report
28
+ # from failing the caller, but it also swallowed a real printf failure, and shellcheck
29
+ # flags the shape (SC2015) for exactly that reason.
30
+ report() {
31
+ if [[ "${CRATIS_HOOKS_SUBPATH_REPORT:-0}" == "1" ]]; then printf 'ai-corpus subpath: %s\n' "$1" >&2; fi
32
+ }
33
+
34
+ # Tier 3 over the same roots: whether the .NET types the corpus names in prose and in C# positions
35
+ # exist at all. Invoked exactly like Tier 2 at the bottom of this file — tested with -f, not -x, and
36
+ # run through `bash`, so a checkout that lost the exec bit does not silently drop the guard — but
37
+ # necessarily *above* the two gates below, because it reads a NuGet cache rather than node_modules
38
+ # and must still run in a repository that has no frontend and no `jq`.
39
+ types="$(dirname "${BASH_SOURCE[0]}")/validate-type-references.sh"
40
+ if [[ -f "$types" ]]; then bash "$types" "$@" || true; fi
41
+
42
+ command -v jq >/dev/null 2>&1 || exit 0
43
+ [[ -d node_modules/@cratis ]] || exit 0
44
+
45
+ # `.cratis/ai/hooks` is deliberately not a default root: this file and ../README.md name deliberately-bogus
46
+ # subpaths as examples, and a guard that reports its own documentation is a guard people switch off.
47
+ if [[ $# -gt 0 ]]; then roots=("$@"); else roots=(.cratis/ai/rules .cratis/ai/skills .cratis/ai/agents .cratis/ai/prompts); fi
48
+ scan=()
49
+ for d in "${roots[@]}"; do [[ -d "$d" ]] && scan+=("$d"); done
50
+ [[ "${#scan[@]}" -gt 0 ]] || exit 0
51
+
52
+ # A line that carries a version alongside the reference has already declared the skew on purpose
53
+ # (`(**≥ 3.0.0**)`), so it is not drift. Kept deliberately generous — a dotted version, an `N.x`, or
54
+ # either inequality spelling qualifies — because the cost of a missed warning is one stale line while
55
+ # the cost of a false one is noise on exactly the lines someone just fixed correctly.
56
+ version_re='[0-9]+\.[0-9x]+|≥|>='
57
+
58
+ # `node_modules/@cratis/...` in the corpus is a filesystem path (a "look in the .d.ts" pointer), not a
59
+ # module specifier. Capture the prefix so it can be dropped rather than mis-parsed as a subpath.
60
+ refs="$(grep -rhoE '(node_modules/)?@cratis/[A-Za-z0-9._-]+(/[A-Za-z0-9._-]+)+' "${scan[@]}" 2>/dev/null \
61
+ | grep -v '^node_modules/' | sed -E 's/[.-]+$//' | LC_ALL=C sort -u || true)"
62
+ [[ -n "$refs" ]] || exit 0
63
+
64
+ # Prints yes | no | unknown for "./<subpath>" against a package.json's exports map. `unknown` covers
65
+ # every shape that carries no authoritative subpath list; only `no` is ever reported.
66
+ resolves() {
67
+ jq -r --arg s "./$2" '
68
+ def matches($key): if ($key | contains("*"))
69
+ then ($key | split("*")) as $p
70
+ | if ($p | length) == 2 then ($s | startswith($p[0])) and ($s | endswith($p[1])) else true end
71
+ else $key == $s end;
72
+ (.exports // null) as $e
73
+ | if $e == null then "unknown"
74
+ elif ($e | type) == "string" then "no"
75
+ elif ($e | type) != "object" then "unknown"
76
+ else ($e | keys) as $k
77
+ | if ([$k[] | startswith(".")] | any) == false then "no"
78
+ elif ([$k[] | select(matches(.))] | length) > 0 then "yes"
79
+ else "no" end
80
+ end' "$1" 2>/dev/null || printf 'unknown'
81
+ }
82
+
83
+ printf '%s\n' "$refs" | while IFS= read -r ref; do
84
+ [[ -n "$ref" ]] || continue
85
+ rest="${ref#@cratis/}"; pkg="${rest%%/*}"; sub="${rest#*/}"
86
+ manifest="node_modules/@cratis/$pkg/package.json"
87
+ if [[ ! -f "$manifest" ]]; then report "$ref — skipped (@cratis/$pkg not installed)"; continue; fi
88
+ status="$(resolves "$manifest" "$sub")"
89
+ version="$(jq -r '.version // "?"' "$manifest" 2>/dev/null || printf '?')"
90
+ if [[ "$status" != "no" ]]; then report "$ref — $status (@cratis/$pkg $version)"; continue; fi
91
+
92
+ # Bounded so `.../CommandForm` does not answer for `.../CommandForm/fields`. Qualification is
93
+ # judged per (file, reference): the corpus states a subpath's version requirement once and may
94
+ # then mention it again unqualified in the same file, so any qualified line clears the file.
95
+ bounded="($(printf '%s' "$ref" | sed 's/\./\\./g'))([^A-Za-z0-9._/-]|\$)"
96
+ { grep -rlE "$bounded" "${scan[@]}" 2>/dev/null || true; } | LC_ALL=C sort | while IFS= read -r file; do
97
+ # `grep -n` prefixes each hit with `<line>:`, which carries no dot and so cannot itself look
98
+ # like a version. Lines where the reference is part of a node_modules path are dropped again
99
+ # here — the extraction pass already ignores them, and they are not module specifiers.
100
+ hits="$(grep -nE "$bounded" "$file" 2>/dev/null | grep -vF "node_modules/$ref" || true)"
101
+ [[ -n "$hits" ]] || continue
102
+ # A here-string, never `printf … | grep -q`: an early-exiting `grep -q` closes the pipe,
103
+ # `printf` dies of SIGPIPE with 141, and under `pipefail` the pipeline reports failure even
104
+ # though the version matched — turning a correctly-qualified line into a warning.
105
+ if grep -qE "$version_re" <<<"$hits"; then
106
+ report "$ref — missing from @cratis/$pkg $version but version-qualified in $file"
107
+ continue
108
+ fi
109
+ line="$(printf '%s\n' "$hits" | head -1 | cut -d: -f1)"
110
+ warn "$file:$line: '$ref' is not in the exports map of the installed @cratis/$pkg $version — fix the reference, or mark the line with the version it needs (e.g. '(≥ 3.0.0)')"
111
+ done
112
+ done
113
+
114
+ # Tier 2 over the same roots: a subpath that resolves says nothing about the *names* imported
115
+ # through it. Kept in its own script — a different question, a different corpus extraction and a
116
+ # different report variable — and invoked from here so the single call site in validate-ai-setup.sh
117
+ # gets both. Tested with -f, not -x, and run through `bash`: a checkout that lost the exec bit must
118
+ # not silently drop the guard.
119
+ imports="$(dirname "${BASH_SOURCE[0]}")/validate-package-imports.sh"
120
+ if [[ -f "$imports" ]]; then bash "$imports" "$@" || true; fi