ddd-ts-kit 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.fr.md +10 -12
  2. package/README.md +9 -10
  3. package/dist/affected/reach-of.js +1 -1
  4. package/dist/affected/reach-of.js.map +1 -1
  5. package/dist/check/catalogue/architecture-rules.d.ts.map +1 -1
  6. package/dist/check/catalogue/architecture-rules.js +2 -0
  7. package/dist/check/catalogue/architecture-rules.js.map +1 -1
  8. package/dist/check/declarations/declared-event-handlers.d.ts +4 -9
  9. package/dist/check/declarations/declared-event-handlers.d.ts.map +1 -1
  10. package/dist/check/declarations/declared-event-handlers.js +5 -5
  11. package/dist/check/declarations/declared-event-handlers.js.map +1 -1
  12. package/dist/check/declarations/option-of.d.ts +0 -2
  13. package/dist/check/declarations/option-of.d.ts.map +1 -1
  14. package/dist/check/declarations/option-of.js +1 -7
  15. package/dist/check/declarations/option-of.js.map +1 -1
  16. package/dist/check/explain/authz-docs.d.ts.map +1 -1
  17. package/dist/check/explain/authz-docs.js +14 -1
  18. package/dist/check/explain/authz-docs.js.map +1 -1
  19. package/dist/check/explain/events-docs.d.ts.map +1 -1
  20. package/dist/check/explain/events-docs.js +31 -11
  21. package/dist/check/explain/events-docs.js.map +1 -1
  22. package/dist/check/explain/ports-docs.d.ts.map +1 -1
  23. package/dist/check/explain/ports-docs.js +11 -9
  24. package/dist/check/explain/ports-docs.js.map +1 -1
  25. package/dist/check/explain/scheduling-docs.d.ts.map +1 -1
  26. package/dist/check/explain/scheduling-docs.js +10 -5
  27. package/dist/check/explain/scheduling-docs.js.map +1 -1
  28. package/dist/check/explain/wiring-docs.js +4 -4
  29. package/dist/check/explain/wiring-docs.js.map +1 -1
  30. package/dist/check/fixtures/authz-policy-fixtures.d.ts +3 -0
  31. package/dist/check/fixtures/authz-policy-fixtures.d.ts.map +1 -0
  32. package/dist/check/fixtures/authz-policy-fixtures.js +97 -0
  33. package/dist/check/fixtures/authz-policy-fixtures.js.map +1 -0
  34. package/dist/check/fixtures/declarations-fixtures.d.ts.map +1 -1
  35. package/dist/check/fixtures/declarations-fixtures.js +2 -42
  36. package/dist/check/fixtures/declarations-fixtures.js.map +1 -1
  37. package/dist/check/fixtures/declared.d.ts.map +1 -1
  38. package/dist/check/fixtures/declared.js +3 -3
  39. package/dist/check/fixtures/declared.js.map +1 -1
  40. package/dist/check/fixtures/domain-events-fixtures.js +1 -1
  41. package/dist/check/fixtures/domain-events-fixtures.js.map +1 -1
  42. package/dist/check/fixtures/fixtures.d.ts.map +1 -1
  43. package/dist/check/fixtures/fixtures.js +2 -0
  44. package/dist/check/fixtures/fixtures.js.map +1 -1
  45. package/dist/check/rules/authz-policy-asked.d.ts +3 -0
  46. package/dist/check/rules/authz-policy-asked.d.ts.map +1 -0
  47. package/dist/check/rules/authz-policy-asked.js +184 -0
  48. package/dist/check/rules/authz-policy-asked.js.map +1 -0
  49. package/dist/check/rules/declarations-role-declared.d.ts.map +1 -1
  50. package/dist/check/rules/declarations-role-declared.js +4 -35
  51. package/dist/check/rules/declarations-role-declared.js.map +1 -1
  52. package/dist/check/rules/events-consumer-idempotent.js +3 -3
  53. package/dist/check/rules/events-consumer-idempotent.js.map +1 -1
  54. package/dist/generate/emit.d.ts.map +1 -1
  55. package/dist/generate/emit.js +2 -14
  56. package/dist/generate/emit.js.map +1 -1
  57. package/dist/generate/kinds/ask-policy-in-generated-use-case.d.ts +9 -0
  58. package/dist/generate/kinds/ask-policy-in-generated-use-case.d.ts.map +1 -0
  59. package/dist/generate/kinds/ask-policy-in-generated-use-case.js +102 -0
  60. package/dist/generate/kinds/ask-policy-in-generated-use-case.js.map +1 -0
  61. package/dist/generate/kinds/assert-declares-its-name.d.ts +9 -0
  62. package/dist/generate/kinds/assert-declares-its-name.d.ts.map +1 -0
  63. package/dist/generate/kinds/assert-declares-its-name.js +16 -0
  64. package/dist/generate/kinds/assert-declares-its-name.js.map +1 -0
  65. package/dist/generate/kinds/command-context.d.ts +12 -0
  66. package/dist/generate/kinds/command-context.d.ts.map +1 -0
  67. package/dist/generate/kinds/command-context.js +18 -0
  68. package/dist/generate/kinds/command-context.js.map +1 -0
  69. package/dist/generate/kinds/command-kind.d.ts.map +1 -1
  70. package/dist/generate/kinds/command-kind.js +7 -24
  71. package/dist/generate/kinds/command-kind.js.map +1 -1
  72. package/dist/generate/kinds/command-outputs.d.ts +6 -0
  73. package/dist/generate/kinds/command-outputs.d.ts.map +1 -0
  74. package/dist/generate/kinds/command-outputs.js +13 -0
  75. package/dist/generate/kinds/command-outputs.js.map +1 -0
  76. package/dist/generate/kinds/declares-its-name.d.ts +5 -0
  77. package/dist/generate/kinds/declares-its-name.d.ts.map +1 -0
  78. package/dist/generate/kinds/declares-its-name.js +7 -0
  79. package/dist/generate/kinds/declares-its-name.js.map +1 -0
  80. package/dist/generate/kinds/event-file-of.d.ts +5 -0
  81. package/dist/generate/kinds/event-file-of.d.ts.map +1 -0
  82. package/dist/generate/kinds/event-file-of.js +5 -0
  83. package/dist/generate/kinds/event-file-of.js.map +1 -0
  84. package/dist/generate/kinds/event-handler-kind.d.ts.map +1 -1
  85. package/dist/generate/kinds/event-handler-kind.js +2 -0
  86. package/dist/generate/kinds/event-handler-kind.js.map +1 -1
  87. package/dist/generate/kinds/event-name-reference.d.ts +4 -3
  88. package/dist/generate/kinds/event-name-reference.d.ts.map +1 -1
  89. package/dist/generate/kinds/event-name-reference.js +6 -6
  90. package/dist/generate/kinds/event-name-reference.js.map +1 -1
  91. package/dist/generate/kinds/integration-event-kind.d.ts.map +1 -1
  92. package/dist/generate/kinds/integration-event-kind.js +2 -0
  93. package/dist/generate/kinds/integration-event-kind.js.map +1 -1
  94. package/dist/generate/kinds/policy-kind.d.ts.map +1 -1
  95. package/dist/generate/kinds/policy-kind.js +2 -0
  96. package/dist/generate/kinds/policy-kind.js.map +1 -1
  97. package/dist/generate/kinds/query-context.d.ts +9 -0
  98. package/dist/generate/kinds/query-context.d.ts.map +1 -0
  99. package/dist/generate/kinds/query-context.js +27 -0
  100. package/dist/generate/kinds/query-context.js.map +1 -0
  101. package/dist/generate/kinds/query-kind.d.ts.map +1 -1
  102. package/dist/generate/kinds/query-kind.js +16 -52
  103. package/dist/generate/kinds/query-kind.js.map +1 -1
  104. package/dist/generate/kinds/query-outputs.d.ts +8 -0
  105. package/dist/generate/kinds/query-outputs.d.ts.map +1 -0
  106. package/dist/generate/kinds/query-outputs.js +20 -0
  107. package/dist/generate/kinds/query-outputs.js.map +1 -0
  108. package/dist/generate/kinds/reader-file.d.ts +8 -0
  109. package/dist/generate/kinds/reader-file.d.ts.map +1 -0
  110. package/dist/generate/kinds/reader-file.js +9 -0
  111. package/dist/generate/kinds/reader-file.js.map +1 -0
  112. package/dist/generate/mark-policy-questions.d.ts +10 -0
  113. package/dist/generate/mark-policy-questions.d.ts.map +1 -0
  114. package/dist/generate/mark-policy-questions.js +76 -0
  115. package/dist/generate/mark-policy-questions.js.map +1 -0
  116. package/dist/generate/permission-pattern.d.ts +4 -0
  117. package/dist/generate/permission-pattern.d.ts.map +1 -0
  118. package/dist/generate/permission-pattern.js +7 -0
  119. package/dist/generate/permission-pattern.js.map +1 -0
  120. package/dist/generate/refined-by-policy.d.ts +4 -0
  121. package/dist/generate/refined-by-policy.d.ts.map +1 -0
  122. package/dist/generate/refined-by-policy.js +9 -0
  123. package/dist/generate/refined-by-policy.js.map +1 -0
  124. package/dist/generate/rendered.d.ts +5 -0
  125. package/dist/generate/rendered.d.ts.map +1 -0
  126. package/dist/generate/rendered.js +18 -0
  127. package/dist/generate/rendered.js.map +1 -0
  128. package/dist/generate/wire.d.ts.map +1 -1
  129. package/dist/generate/wire.js +3 -25
  130. package/dist/generate/wire.js.map +1 -1
  131. package/dist/generate/wirings.d.ts +8 -0
  132. package/dist/generate/wirings.d.ts.map +1 -0
  133. package/dist/generate/wirings.js +28 -0
  134. package/dist/generate/wirings.js.map +1 -0
  135. package/dist/init/copilot-instruction.d.ts +6 -0
  136. package/dist/init/copilot-instruction.d.ts.map +1 -0
  137. package/dist/init/copilot-instruction.js +15 -0
  138. package/dist/init/copilot-instruction.js.map +1 -0
  139. package/dist/init/files/guidance-files.d.ts +1 -0
  140. package/dist/init/files/guidance-files.d.ts.map +1 -1
  141. package/dist/init/files/guidance-files.js +4 -3
  142. package/dist/init/files/guidance-files.js.map +1 -1
  143. package/dist/init/files/layer-rules.d.ts +3 -0
  144. package/dist/init/files/layer-rules.d.ts.map +1 -0
  145. package/dist/init/files/layer-rules.js +11 -0
  146. package/dist/init/files/layer-rules.js.map +1 -0
  147. package/dist/init/files/pipeline-files.d.ts.map +1 -1
  148. package/dist/init/files/pipeline-files.js +2 -0
  149. package/dist/init/files/pipeline-files.js.map +1 -1
  150. package/dist/init/json-object.d.ts +3 -0
  151. package/dist/init/json-object.d.ts.map +1 -0
  152. package/dist/init/json-object.js +12 -0
  153. package/dist/init/json-object.js.map +1 -0
  154. package/dist/init/manifest/versions.js +24 -24
  155. package/dist/init/run-init.d.ts.map +1 -1
  156. package/dist/init/run-init.js +4 -0
  157. package/dist/init/run-init.js.map +1 -1
  158. package/dist/init/write-agent-settings.d.ts.map +1 -1
  159. package/dist/init/write-agent-settings.js +3 -10
  160. package/dist/init/write-agent-settings.js.map +1 -1
  161. package/dist/init/write-copilot-instructions.d.ts +7 -0
  162. package/dist/init/write-copilot-instructions.d.ts.map +1 -0
  163. package/dist/init/write-copilot-instructions.js +16 -0
  164. package/dist/init/write-copilot-instructions.js.map +1 -0
  165. package/dist/init/write-copilot-settings.d.ts +8 -0
  166. package/dist/init/write-copilot-settings.d.ts.map +1 -0
  167. package/dist/init/write-copilot-settings.js +36 -0
  168. package/dist/init/write-copilot-settings.js.map +1 -0
  169. package/dist/markers/index.d.ts +1 -0
  170. package/dist/markers/index.d.ts.map +1 -1
  171. package/dist/markers/index.js +1 -0
  172. package/dist/markers/index.js.map +1 -1
  173. package/dist/markers/replace-in-region.d.ts +10 -0
  174. package/dist/markers/replace-in-region.d.ts.map +1 -0
  175. package/dist/markers/replace-in-region.js +30 -0
  176. package/dist/markers/replace-in-region.js.map +1 -0
  177. package/dist/sdk/agent-instruction.d.ts +1 -1
  178. package/dist/sdk/agent-instruction.js +1 -1
  179. package/dist/sdk/instruct-agents.d.ts +1 -1
  180. package/dist/sdk/instruct-agents.d.ts.map +1 -1
  181. package/dist/sdk/instruct-agents.js +12 -8
  182. package/dist/sdk/instruct-agents.js.map +1 -1
  183. package/dist/version.d.ts +1 -1
  184. package/dist/version.js +1 -1
  185. package/package.json +24 -3
  186. package/templates/command/handler.declared.ts.tpl +29 -0
  187. package/templates/command/handler.spec.buses.ts.tpl +24 -1
  188. package/templates/command/handler.ts.tpl +29 -0
  189. package/templates/command/wiring.manual.tpl +2 -2
  190. package/templates/command/wiring.tsyringe.tpl +5 -2
  191. package/templates/event-handler/event-handler.declared.ts.tpl +1 -1
  192. package/templates/init/AGENTS.md.tpl +46 -0
  193. package/templates/init/CLAUDE.md.tpl +2 -45
  194. package/templates/init/README.md.tpl +1 -1
  195. package/templates/init/rules/application.md.tpl +1 -1
  196. package/templates/integration-event/publisher.declared.ts.tpl +1 -1
  197. package/templates/pipeline/github/workflows/copilot-setup-steps.yml +26 -0
  198. package/templates/pipeline/package-scripts.json +1 -1
  199. package/templates/presets/identity/context/application/integration/publishers/account-deactivated.publisher.ts.tpl +1 -1
  200. package/templates/presets/identity/context/application/integration/publishers/delegation-granted.publisher.ts.tpl +1 -1
  201. package/templates/presets/identity/context/application/integration/publishers/delegation-revoked.publisher.ts.tpl +1 -1
  202. package/templates/presets/identity/context/application/integration/publishers/member-joined.publisher.ts.tpl +1 -1
  203. package/templates/presets/identity/context/application/integration/publishers/member-removed.publisher.ts.tpl +1 -1
  204. package/templates/presets/identity/context/application/integration/publishers/member-role-changed.publisher.ts.tpl +1 -1
  205. package/templates/presets/identity/context/application/integration/publishers/organisation-opened.publisher.ts.tpl +1 -1
  206. package/templates/query/handler.declared.ts.tpl +19 -0
  207. package/templates/query/handler.spec.buses.ts.tpl +21 -0
  208. package/templates/query/handler.ts.tpl +19 -0
  209. package/templates/query/wiring.manual.tpl +1 -1
  210. package/templates/query/wiring.tsyringe.tpl +4 -1
@@ -1,19 +1,23 @@
1
1
  import { AGENT_INSTRUCTION } from './agent-instruction.js';
2
- const INSTRUCTIONS = 'CLAUDE.md';
3
- const PRESENTATION_RULE = '.claude/rules/presentation.md';
2
+ /** The guide of every agent; a project written before it kept its instructions in CLAUDE.md. */
3
+ const GUIDES = ['AGENTS.md', 'CLAUDE.md'];
4
+ const PRESENTATION_RULES = ['.claude/rules/presentation.md', '.github/instructions/presentation.instructions.md'];
4
5
  /**
5
6
  * What `init --sdk` writes for the agents of a project, in one that asks for clients later: the
6
- * instruction of CLAUDE.md, appended once, and a note for the paths of the presentation rule,
7
+ * instruction of its guide, appended once, and a note for the paths of the presentation rules,
7
8
  * which the team may have rewritten.
8
9
  */
9
10
  export function instructAgents(ws) {
10
- const instructions = ws.read(INSTRUCTIONS);
11
+ const guide = GUIDES.find((file) => ws.exists(file));
12
+ const instructions = guide === undefined ? undefined : ws.read(guide);
11
13
  const instruction = ws.spoken(AGENT_INSTRUCTION);
12
- if (instructions !== undefined && !instructions.includes(instruction)) {
13
- ws.update(INSTRUCTIONS, `${instructions}${instructions.endsWith('\n') ? '' : '\n'}\n${instruction}\n`);
14
+ if (guide !== undefined && instructions !== undefined && !instructions.includes(instruction)) {
15
+ ws.update(guide, `${instructions}${instructions.endsWith('\n') ? '' : '\n'}\n${instruction}\n`);
14
16
  }
15
- if (!(ws.read(PRESENTATION_RULE) ?? '"clients/**"').includes('"clients/**"')) {
16
- ws.note(`${PRESENTATION_RULE}: add "clients/**" to its paths, so that an agent editing a client reads it.`);
17
+ for (const rule of PRESENTATION_RULES) {
18
+ if (!(ws.read(rule) ?? 'clients/**').includes('clients/**')) {
19
+ ws.note(`${rule}: add "clients/**" to its paths, so that an agent editing a client reads it.`);
20
+ }
17
21
  }
18
22
  }
19
23
  //# sourceMappingURL=instruct-agents.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"instruct-agents.js","sourceRoot":"","sources":["../../src/sdk/instruct-agents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,MAAM,YAAY,GAAG,WAAW,CAAC;AACjC,MAAM,iBAAiB,GAAG,+BAA+B,CAAC;AAE1D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAa;IAC1C,MAAM,YAAY,GAAG,EAAE,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACtE,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE,GAAG,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC;IACzG,CAAC;IAED,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,cAAc,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;QAC7E,EAAE,CAAC,IAAI,CAAC,GAAG,iBAAiB,8EAA8E,CAAC,CAAC;IAC9G,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"instruct-agents.js","sourceRoot":"","sources":["../../src/sdk/instruct-agents.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,gGAAgG;AAChG,MAAM,MAAM,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAC1C,MAAM,kBAAkB,GAAG,CAAC,+BAA+B,EAAE,mDAAmD,CAAC,CAAC;AAElH;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,EAAa;IAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtE,MAAM,WAAW,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;IAEjD,IAAI,KAAK,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7F,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC;IAClG,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,kBAAkB,EAAE,CAAC;QACtC,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,YAAY,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YAC5D,EAAE,CAAC,IAAI,CAAC,GAAG,IAAI,8EAA8E,CAAC,CAAC;QACjG,CAAC;IACH,CAAC;AACH,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const VERSION = "0.17.0";
1
+ export declare const VERSION = "0.18.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/dist/version.js CHANGED
@@ -1,2 +1,2 @@
1
- export const VERSION = '0.17.0';
1
+ export const VERSION = '0.18.0';
2
2
  //# sourceMappingURL=version.js.map
package/package.json CHANGED
@@ -1,7 +1,28 @@
1
1
  {
2
2
  "name": "ddd-ts-kit",
3
- "version": "0.17.0",
4
- "description": "Bootstrap, scaffold and verify DDD and Clean Architecture TypeScript projects.",
3
+ "version": "0.18.0",
4
+ "description": "Bootstrap, scaffold and verify Domain-Driven Design (DDD), Clean Architecture and hexagonal TypeScript backends on Node.js: project and code generators, architecture checks, OpenAPI and AsyncAPI documents, for developers and AI coding agents (Claude Code, Codex, GitHub Copilot).",
5
+ "keywords": [
6
+ "ddd",
7
+ "domain-driven-design",
8
+ "clean-architecture",
9
+ "hexagonal-architecture",
10
+ "typescript",
11
+ "cli",
12
+ "scaffolding",
13
+ "code-generator",
14
+ "project-generator",
15
+ "architecture-testing",
16
+ "cqrs",
17
+ "event-sourcing",
18
+ "modular-monolith",
19
+ "microservices",
20
+ "nodejs",
21
+ "claude-code",
22
+ "codex",
23
+ "github-copilot",
24
+ "ai-agents"
25
+ ],
5
26
  "license": "MIT",
6
27
  "repository": {
7
28
  "type": "git",
@@ -29,6 +50,6 @@
29
50
  "clean:packages": "node dist/bin.js check --clean-only ../kernel/src ../validation/src ../http/src ../tracing/src ../di/src ../events/src ../testing/src ../s3/src ../drizzle/src ../knex/src ../prisma/src ../typeorm/src ../aspects/src ../rabbitmq/src ../auth/src ../audit/src ../redis/src ../scheduling/src ../pino/src ../winston/src ../sentry/src ../kafka/src ../nats/src ../eslint-plugin/src"
30
51
  },
31
52
  "devDependencies": {
32
- "@ddd-ts-kit/kernel": "^0.17.0"
53
+ "@ddd-ts-kit/kernel": "^0.18.0"
33
54
  }
34
55
  }
@@ -1,8 +1,14 @@
1
1
  {{#if command.hasAggregate}}
2
+ {{#if access.policy}}
3
+ import type { Access } from '@ddd-ts-kit/auth';
4
+ {{/if}}
2
5
  import type { Clock, IdGenerator } from '@ddd-ts-kit/kernel';
3
6
  import { CommandHandler } from '@ddd-ts-kit/kernel/declarations';
4
7
  import { {{aggregate.pascal}} } from '{{alias.context}}/domain/aggregates/{{aggregate.kebab}}.aggregate{{alias.ext}}';
5
8
  import type { {{aggregate.pascal}}Repository } from '{{alias.context}}/domain/repositories/{{aggregate.kebab}}.repository{{alias.ext}}';
9
+ {{#if access.policy}}
10
+ {{access.useCasePermissionsImport}}
11
+ {{/if}}
6
12
  import { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.command{{alias.ext}}';
7
13
 
8
14
  @CommandHandler({{name.pascal}}Command)
@@ -11,9 +17,17 @@ export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Com
11
17
  private readonly {{aggregate.camel}}Repository: {{aggregate.pascal}}Repository,
12
18
  private readonly ids: IdGenerator,
13
19
  private readonly clock: Clock,
20
+ {{#if access.policy}}
21
+ private readonly access: Access,
22
+ {{/if}}
14
23
  ) {}
15
24
 
16
25
  async execute(command: {{name.pascal}}Command): Promise<string> {
26
+ {{#if access.policy}}
27
+ // TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" acts on once it is loaded, in place of `command`.
28
+ await this.access.authorize(command.actor, {{access.reference}}, command);
29
+
30
+ {{/if}}
17
31
  {{#if refusals.factories}}
18
32
  // TODO(domain): decide what "{{name.words}}" does to a {{aggregate.pascal}}: either it gives birth to one (keep `create`, pass the facts of `command` as value objects), or it changes an existing one (load it with `findById`, throw its not-found refusal, `{{aggregate.pascal}}Error.notFound(id)` (`ddd-ts-kit generate error {{context.kebab}} {{aggregate.pascal}} notFound`), call ONE intention method). The events it records leave with the save: nothing else to do here.
19
33
  {{/if}}
@@ -29,12 +43,27 @@ export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Com
29
43
  }
30
44
  {{/if}}
31
45
  {{#unless command.hasAggregate}}
46
+ {{#if access.policy}}
47
+ import type { Access } from '@ddd-ts-kit/auth';
48
+ {{/if}}
32
49
  import { CommandHandler } from '@ddd-ts-kit/kernel/declarations';
50
+ {{#if access.policy}}
51
+ {{access.useCasePermissionsImport}}
52
+ {{/if}}
33
53
  import { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.command{{alias.ext}}';
34
54
 
35
55
  @CommandHandler({{name.pascal}}Command)
36
56
  export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Command> {
57
+ {{#if access.policy}}
58
+ constructor(private readonly access: Access) {}
59
+
60
+ {{/if}}
37
61
  async execute(command: {{name.pascal}}Command): Promise<void> {
62
+ {{#if access.policy}}
63
+ // TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" acts on once it is loaded, in place of `command`.
64
+ await this.access.authorize(command.actor, {{access.reference}}, command);
65
+
66
+ {{/if}}
38
67
  // TODO(domain): receive the repository of the aggregate "{{name.words}}" acts on in the constructor, typed by its port (rerun with --aggregate <Name> --force to get it declared), load or create the aggregate from `command`, call its intention method and save it (its events leave with the save); the unit of work is opened by the command bus.
39
68
  }
40
69
  }
@@ -1,6 +1,11 @@
1
1
  import { describe, expect, it } from '{{tests.module}}';
2
2
  {{#if access.protected}}
3
+ {{#if access.policy}}
4
+ import { AuthorizationMap, PolicyAccess, RoleBasedAuthorizer, authorizationMiddleware, requires } from '@ddd-ts-kit/auth';
5
+ {{/if}}
6
+ {{#unless access.policy}}
3
7
  import { AuthorizationMap, RoleBasedAuthorizer, authorizationMiddleware, requires } from '@ddd-ts-kit/auth';
8
+ {{/unless}}
4
9
  import { anActor } from '@ddd-ts-kit/auth/testing';
5
10
  {{/if}}
6
11
  import { createTestBuses } from '@ddd-ts-kit/testing';
@@ -14,12 +19,22 @@ import { Fake{{aggregate.pascal}}Repository } from '{{alias.context}}/test/fakes
14
19
  {{/if}}
15
20
  {{#if access.protected}}
16
21
 
22
+ {{#if access.policy}}
23
+ const authorizer = new RoleBasedAuthorizer({ roles: { holder: [{{context.camel}}Permissions['{{access.permission}}']] } });
24
+
25
+ const holders = authorizationMiddleware({
26
+ authorizer,
27
+ map: AuthorizationMap.of(requires({{name.pascal}}Command, {{context.camel}}Permissions['{{access.permission}}'])),
28
+ });
29
+ {{/if}}
30
+ {{#unless access.policy}}
17
31
  const holders = authorizationMiddleware({
18
32
  authorizer: new RoleBasedAuthorizer({
19
33
  roles: { holder: [{{context.camel}}Permissions['{{access.permission}}']] },
20
34
  }),
21
35
  map: AuthorizationMap.of(requires({{name.pascal}}Command, {{context.camel}}Permissions['{{access.permission}}'])),
22
36
  });
37
+ {{/unless}}
23
38
 
24
39
  const holder = anActor().withRole('holder').build();
25
40
  {{/if}}
@@ -71,16 +86,24 @@ function arrange() {
71
86
  // As the application hands it to the handler: the save announces the events of the {{aggregate.words}} to `domainEvents`.
72
87
  const {{aggregate.camel}}Repository = dispatching(new Fake{{aggregate.pascal}}Repository());
73
88
 
74
- const handler = new {{name.pascal}}Handler({{aggregate.camel}}Repository, ids, clock);
89
+ const handler = new {{name.pascal}}Handler({{aggregate.camel}}Repository, ids, clock{{#if access.policy}}, new PolicyAccess({ authorizer, clock }){{/if}});
75
90
 
76
91
  commandBus{{spec.guard}}.register({{name.pascal}}Command, handler);
77
92
 
78
93
  return { commandBus, {{aggregate.camel}}Repository, domainEvents };
79
94
  {{/if}}
80
95
  {{#unless command.hasAggregate}}
96
+ {{#if access.policy}}
97
+ const { commandBus, clock } = createTestBuses();
98
+ const handler = new {{name.pascal}}Handler(new PolicyAccess({ authorizer, clock }));
99
+
100
+ commandBus{{spec.guard}}.register({{name.pascal}}Command, handler);
101
+ {{/if}}
102
+ {{#unless access.policy}}
81
103
  const { commandBus } = createTestBuses();
82
104
 
83
105
  commandBus{{spec.guard}}.register({{name.pascal}}Command, new {{name.pascal}}Handler());
106
+ {{/unless}}
84
107
 
85
108
  return { commandBus };
86
109
  {{/unless}}
@@ -1,7 +1,13 @@
1
1
  {{#if command.hasAggregate}}
2
+ {{#if access.policy}}
3
+ import type { Access } from '@ddd-ts-kit/auth';
4
+ {{/if}}
2
5
  import type { Clock, CommandHandler, IdGenerator } from '@ddd-ts-kit/kernel';
3
6
  import { {{aggregate.pascal}} } from '{{alias.context}}/domain/aggregates/{{aggregate.kebab}}.aggregate{{alias.ext}}';
4
7
  import type { {{aggregate.pascal}}Repository } from '{{alias.context}}/domain/repositories/{{aggregate.kebab}}.repository{{alias.ext}}';
8
+ {{#if access.policy}}
9
+ {{access.useCasePermissionsImport}}
10
+ {{/if}}
5
11
  import type { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.command{{alias.ext}}';
6
12
 
7
13
  export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Command> {
@@ -9,9 +15,17 @@ export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Com
9
15
  private readonly {{aggregate.camel}}Repository: {{aggregate.pascal}}Repository,
10
16
  private readonly ids: IdGenerator,
11
17
  private readonly clock: Clock,
18
+ {{#if access.policy}}
19
+ private readonly access: Access,
20
+ {{/if}}
12
21
  ) {}
13
22
 
14
23
  async execute(command: {{name.pascal}}Command): Promise<string> {
24
+ {{#if access.policy}}
25
+ // TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" acts on once it is loaded, in place of `command`.
26
+ await this.access.authorize(command.actor, {{access.reference}}, command);
27
+
28
+ {{/if}}
15
29
  {{#if refusals.factories}}
16
30
  // TODO(domain): decide what "{{name.words}}" does to a {{aggregate.pascal}}: either it gives birth to one (keep `create`, pass the facts of `command` as value objects), or it changes an existing one (load it with `findById`, throw its not-found refusal, `{{aggregate.pascal}}Error.notFound(id)` (`ddd-ts-kit generate error {{context.kebab}} {{aggregate.pascal}} notFound`), call ONE intention method). The events it records leave with the save: nothing else to do here.
17
31
  {{/if}}
@@ -27,11 +41,26 @@ export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Com
27
41
  }
28
42
  {{/if}}
29
43
  {{#unless command.hasAggregate}}
44
+ {{#if access.policy}}
45
+ import type { Access } from '@ddd-ts-kit/auth';
46
+ {{/if}}
30
47
  import type { CommandHandler } from '@ddd-ts-kit/kernel';
48
+ {{#if access.policy}}
49
+ {{access.useCasePermissionsImport}}
50
+ {{/if}}
31
51
  import type { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.command{{alias.ext}}';
32
52
 
33
53
  export class {{name.pascal}}Handler implements CommandHandler<{{name.pascal}}Command> {
54
+ {{#if access.policy}}
55
+ constructor(private readonly access: Access) {}
56
+
57
+ {{/if}}
34
58
  async execute(command: {{name.pascal}}Command): Promise<void> {
59
+ {{#if access.policy}}
60
+ // TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" acts on once it is loaded, in place of `command`.
61
+ await this.access.authorize(command.actor, {{access.reference}}, command);
62
+
63
+ {{/if}}
35
64
  // TODO(domain): receive the repository of the aggregate "{{name.words}}" acts on in the constructor (rerun with --aggregate <Name> --force to get it wired), load or create the aggregate from `command`, call its intention method and save it (its events leave with the save); the unit of work is opened by the command bus.
36
65
  }
37
66
  }
@@ -5,10 +5,10 @@ import { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{
5
5
  import { {{name.pascal}}Handler } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.handler{{alias.ext}}';
6
6
  @@registrations
7
7
  {{#if command.hasAggregate}}
8
- platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler({{aggregate.camel}}Repository, platform.ids, platform.clock), { context: '{{context.kebab}}' });
8
+ platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler({{aggregate.camel}}Repository, platform.ids, platform.clock{{#if access.policy}}, platform.access{{/if}}), { context: '{{context.kebab}}' });
9
9
  {{/if}}
10
10
  {{#unless command.hasAggregate}}
11
- platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler(), { context: '{{context.kebab}}' });
11
+ platform.commands.register({{name.pascal}}Command, new {{name.pascal}}Handler({{#if access.policy}}platform.access{{/if}}), { context: '{{context.kebab}}' });
12
12
  {{/unless}}
13
13
  @@authorization
14
14
  {{access.manualDeclaration}}
@@ -1,4 +1,7 @@
1
1
  @@imports
2
+ {{#if access.policy}}
3
+ import { Access } from '@ddd-ts-kit/auth';
4
+ {{/if}}
2
5
  {{#if command.hasAggregate}}
3
6
  import { Clock, IdGenerator } from '@ddd-ts-kit/kernel';
4
7
  import { {{aggregate.pascal}}Repository } from '{{alias.context}}/domain/repositories/{{aggregate.kebab}}.repository{{alias.ext}}';
@@ -8,8 +11,8 @@ import { {{name.pascal}}Command } from '{{alias.context}}/application/commands/{
8
11
  import { {{name.pascal}}Handler } from '{{alias.context}}/application/commands/{{name.kebab}}/{{name.kebab}}.handler{{alias.ext}}';
9
12
  @@registrations
10
13
  {{#if command.hasAggregate}}
11
- builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler, [{{aggregate.pascal}}Repository, IdGenerator, Clock]);
14
+ builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler, [{{aggregate.pascal}}Repository, IdGenerator, Clock{{#if access.policy}}, Access{{/if}}]);
12
15
  {{/if}}
13
16
  {{#unless command.hasAggregate}}
14
- builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler);
17
+ builder.command({{name.pascal}}Command){{access.declaration}}.handledBy({{name.pascal}}Handler{{#if access.policy}}, [Access]{{/if}});
15
18
  {{/unless}}
@@ -1,7 +1,7 @@
1
1
  import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
2
2
  import { {{event.pascal}} } from '{{alias.context}}/domain/events/{{event.kebab}}.event{{alias.ext}}';
3
3
 
4
- @DomainEventHandler({{event.pascal}}, { name: {{event.reference}}{{#if handler.afterCommit}}, phase: 'after-commit'{{/if}} })
4
+ @DomainEventHandler({{event.pascal}}{{#if handler.afterCommit}}, { phase: 'after-commit' }{{/if}})
5
5
  export class {{event.pascal}}EventHandler implements DomainEventHandler<{{event.pascal}}> {
6
6
  async handle(event: {{event.pascal}}): Promise<void> {
7
7
  {{#unless handler.afterCommit}}
@@ -0,0 +1,46 @@
1
+ # {{project.name}}
2
+
3
+ A ddd-ts-kit project: one bounded context per directory of `{{config.contextsDir}}/`, each described by its `CONTEXT.md`. The plan and what is next: `docs/architecture/plan.md`, when the project follows the method.
4
+
5
+ ## Commands
6
+
7
+ - `npm run verify`: types, lint, format, tests with the coverage thresholds of `ddd-ts-kit.config.json` (per context and layer), architecture rules, import graph, dead code, documentation drift. Once, before a commit that closes a slice. A layer under its threshold gets the missing test, never an exclusion.
8
+ - `npx ddd-ts-kit check --changed-since <ref>`: the architecture rules on what changed; `npx ddd-ts-kit explain <rule-id>` gives the reason and an example.
9
+ - `npx ddd-ts-kit list --json`: the map of the contexts. `npx ddd-ts-kit describe <context> --json`: one context as data (use cases, routes, events, ports, dependencies).
10
+ - `npx ddd-ts-kit status`: the next step of the plan, the slices by status, the markers left per context. `npx ddd-ts-kit todo [<context>]`: each remaining `TODO(domain)` and `it.todo` as `file:line`.
11
+ - `npx ddd-ts-kit docs`: regenerates the context map and the generated sections of each `CONTEXT.md`; `npm run docs:generate` the OpenAPI contract, the forms and the event catalog.
12
+
13
+ ## Read less
14
+
15
+ 1. `describe <context> --json` and the `CONTEXT.md` of the context before the tree: do not browse `{{config.sourceRoot}}/` to learn what exists.
16
+ 2. Open only what the `next` list of a generator (`file:line`) or a violation names.
17
+ 3. `check --changed-since <ref>` and the tests of the context while iterating; `npm run verify` once at the end.
18
+
19
+ ## Rules by layer
20
+
21
+ One file per layer in `.claude/rules/`: `domain.md` (`domain/`, the shared kernel), `application.md`, `published-language.md`, `infrastructure.md`, `presentation.md` (and the OpenAPI contract), `platform.md` (`{{config.platformDir}}/`, `main.ts`, `apps/`{{#if style.explicit}}, module files{{/if}}), `tests.md` (specs, fakes, builders, `test/`). The same text is in `.github/instructions/<layer>.instructions.md`. Claude Code and GitHub Copilot load it with the files it applies to; an agent that does not (Codex) reads the file of a layer before it edits a file of that layer.
22
+
23
+ ## Generated files
24
+
25
+ Create every file of a role with `npx ddd-ts-kit generate <kind> ... --json`, then fill the `TODO(domain)` places it lists; never write one by hand, never restructure what a generator wrote.
26
+ {{#if assembly.barrel}}
27
+ Never edit `<context>.declarations.ts`: `generate`, `docs` and `npm run dev` rewrite it, from the role files and from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
28
+ {{/if}}
29
+ {{#if assembly.discovery}}
30
+ Never edit `<context>.dependencies.ts`: `generate`, `docs` and `npm run dev` rewrite it from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
31
+ {{/if}}
32
+ Never edit the contracts under `openapi/` (`openapi/openapi.yaml`, one file per document when `openapi.documents` splits it, the forms of `openapi/forms/`), `docs/integration/asyncapi.yaml`, `docs/integration/event-catalog.md`, `{{config.platformDir}}/http/openapi-documents.ts`, `.dependency-cruiser.cjs` or the generated sections of a `CONTEXT.md`: change the code or the config, then `npm run docs:generate`, `npx ddd-ts-kit docs` and `npx ddd-ts-kit depcruise-config --write`. `stored-events.lock.json` is written by `npx ddd-ts-kit docs --stored-events` once the shape of a version is final (`docs --check` fails while one is unrecorded), never by hand: a stored shape changes only by a new version with its upcaster (`event-sourcing/upcaster-required`).
33
+ {{#if sdk.on}}
34
+ {{sdk.instruction}}
35
+ {{/if}}
36
+ Imports: a file of the project is imported by its alias from the project root, never by a relative path, the same directory included: `{{alias.source}}/...` for `{{config.sourceRoot}}/`, `{{alias.test}}/...` for `test/`, `{{alias.scripts}}/...` for `scripts/`, {{#if alias.extensionless}}never ending in `.js` or `.ts`, and naming a file, never a directory{{/if}}{{#unless alias.extensionless}}always ending in `.js`{{/unless}} (`imports/alias-only`, `imports/extension`; `npm run lint:fix` rewrites a relative import and a wrong ending). The alias is declared once, under `paths` in `tsconfig.json`, from `importAlias` of `ddd-ts-kit.config.json`, and the ending follows `importExtension`: never add a second entry or mapping, never change `module` or `moduleResolution` in `tsconfig.json`, and never compile without `npm run build`, which rewrites the aliases of `dist/` into relative paths ending in `.js` for Node (`imports/alias-declared`).
37
+ Everywhere: one exported class, interface or function per file, named after it in kebab-case; at most 350 lines a file, 30 lines and 3 parameters a function; `private` members, never `#name`; a blank line before a `return` and around `if`, `for`, `try` (`npm run lint:fix`); braces around the body of every `if`, `else`, `for`, `while` and `do`, on its own lines: a branch is visible at a glance, and a statement added to it later cannot silently fall outside it. `npm run format` (Biome, line width 120) lays out every file: do not spend tokens on line breaks.
38
+
39
+ ## TypeScript
40
+
41
+ The compiler is the native TypeScript 7, installed as `@typescript/native`; `typescript` 6 is there for the tools that need its API (ESLint, dependency-cruiser). Use `npm run typecheck` and `npm run build`, never `npx tsc`, which would run version 6.
42
+
43
+ ## Workflow
44
+
45
+ Follow the recommended steps in order, the supported path (`README.md`, "Recommended steps"): discover the domain, bounded contexts, glossaries, context map, bootstrap, plan the slices, build slice by slice, quality gate, document. `npx ddd-ts-kit status` says where the project stands. When asked to skip a step, say which one and what it costs, and record it in `docs/architecture/plan.md`.
46
+ With the ddd-ts-kit plugin: `/ddd-ts-kit:start` for a new project or to resume one, `/ddd-ts-kit:glossary` before a new name, `/ddd-ts-kit:use-case` for a feature, `/ddd-ts-kit:document` for the contracts, `/ddd-ts-kit:quality-gate` before a pull or merge request. The plugin installs in Claude Code, Codex and the Copilot CLI from the marketplace `gbourgeat/ddd-ts-kit`; Codex calls a skill by its name, `$start`, `$use-case`.
@@ -1,46 +1,3 @@
1
- # {{project.name}}
1
+ @AGENTS.md
2
2
 
3
- A ddd-ts-kit project: one bounded context per directory of `{{config.contextsDir}}/`, each described by its `CONTEXT.md`. The plan and what is next: `docs/architecture/plan.md`, when the project follows the method.
4
-
5
- ## Commands
6
-
7
- - `npm run verify`: types, lint, format, tests with the coverage thresholds of `ddd-ts-kit.config.json` (per context and layer), architecture rules, import graph, dead code, documentation drift. Once, before a commit that closes a slice. A layer under its threshold gets the missing test, never an exclusion.
8
- - `npx ddd-ts-kit check --changed-since <ref>`: the architecture rules on what changed; `npx ddd-ts-kit explain <rule-id>` gives the reason and an example.
9
- - `npx ddd-ts-kit list --json`: the map of the contexts. `npx ddd-ts-kit describe <context> --json`: one context as data (use cases, routes, events, ports, dependencies).
10
- - `npx ddd-ts-kit status`: the next step of the plan, the slices by status, the markers left per context. `npx ddd-ts-kit todo [<context>]`: each remaining `TODO(domain)` and `it.todo` as `file:line`.
11
- - `npx ddd-ts-kit docs`: regenerates the context map and the generated sections of each `CONTEXT.md`; `npm run docs:generate` the OpenAPI contract, the forms and the event catalog.
12
-
13
- ## Read less
14
-
15
- 1. `describe <context> --json` and the `CONTEXT.md` of the context before the tree: do not browse `{{config.sourceRoot}}/` to learn what exists.
16
- 2. Open only what the `next` list of a generator (`file:line`) or a violation names.
17
- 3. `check --changed-since <ref>` and the tests of the context while iterating; `npm run verify` once at the end.
18
-
19
- ## Rules by layer
20
-
21
- Loaded with the files they apply to, from `.claude/rules/`: `domain.md` (`domain/`, the shared kernel), `application.md`, `published-language.md`, `infrastructure.md`, `presentation.md` (and the OpenAPI contract), `platform.md` (`{{config.platformDir}}/`, `main.ts`, `apps/`{{#if style.explicit}}, module files{{/if}}), `tests.md` (specs, fakes, builders, `test/`).
22
-
23
- ## Generated files
24
-
25
- Create every file of a role with `npx ddd-ts-kit generate <kind> ... --json`, then fill the `TODO(domain)` places it lists; never write one by hand, never restructure what a generator wrote.
26
- {{#if assembly.barrel}}
27
- Never edit `<context>.declarations.ts`: `generate`, `docs` and `npm run dev` rewrite it, from the role files and from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
28
- {{/if}}
29
- {{#if assembly.discovery}}
30
- Never edit `<context>.dependencies.ts`: `generate`, `docs` and `npm run dev` rewrite it from the constructor signatures (it registers what each constructor is injected). After changing a constructor by hand, `npx ddd-ts-kit docs`.
31
- {{/if}}
32
- Never edit the contracts under `openapi/` (`openapi/openapi.yaml`, one file per document when `openapi.documents` splits it, the forms of `openapi/forms/`), `docs/integration/asyncapi.yaml`, `docs/integration/event-catalog.md`, `{{config.platformDir}}/http/openapi-documents.ts`, `.dependency-cruiser.cjs` or the generated sections of a `CONTEXT.md`: change the code or the config, then `npm run docs:generate`, `npx ddd-ts-kit docs` and `npx ddd-ts-kit depcruise-config --write`. `stored-events.lock.json` is written by `npx ddd-ts-kit docs --stored-events` once the shape of a version is final (`docs --check` fails while one is unrecorded), never by hand: a stored shape changes only by a new version with its upcaster (`event-sourcing/upcaster-required`).
33
- {{#if sdk.on}}
34
- {{sdk.instruction}}
35
- {{/if}}
36
- Imports: a file of the project is imported by its alias from the project root, never by a relative path, the same directory included: `{{alias.source}}/...` for `{{config.sourceRoot}}/`, `{{alias.test}}/...` for `test/`, `{{alias.scripts}}/...` for `scripts/`, {{#if alias.extensionless}}never ending in `.js` or `.ts`, and naming a file, never a directory{{/if}}{{#unless alias.extensionless}}always ending in `.js`{{/unless}} (`imports/alias-only`, `imports/extension`; `npm run lint:fix` rewrites a relative import and a wrong ending). The alias is declared once, under `paths` in `tsconfig.json`, from `importAlias` of `ddd-ts-kit.config.json`, and the ending follows `importExtension`: never add a second entry or mapping, never change `module` or `moduleResolution` in `tsconfig.json`, and never compile without `npm run build`, which rewrites the aliases of `dist/` into relative paths ending in `.js` for Node (`imports/alias-declared`).
37
- Everywhere: one exported class, interface or function per file, named after it in kebab-case; at most 350 lines a file, 30 lines and 3 parameters a function; `private` members, never `#name`; a blank line before a `return` and around `if`, `for`, `try` (`npm run lint:fix`); braces around the body of every `if`, `else`, `for`, `while` and `do`, on its own lines: a branch is visible at a glance, and a statement added to it later cannot silently fall outside it. `npm run format` (Biome, line width 120) lays out every file: do not spend tokens on line breaks.
38
-
39
- ## TypeScript
40
-
41
- The compiler is the native TypeScript 7, installed as `@typescript/native`; `typescript` 6 is there for the tools that need its API (ESLint, dependency-cruiser). Use `npm run typecheck` and `npm run build`, never `npx tsc`, which would run version 6.
42
-
43
- ## Workflow
44
-
45
- Follow the recommended steps in order, the supported path (`README.md`, "Recommended steps"): discover the domain, bounded contexts, glossaries, context map, bootstrap, plan the slices, build slice by slice, quality gate, document. `npx ddd-ts-kit status` says where the project stands. When asked to skip a step, say which one and what it costs, and record it in `docs/architecture/plan.md`.
46
- With the ddd-ts-kit plugin: `/ddd-ts-kit:start` for a new project or to resume one, `/ddd-ts-kit:glossary` before a new name, `/ddd-ts-kit:use-case` for a feature, `/ddd-ts-kit:document` for the contracts, `/ddd-ts-kit:quality-gate` before a pull or merge request.
3
+ The rules by layer of `.claude/rules/` load with the files they apply to.
@@ -1,7 +1,7 @@
1
1
  # {{project.name}}
2
2
 
3
3
  Domain-Driven Design and Clean Architecture on [ddd-ts-kit](https://www.npmjs.com/package/ddd-ts-kit).
4
- The rules are in `CLAUDE.md` and `.claude/rules/`; `npx ddd-ts-kit explain <rule-id>` explains each one.
4
+ The rules are in `AGENTS.md`, which every coding agent reads, and by layer in `.claude/rules/` and `.github/instructions/`; `npx ddd-ts-kit explain <rule-id>` explains each one.
5
5
 
6
6
  ## Run it
7
7
 
@@ -11,7 +11,7 @@ paths:
11
11
  - Nothing here reads the event store: an event-sourced aggregate is loaded and saved through its repository port like any other (`event-sourcing/store-behind-repository`). A projection writes its read model and never a stream (`event-sourcing/projection-never-appends`); one fed by the store is declared `@Projection(name, { feed: 'event-store' })`.
12
12
  {{#if style.declared}}
13
13
  - Every command and query carries `@Requires(permission)` or `@Public()` (`authz/declared`), and the actor is in the message (`authz/actor-explicit`). Permissions live in `authorization/<context>.permissions.ts`, rules that need loaded state in `authorization/*.policy.ts`.
14
- - The decorator is the registration (`declarations/role-declared`): `@CommandHandler(Message)`, `@QueryHandler(Message)`, `@DomainEventHandler(Event, { name })`, `@IntegrationEventHandler(definition, { consumer })`, `@Scheduled('<context>.<job>', schedule)`, `@Background(options)` (the name is the static `jobName`), `@Projection('<context>.<name>')`, `@Policy(permission, { name })`. Names are string literals.
14
+ - The decorator is the registration (`declarations/role-declared`): `@CommandHandler(Message)`, `@QueryHandler(Message)`, `@DomainEventHandler(Event)` (the name is the static `eventName` of the event class), `@IntegrationEventHandler(definition, { consumer })`, `@Scheduled('<context>.<job>', schedule)`, `@Background(options)` (the name is the static `jobName`), `@Projection('<context>.<name>')`, `@Policy(permission, { name })`. Names are string literals.
15
15
  - The constructor is the one statement of what a class depends on (`declarations/constructor-injection`): each parameter is typed by one port class, with no `?`, default value, rest or destructuring, and no list repeats it in the decorator. To add a dependency, add the parameter: `npx ddd-ts-kit docs` reads the signature into the generated file of the context (`npm run dev` does it on every change), never edit that file.
16
16
  {{/if}}
17
17
  {{#if style.explicit}}
@@ -3,7 +3,7 @@ import { DomainEventHandler } from '@ddd-ts-kit/kernel/declarations';
3
3
  import { {{event.pascal}} } from '{{alias.context}}/domain/events/{{event.kebab}}.event{{alias.ext}}';
4
4
  import { {{event.camel}}V{{event.version}} } from '{{alias.context}}/published-language/{{event.kebab}}.v{{event.version}}.integration-event{{alias.ext}}';
5
5
 
6
- @DomainEventHandler({{event.pascal}}, { name: {{event.reference}} })
6
+ @DomainEventHandler({{event.pascal}})
7
7
  export class {{publisher.pascal}}Publisher implements DomainEventHandler<{{event.pascal}}> {
8
8
  constructor(
9
9
  private readonly publisher: IntegrationEventPublisher,
@@ -0,0 +1,26 @@
1
+ name: Copilot setup steps
2
+
3
+ # The environment the Copilot cloud agent starts in: Node and the dependencies of the lockfile,
4
+ # so that the checks and the generators of the kit answer at once. Copilot reads this file from
5
+ # the default branch, and only a job of this name; the runs below check it when it changes.
6
+ on:
7
+ workflow_dispatch:
8
+ push:
9
+ paths:
10
+ - .github/workflows/copilot-setup-steps.yml
11
+ pull_request:
12
+ paths:
13
+ - .github/workflows/copilot-setup-steps.yml
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ copilot-setup-steps:
20
+ runs-on: ubuntu-latest
21
+ timeout-minutes: 15
22
+ permissions:
23
+ contents: read
24
+ steps:
25
+ - uses: actions/checkout@v7
26
+ - uses: ./.github/actions/setup
@@ -23,7 +23,7 @@
23
23
  "@biomejs/biome": "2.5.15",
24
24
  "@commitlint/cli": "^21.2.3",
25
25
  "@commitlint/config-conventional": "^21.2.3",
26
- "@ddd-ts-kit/eslint-plugin": "^0.17.0",
26
+ "@ddd-ts-kit/eslint-plugin": "^0.18.0",
27
27
  "@eslint/js": "^10.0.1",
28
28
  "@typescript/native": "npm:typescript@~7.0.2",
29
29
  "@vitest/coverage-v8": "5.0.3",
@@ -4,7 +4,7 @@ import { AccountDeactivated } from '{{alias.context}}/domain/events/account-deac
4
4
  import { accountDeactivatedV1 } from '{{alias.context}}/published-language/account-deactivated.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(AccountDeactivated, { name: AccountDeactivated.eventName })
7
+ @DomainEventHandler(AccountDeactivated)
8
8
  export class AccountDeactivatedPublisher implements DomainEventHandler<AccountDeactivated> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -4,7 +4,7 @@ import { DelegationGranted } from '{{alias.context}}/domain/events/delegation-gr
4
4
  import { delegationGrantedV1 } from '{{alias.context}}/published-language/delegation-granted.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(DelegationGranted, { name: DelegationGranted.eventName })
7
+ @DomainEventHandler(DelegationGranted)
8
8
  export class DelegationGrantedPublisher implements DomainEventHandler<DelegationGranted> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -4,7 +4,7 @@ import { DelegationRevoked } from '{{alias.context}}/domain/events/delegation-re
4
4
  import { delegationRevokedV1 } from '{{alias.context}}/published-language/delegation-revoked.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(DelegationRevoked, { name: DelegationRevoked.eventName })
7
+ @DomainEventHandler(DelegationRevoked)
8
8
  export class DelegationRevokedPublisher implements DomainEventHandler<DelegationRevoked> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -4,7 +4,7 @@ import { MemberJoined } from '{{alias.context}}/domain/events/member-joined.even
4
4
  import { memberJoinedV1 } from '{{alias.context}}/published-language/member-joined.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(MemberJoined, { name: MemberJoined.eventName })
7
+ @DomainEventHandler(MemberJoined)
8
8
  export class MemberJoinedPublisher implements DomainEventHandler<MemberJoined> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -4,7 +4,7 @@ import { MemberRemoved } from '{{alias.context}}/domain/events/member-removed.ev
4
4
  import { memberRemovedV1 } from '{{alias.context}}/published-language/member-removed.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(MemberRemoved, { name: MemberRemoved.eventName })
7
+ @DomainEventHandler(MemberRemoved)
8
8
  export class MemberRemovedPublisher implements DomainEventHandler<MemberRemoved> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -4,7 +4,7 @@ import { MemberRoleChanged } from '{{alias.context}}/domain/events/member-role-c
4
4
  import { memberRoleChangedV1 } from '{{alias.context}}/published-language/member-role-changed.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(MemberRoleChanged, { name: MemberRoleChanged.eventName })
7
+ @DomainEventHandler(MemberRoleChanged)
8
8
  export class MemberRoleChangedPublisher implements DomainEventHandler<MemberRoleChanged> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -4,7 +4,7 @@ import { OrganisationOpened } from '{{alias.context}}/domain/events/organisation
4
4
  import { organisationOpenedV1 } from '{{alias.context}}/published-language/organisation-opened.v1.integration-event{{alias.ext}}';
5
5
 
6
6
  /** Tells the other contexts, through the outbox, in the transaction of the fact. */
7
- @DomainEventHandler(OrganisationOpened, { name: OrganisationOpened.eventName })
7
+ @DomainEventHandler(OrganisationOpened)
8
8
  export class OrganisationOpenedPublisher implements DomainEventHandler<OrganisationOpened> {
9
9
  constructor(
10
10
  private readonly publisher: IntegrationEventPublisher,
@@ -1,13 +1,32 @@
1
+ {{#if access.policy}}
2
+ import type { Access } from '@ddd-ts-kit/auth';
3
+ {{/if}}
1
4
  import { QueryHandler } from '@ddd-ts-kit/kernel/declarations';
5
+ {{#if access.policy}}
6
+ {{access.useCasePermissionsImport}}
7
+ {{/if}}
2
8
  import type { {{name.pascal}}Reader } from '{{alias.context}}/application/ports/{{name.kebab}}-reader.port{{alias.ext}}';
3
9
  import { {{name.pascal}}Query } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.query{{alias.ext}}';
4
10
  import type { {{name.pascal}}Result } from '{{alias.context}}/application/queries/{{name.kebab}}/{{name.kebab}}.result{{alias.ext}}';
5
11
 
6
12
  @QueryHandler({{name.pascal}}Query)
7
13
  export class {{name.pascal}}Handler implements QueryHandler<{{name.pascal}}Query> {
14
+ {{#if access.policy}}
15
+ constructor(
16
+ private readonly reader: {{name.pascal}}Reader,
17
+ private readonly access: Access,
18
+ ) {}
19
+ {{/if}}
20
+ {{#unless access.policy}}
8
21
  constructor(private readonly reader: {{name.pascal}}Reader) {}
22
+ {{/unless}}
9
23
 
10
24
  async execute(query: {{name.pascal}}Query): Promise<{{name.pascal}}Result> {
25
+ {{#if access.policy}}
26
+ // TODO(domain): a policy refines {{access.reference}}: ask it about the element "{{name.words}}" reads once it is read, in place of `query`.
27
+ await this.access.authorize(query.actor, {{access.reference}}, query);
28
+
29
+ {{/if}}
11
30
  // TODO(domain): narrow what is read to what the asker may see: ask `Access.scope(query.actor, permission, '<type>')` for the scope and hand it to the reader, never filter in memory after loading; a query never changes state.
12
31
  return this.reader.read(query);
13
32
  }