@phuc1403/musketeer 0.6.0 → 0.8.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 (60) hide show
  1. package/INSTALLATION.md +12 -7
  2. package/manifest.json +12 -14
  3. package/package.json +4 -2
  4. package/src/dotnet-scaffold-copier.js +7 -0
  5. package/src/provisioner/detect.js +3 -23
  6. package/template/.claude/hooks/block-unsafe-adr-title.cjs +40 -27
  7. package/template/.claude/hooks/init-adr-dir.cjs +62 -42
  8. package/template/.claude/hooks/inject-adr-flags.cjs +94 -0
  9. package/template/.claude/hooks/inject-design-docs.cjs +13 -13
  10. package/template/.claude/hooks/inject-ubiquitous-language.cjs +52 -0
  11. package/template/.claude/hooks/lib/adr/command-scan.cjs +9 -1
  12. package/template/.claude/hooks/sync-adr-toc.cjs +42 -7
  13. package/template/.claude/skills/adr-writer/SKILL.md +48 -46
  14. package/template/.claude/skills/context-map/SKILL.md +1 -1
  15. package/template/.claude/skills/knowledge-crunching/SKILL.md +56 -92
  16. package/template/.claude/skills/knowledge-crunching/assets/ubiquitous-language.template.md +3 -0
  17. package/template/.claude/hooks/inject-adr-env.cjs +0 -83
  18. package/template/.claude/hooks/inject-context.cjs +0 -52
  19. package/template/.claude/skills/knowledge-crunching/assets/context.template.md +0 -59
  20. package/template/.claude/skills/knowledge-crunching/references/crunching-dialogue.md +0 -113
  21. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  22. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.AssemblyInfo.cs +0 -22
  23. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.AssemblyInfoInputs.cache +0 -1
  24. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.GeneratedMSBuildEditorConfig.editorconfig +0 -23
  25. package/template/dotnet-scaffold/src/__SolutionName__.Api/obj/Debug/net10.0/__SolutionName__.Api.GlobalUsings.g.cs +0 -17
  26. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  27. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.AssemblyInfo.cs +0 -22
  28. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.AssemblyInfoInputs.cache +0 -1
  29. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  30. package/template/dotnet-scaffold/src/__SolutionName__.Application/obj/Debug/net10.0/__SolutionName__.Application.GlobalUsings.g.cs +0 -8
  31. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  32. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.AssemblyInfo.cs +0 -22
  33. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.AssemblyInfoInputs.cache +0 -1
  34. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  35. package/template/dotnet-scaffold/src/__SolutionName__.Domain/obj/Debug/net10.0/__SolutionName__.Domain.GlobalUsings.g.cs +0 -8
  36. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  37. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.AssemblyInfo.cs +0 -22
  38. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.AssemblyInfoInputs.cache +0 -1
  39. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  40. package/template/dotnet-scaffold/src/__SolutionName__.Infrastructure/obj/Debug/net10.0/__SolutionName__.Infrastructure.GlobalUsings.g.cs +0 -8
  41. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  42. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.AssemblyInfo.cs +0 -22
  43. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.AssemblyInfoInputs.cache +0 -1
  44. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  45. package/template/dotnet-scaffold/tests/__SolutionName__.Api.Tests/obj/Debug/net10.0/__SolutionName__.Api.Tests.GlobalUsings.g.cs +0 -9
  46. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  47. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.AssemblyInfo.cs +0 -22
  48. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.AssemblyInfoInputs.cache +0 -1
  49. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  50. package/template/dotnet-scaffold/tests/__SolutionName__.Application.Tests/obj/Debug/net10.0/__SolutionName__.Application.Tests.GlobalUsings.g.cs +0 -9
  51. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  52. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.AssemblyInfo.cs +0 -22
  53. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.AssemblyInfoInputs.cache +0 -1
  54. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  55. package/template/dotnet-scaffold/tests/__SolutionName__.Domain.Tests/obj/Debug/net10.0/__SolutionName__.Domain.Tests.GlobalUsings.g.cs +0 -9
  56. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs +0 -4
  57. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.AssemblyInfo.cs +0 -22
  58. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.AssemblyInfoInputs.cache +0 -1
  59. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.GeneratedMSBuildEditorConfig.editorconfig +0 -17
  60. package/template/dotnet-scaffold/tests/__SolutionName__.Infrastructure.Tests/obj/Debug/net10.0/__SolutionName__.Infrastructure.Tests.GlobalUsings.g.cs +0 -9
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart hook (dotnet company): auto-load `docs/ubiquitous-language.md` —
3
+ // the bounded context's vocabulary, produced by the knowledge-crunching skill —
4
+ // into every session, so the domain's language and invariants "lead the code".
5
+ //
6
+ // If the doc is missing, ALERT the user (systemMessage) with the command that
7
+ // creates it. Any other error fails open (emits nothing, exit 0) so it can never
8
+ // block a session.
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+
12
+ const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
13
+ const REL = "docs/ubiquitous-language.md";
14
+ const file = path.join(root, "docs", "ubiquitous-language.md");
15
+
16
+ try {
17
+ let content;
18
+ try {
19
+ content = fs.readFileSync(file, "utf-8");
20
+ } catch {
21
+ // Not found — surface a visible warning to the user, inject nothing.
22
+ process.stdout.write(
23
+ JSON.stringify({
24
+ systemMessage:
25
+ `musketeer: no ${REL} — the bounded context's ubiquitous language is missing. ` +
26
+ "Run the knowledge-crunching skill (/knowledge-crunching) to create it.",
27
+ })
28
+ );
29
+ process.exit(0);
30
+ }
31
+
32
+ const additionalContext =
33
+ `Ubiquitous language (${REL}) — injected every session. It is the vocabulary and ` +
34
+ "model rules of this bounded context, kept vendor- and decision-neutral. Treat it as " +
35
+ "canonical for domain naming, concepts, and invariants: name new code after it, and when a " +
36
+ `concept is renamed update ${REL} and the code in the same turn. It bounds what the DOMAIN ` +
37
+ "model sees, not what infrastructure may do (an ACL can legitimately key on more).\n\n" +
38
+ `===== ${REL} =====\n` +
39
+ content.trimEnd();
40
+
41
+ process.stdout.write(
42
+ JSON.stringify({
43
+ hookSpecificOutput: {
44
+ hookEventName: "SessionStart",
45
+ additionalContext,
46
+ },
47
+ })
48
+ );
49
+ process.exit(0);
50
+ } catch {
51
+ process.exit(0); // fail open
52
+ }
@@ -22,9 +22,17 @@
22
22
  // false one blocks an unrelated command for a reason nobody can guess.
23
23
 
24
24
  // boundary/start, whitespace, any existing KEY=value prefix, then `adr <sub>`.
25
+ //
26
+ // The subcommand must not run straight into a word character or a hyphen. `\b`
27
+ // alone is not enough: it sits happily between the `w` of `new` and the `-` of
28
+ // `adr new-thing`, so that string would count as an `adr new` invocation. A hook
29
+ // that only reads the command could shrug that off, but inject-adr-flags writes
30
+ // the flag directly after the subcommand and would turn it into `adr new -q-thing`.
31
+ // A trailing `;` or `|` still has to count, so this is a negated class rather
32
+ // than a required space.
25
33
  function invocationRe(sub) {
26
34
  return new RegExp(
27
- `(^|[;&|\\n(){}])([ \\t]*)((?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\\S*)[ \\t]+)*)(adr[ \\t]+${sub}\\b)`,
35
+ `(^|[;&|\\n(){}])([ \\t]*)((?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\\S*)[ \\t]+)*)(adr[ \\t]+${sub})(?![\\w-])`,
28
36
  'g'
29
37
  );
30
38
  }
@@ -6,6 +6,11 @@
6
6
  // source. That is why there is no need to be clever about which adr subcommand
7
7
  // ran, or to protect the file from being rewritten.
8
8
  //
9
+ // Also deletes the tool's own `decisions.md`. Every `adr new` regenerates that
10
+ // file unconditionally (not just on `adr generate toc`), which would leave two
11
+ // competing indexes side by side in the same directory. README.md is the richer
12
+ // one — it carries a status column — so it is the one kept.
13
+ //
9
14
  // Also runnable directly: node sync-adr-toc.cjs --write
10
15
  //
11
16
  // Fails open at exit 0 on any problem — a hook must never block a session.
@@ -16,7 +21,7 @@ const path = require('path');
16
21
  const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
17
22
  const ADR_FILE = /^\d+-.*\.md$/;
18
23
 
19
- // adr-tools reads its directory from a committed `.adr-dir` (default `doc/adr`).
24
+ // The tool reads its directory from a committed `.adr-dir` (default `doc/adr`).
20
25
  function adrDir() {
21
26
  try {
22
27
  return fs.readFileSync(path.join(root, '.adr-dir'), 'utf8').trim() || 'doc/adr';
@@ -25,8 +30,8 @@ function adrDir() {
25
30
  }
26
31
  }
27
32
 
28
- // Was this Bash call an adr command? (`VISUAL=true EDITOR=true adr new …` counts;
29
- // the internal `adr-new` script name does not, hence the required whitespace.)
33
+ // Was this Bash call an adr command? (`adr new -q …` counts; a hyphenated name
34
+ // such as `adr-new` does not, hence the required whitespace.)
30
35
  function isAdrCommand() {
31
36
  try {
32
37
  const payload = JSON.parse(fs.readFileSync(0, 'utf8'));
@@ -36,8 +41,9 @@ function isAdrCommand() {
36
41
  }
37
42
  }
38
43
 
39
- // First line, minus the leading "# " and adr-tools' own number prefix — it writes
40
- // "# 12: Use X over Y" for 0012-….md. Dropping the unpadded 12 keeps the padded
44
+ // First line, minus the leading "# " and the tool's own number prefix — the
45
+ // template renders "# 12: Use X over Y" for 0012-….md. Dropping the unpadded 12
46
+ // keeps the padded
41
47
  // stem in the ADR column the only number in the file, so nothing here can be
42
48
  // mistaken for the stem `adr new -s` expects.
43
49
  function title(body, fallback) {
@@ -45,8 +51,8 @@ function title(body, fallback) {
45
51
  return first.replace(/^#\s*(\d+\s*[:.]\s*)?/, '').trim() || fallback;
46
52
  }
47
53
 
48
- // First non-empty line of the `## Status` section. adr-tools maintains this:
49
- // "Accepted", or "Superceded by [4: …](0004-….md)" after `adr new -s`.
54
+ // First non-empty line of the `## Status` section. The tool maintains this:
55
+ // "Accepted", or "Superseded by [4: …](0004-….md)" after `adr new -s`.
50
56
  function status(body) {
51
57
  const lines = body.split(/\r?\n/);
52
58
  const start = lines.findIndex((l) => /^##\s+Status\s*$/i.test(l));
@@ -78,10 +84,39 @@ function render(dir, files) {
78
84
  ].join('\n');
79
85
  }
80
86
 
87
+ // The tool writes its own table of contents to `<adr-dir>/decisions.md` on every
88
+ // `adr new`. README.md supersedes it, so drop it rather than commit two indexes
89
+ // that have to agree with each other.
90
+ //
91
+ // Only ever deletes a file that is recognisably that generated index: the exact
92
+ // heading it writes, followed by nothing but link lines. `decisions.md` is an
93
+ // ordinary name for a hand-written decision log, and deleting one of those on
94
+ // the next `adr new` would destroy work no backup covers.
95
+ const GENERATED_TOC = /^# Table of Contents\r?\n\r?\n(?:- \[[^\]]*\]\([^)]*\)\r?\n?)*$/;
96
+
97
+ function removeRedundantToc(dir) {
98
+ const stale = path.join(root, dir, 'decisions.md');
99
+ try {
100
+ if (!fs.existsSync(stale)) return;
101
+ if (!GENERATED_TOC.test(fs.readFileSync(stale, 'utf8'))) {
102
+ process.stdout.write(
103
+ `Left ${dir}/decisions.md alone — it is not the generated index. ` +
104
+ 'README.md is the one this hook maintains.\n'
105
+ );
106
+ return;
107
+ }
108
+ fs.unlinkSync(stale);
109
+ process.stdout.write(`Removed ${dir}/decisions.md (README.md is the index).\n`);
110
+ } catch {
111
+ /* not worth failing the hook over */
112
+ }
113
+ }
114
+
81
115
  function main() {
82
116
  if (!process.argv.includes('--write') && !isAdrCommand()) return;
83
117
 
84
118
  const dir = adrDir();
119
+ removeRedundantToc(dir);
85
120
  const files = fs
86
121
  .readdirSync(path.join(root, dir))
87
122
  .filter((f) => ADR_FILE.test(f))
@@ -1,46 +1,48 @@
1
- ---
2
- name: adr-writer
3
- description: Write and manage Architecture Decision Records (ADRs) following structured methodology with proper numbering, status tracking, governance, and conversational writing style.
4
- ---
5
-
6
- # ADR Writer
7
-
8
- Write Architecture Decision Records — the log of "architecturally significant" decisions affecting structure, non-functional characteristics, dependencies, interfaces, or construction techniques.
9
-
10
- **Scope:** Create, update, and supersede ADRs. Does NOT implement the decisions themselves.
11
-
12
- ## Workflow
13
-
14
- 1. **Read the context.** `docs/architecture-characteristics.md` — every Decision is justified against these, with trade-offs framed as which are favored vs. sacrificed. If it is missing, read `.claude/skills/architecture-characteristic-writer/SKILL.md` and use its questions to gather the driving characteristics from the user before going on. Then read `docs/adr/README.md` for the decisions already recorded — one ADR's Consequences are often the next one's Context.
15
-
16
- 2. **Research before suggesting** — REQUIRED whenever a technology, vendor, product, version, or price is in play. Invoke `/research`; never propose options from memory, it goes stale. Confirm each option still exists and is supported today. Report in one pass, source and verdict per option.
17
-
18
- 3. **Challenge the proposal — be harsh.** REQUIRED whenever the user proposes a specific option. Do not rubber-stamp it:
19
- - **State why** — the concrete reasons, not familiarity or preference.
20
- - **Score it against each driving and implicit characteristic** — does it *serve*, *ignore*, or *actively harm* it?
21
- - **Give a verdict** — suitable / suitable-with-trade-offs / unsuitable. If it conflicts with a driving characteristic, say so plainly and recommend the better fit, even when that is not what was asked for.
22
- - Continue only once it survives, or the user overrides knowing the trade-off — record that override as a Consequence.
23
-
24
- 4. **Create the file.** From the repo root:
25
- ```bash
26
- adr new -- "Use X for Z"
27
- ```
28
- - Use the path `adr new` prints — never guess the number or slug.
29
- - Superseding — always the full padded stem, never a bare number (`-s 2` matches the first path containing "2", possibly the wrong file). Writes the cross-link into both ADRs' `## Status`:
30
- ```bash
31
- adr new -s 0002-use-mysql-for-persistence -- "…"
32
- ```
33
-
34
- 5. **Write the ADR.** Replace every `{…}` placeholder — nothing else. `adr new` has already written the number, title, `Accepted` status, and any supersede/link lines; leave all of them alone. Section Guidance below covers what each section needs. An Accepted ADR is immutable — to change it, write a new ADR that supersedes it.
35
-
36
- ## Writing Style
37
-
38
- - **Calibrate against `references/adr-example.md`** — re-read it before drafting; it is the target for terseness. Cut any section markedly longer than its equivalent.
39
- - **Match `references/adr-template.md` exactly.** Use only the sections it defines. No invented sections or fields; research citations go inline in the Decision.
40
-
41
- ## Section Guidance
42
-
43
- - **Title** — reveal the *decision*, not the topic: "Use X for Z". Bad: "Gmail Polling for Ingestion". Good: "Use Cloud Scheduler Polling for Gmail Ingestion".
44
- - **Context** — value-neutral, tensions explicit. No alternatives here, and no notes about what is *not* being decided.
45
- - **Decision** — record the WHY, not the HOW — omit libraries, drivers, and wiring.
46
- - **Consequences** — both `### Positive` and `### Negative` required. Consider team, infrastructure, cost, and one-way doors.
1
+ ---
2
+ name: adr-writer
3
+ description: Write and manage Architecture Decision Records (ADRs) following structured methodology with proper numbering, status tracking, governance, and conversational writing style.
4
+ ---
5
+
6
+ # ADR Writer
7
+
8
+ Write Architecture Decision Records — the log of "architecturally significant" decisions affecting structure, non-functional characteristics, dependencies, interfaces, or construction techniques.
9
+
10
+ **Scope:** Create, update, and supersede ADRs. Does NOT implement the decisions themselves.
11
+
12
+ ## Workflow
13
+
14
+ 1. **Read the context.** `docs/architecture-characteristics.md` — every Decision is justified against these, with trade-offs framed as which are favored vs. sacrificed. If it is missing, read `.claude/skills/architecture-characteristic-writer/SKILL.md` and use its questions to gather the driving characteristics from the user before going on. Then read `docs/adr/README.md` for the decisions already recorded — one ADR's Consequences are often the next one's Context.
15
+
16
+ 2. **Research before suggesting** — REQUIRED whenever a technology, vendor, product, version, or price is in play. Invoke `/research`; never propose options from memory, it goes stale. Confirm each option still exists and is supported today. Report in one pass, source and verdict per option.
17
+
18
+ 3. **Challenge the proposal — be harsh.** REQUIRED whenever the user proposes a specific option. Do not rubber-stamp it:
19
+ - **State why** — the concrete reasons, not familiarity or preference.
20
+ - **Score it against each driving and implicit characteristic** — does it *serve*, *ignore*, or *actively harm* it?
21
+ - **Give a verdict** — suitable / suitable-with-trade-offs / unsuitable. If it conflicts with a driving characteristic, say so plainly and recommend the better fit, even when that is not what was asked for.
22
+ - Continue only once it survives, or the user overrides knowing the trade-off — record that override as a Consequence.
23
+
24
+ 4. **Create the file.** From the repo root:
25
+ ```bash
26
+ adr new -q -- "Use X for Z"
27
+ ```
28
+ - `-q` is required on every call. Without it an ambiguous `-s` prompts on stdin and hangs the session; with it, that case errors instead.
29
+ - `adr new` prints nothing. Run `adr list` (last line is the new file) or read `docs/adr/README.md` — never guess the number or slug.
30
+ - Never use uppercase `STATUS` in a title. It is substituted after the title is inserted, so "Use STATUS codes" silently becomes "Use Accepted codes" and leaves the real Status unset. Lowercase is fine.
31
+ - Superseding — always the full padded stem, never a bare number. `-s` matches by substring: with `-q` an *ambiguous* match errors, but a single *wrong* match still succeeds silently. Writes the cross-link into both ADRs' `## Status`:
32
+ ```bash
33
+ adr new -q -s 0002-use-mysql-for-persistence -- "…"
34
+ ```
35
+
36
+ 5. **Write the ADR.** Replace every `{…}` placeholder — nothing else. `adr new` has already written the number, title, `Accepted` status, and any supersede/link lines; leave all of them alone. Section Guidance below covers what each section needs. An Accepted ADR is immutable — to change it, write a new ADR that supersedes it.
37
+
38
+ ## Writing Style
39
+
40
+ - **Calibrate against `references/adr-example.md`** — re-read it before drafting; it is the target for terseness. Cut any section markedly longer than its equivalent.
41
+ - **Match `references/adr-template.md` exactly.** Use only the sections it defines. No invented sections or fields; research citations go inline in the Decision.
42
+
43
+ ## Section Guidance
44
+
45
+ - **Title** — reveal the *decision*, not the topic: "Use X for Z". Bad: "Gmail Polling for Ingestion". Good: "Use Cloud Scheduler Polling for Gmail Ingestion".
46
+ - **Context** — value-neutral, tensions explicit. No alternatives here, and no notes about what is *not* being decided.
47
+ - **Decision** — record the WHY, not the HOW — omit libraries, drivers, and wiring.
48
+ - **Consequences** — both `### Positive` and `### Negative` required. Consider team, infrastructure, cost, and one-way doors.
@@ -75,6 +75,6 @@ For deeper validation, open the file in VSCode with the Context Mapper extension
75
75
 
76
76
  ## Glossary
77
77
 
78
- Same `CONTEXT.md` convention as the EventStorming skills. Challenge BC names against the glossary before writing. If a relationship introduces a novel domain term, add a one-sentence definition in `CONTEXT.md` the same turn.
78
+ Same `ubiquitous-language.md` convention as the EventStorming skills. Challenge BC names against the language doc before writing. If a relationship introduces a novel domain term, add a one-sentence definition in `docs/ubiquitous-language.md` the same turn.
79
79
 
80
80
  </supporting-info>
@@ -1,130 +1,94 @@
1
1
  ---
2
2
  name: knowledge-crunching
3
3
  description: >-
4
- Crunch domain knowledge with the user as domain expert, Eric Evans style: instead of drawing
5
- diagrams, express the emerging model as test-driven domain-layer code in the project's own
6
- language, verify understanding in a tight per-concept loop before writing anything, and record the
7
- shared vocabulary in the bounded context's CONTEXT.md (its ## Language section). Use when the user invokes
8
- /knowledge-crunching, describes a domain flow/feature to model, wants to collaboratively discover a
9
- domain model, distill a ubiquitous language, reconcile experts' terminology, or build a DDD domain
10
- prototype test-first while confirming each concept before coding.
4
+ Crunch domain knowledge with the user as domain expert into test-driven domain-layer code and a ubiquitous language (DDD). Use when the user invokes /knowledge-crunching, describes a domain flow or feature to model, wants to discover a domain model, distill a ubiquitous language, or reconcile experts' terminology.
11
5
  ---
12
6
 
13
7
  # Knowledge Crunching
14
8
 
15
- You are the developer/architect on a DDD team; the user is the **domain expert** (the client). You
16
- crunch their knowledge into a model the way Eric Evans did on the PCB project, with one change: **the
17
- emerging model is test-driven domain-layer code, not diagrams.**
9
+ You are the developer/architect on a DDD team; the user is the **domain expert** (the client). You crunch their knowledge into a model through a tight modeling dialogue, with one commitment: **the emerging model is test-driven domain-layer code, not diagrams.**
18
10
 
19
- **This skill handles:** the collaborative modeling *dialogue* and the domain-layer model it produces
20
- — types, behavior, unit tests, and the language in `CONTEXT.md` — for a module that already exists,
21
- whether its domain layer is a bare scaffold or a live model. **Does NOT handle:** persistence,
22
- repositories, UI, API/transport, deployment, strategic context-mapping, or standing up a new module
23
- scaffold (a prerequisite, done first). Stay in the domain layer; if the user asks for the rest, note
24
- it's out of scope and offer to hand off afterward.
11
+ **This skill handles:** the collaborative modeling *dialogue* and the domain-layer model it produces — types, behavior, unit tests, and the language in `docs/ubiquitous-language.md` — for a module that already exists, whether its domain layer is a bare scaffold or a live model. **Does NOT handle:** persistence, repositories, UI, API/transport, deployment, strategic context-mapping, or standing up a new module scaffold (a prerequisite, done first). Stay in the domain layer; if the user asks for the rest, note it's out of scope and offer to hand off afterward.
25
12
 
26
13
  ## The five stances (hold these the whole session)
27
14
 
28
- 1. **Model in code, not diagrams.** Every model fragment is a small type or method — in the bounded
29
- context's own language — you can show on screen. The running test, not a picture, is what makes the
30
- model concrete to the expert. (Examples in this skill are language-neutral pseudocode; write yours
31
- as real, runnable code and tests in the project's language and test framework.)
32
- 2. **Verify before you build.** Never write code for an unconfirmed concept — propose, ask, and
33
- **WAIT** for the answer (mechanics in Step 4). Their corrections *are* the model.
34
- 3. **Speak the expert's words.** Use their nouns and verbs verbatim. Reconcile synonyms and
35
- contradictions out loud ("ref-des and component instance — same thing?"). Code names == language names.
36
- 4. **Distill, don't transcribe.** Exclude every fact the current problem doesn't need (Evans dropped
37
- `Topology` for the probe simulation). Bring a concept back only when a feature actually pulls it in.
38
- 5. **Don't accept the solution as stated.** Experts often describe a mechanical solution ("read a file,
39
- sort it, write a report"). Dig past it for the domain concepts that yield the real leap.
15
+ 1. **Model in code, not diagrams.** Every model fragment is a small type or method — in the bounded context's own language — you can show on screen. The running test, not a picture, is what makes the model concrete to the expert.
16
+ 2. **Verify before you build.** Never write code for an unconfirmed concept — propose, ask, and **WAIT** for the answer.
17
+ 3. **Speak the expert's words.** Use their nouns and verbs verbatim. Reconcile synonyms and contradictions out loud ("customer and account holder — same thing?"). Code names == language names.
18
+ 4. **Distill, don't transcribe.** Exclude every fact the current problem doesn't need — the model is a distillation, not a transcription. Bring a concept back only when a feature actually pulls it in.
19
+ 5. **Don't accept the solution as stated.** A request usually arrives already framed as a mechanism ("export the table, sort it, email a summary"). That framing is not the domain. Dig past it for the concepts underneath — that is where the model is.
40
20
 
41
21
  ## Workflow
42
22
 
43
- ### Step 1 — Open the session and name the flow
23
+ ### Step 1 — Read what already exists
24
+ **First read any prior crunching output for that context** — `docs/ubiquitous-language.md` and the domain's code; in DDD, document = code. If the module is a bare scaffold with no language chosen yet, ask the user which language and test framework the context should be modeled in before proposing any code.
44
25
 
45
- **First read any prior crunching output for that context** — the root-level `CONTEXT.md` and read the domain's code; in DDD, document = code. While reading the code, **note the language, idioms, and test framework already in use** — you'll model in those. If the module is a bare scaffold with no language chosen yet, ask the user which language and test framework the context should be modeled in before proposing any code.
26
+ If a model is already there, read it against the language and let each mismatch become a Step 4 loop turn — surface it, verify with the user, then change code and `docs/ubiquitous-language.md` together:
46
27
 
47
- ### Step 2 — Find the first model elements (the "glimmer")
48
- Listen past the user's solution framing for the **domain nouns and verbs that keep recurring** — Name 2–4 candidate
49
- concepts back to the user and ask which ones are real. Do not scaffold types yet.
28
+ - **Synonym drift** — code says `Learner`, experts now say `Student`. Reconcile and pick one, in both.
29
+ - **Conflated concept** — one type doing two jobs the experts name separately → candidate split.
30
+ - **Leaked invariant** — a rule enforced in a service/controller that an aggregate should own.
31
+ - **Dead concept** — a type no scenario exercises anymore → remove it from the code and drop its entry.
32
+
33
+ ### Step 2 — Find the first model elements
34
+ The opening description is usually a mechanism, not a model (stance 5). Listen past it for the **domain nouns and verbs that keep recurring**, and name 2–4 of them back as candidate concepts.
35
+
36
+ For each, ask whether it is something the business actually names and treats as a distinct thing — as opposed to a word used in passing, or one you invented. You are testing your own guesses, so expect to be corrected. This is *not* "which word do you prefer?" — choosing between two words for one thing is stance 3, and it only arises once you have two.
37
+
38
+ > **You:** I'm hearing `Car`, `Inspection`, and something like `Listing` for a car on the lot for sale. Is `Listing` a real thing you'd name, or just "the car is on the lot"?
39
+ >
40
+ > **Expert:** It's real, but we call it a `Unit`, and only once it's passed inspection. A car we haven't inspected isn't a unit.
41
+
42
+ One answer, and you have a concept, its actual name, and the rule that brings it into existence. Do not scaffold types yet.
50
43
 
51
44
  ### Step 3 — Focus on ONE scenario
52
- Narrow to a single concrete scenario to make progress.
53
- Ask the user to pick or confirm the one scenario you'll model first. Everything else waits.
45
+ Narrow to a single concrete scenario to make progress. Ask the user to pick or confirm the one you'll model first; everything else waits. If their explanation runs ahead of you, say so and pull back here.
54
46
 
55
47
  ### Step 4 — The per-concept tight loop (the heart of the skill)
56
48
  For each concept the chosen scenario pulls in, run this loop — **one concept at a time**:
57
49
 
58
- 1. **Propose** one model fragment as minimal code in the project's language (a type, a method, a
59
- relationship) + a one-line plain gloss of what it means.
60
- 2. **Ask one sharp verifying question** that would expose a misunderstanding if your guess is wrong —
61
- about cardinality, a synonym, behavior ownership, an exclusion, a simplification, or the goal of the
62
- computation. (Catalog of question types + the PCB dialogue worked end-to-end:
63
- `references/crunching-dialogue.md`.)
50
+ 1. **Propose** one model fragment as minimal code in the project's language (a type, a method, a relationship) + a one-line plain gloss of what it means.
51
+ 2. **Ask one sharp verifying question** that would expose a misunderstanding if your guess is wrong. Pick the type that fits the fragment you just proposed — a good question is falsifiable, concrete, and answerable in a sentence, never "does this look right?":
52
+
53
+ | Type | What it pins down | Template |
54
+ |---|---|---|
55
+ | **Cardinality** | how many relate to how many | "Does one X belong to exactly one Y, or many?" |
56
+ | **Synonym** | two words, one concept | "Are X and Y the same thing?" |
57
+ | **Ownership of behavior** | which object does the work | "What pushes the signal — X or Y?" |
58
+ | **Exclusion / relevance** | is this concept needed *now* | "Does X matter for this scenario?" |
59
+ | **Simplification** | how little can we model | "Is a simplified Z enough instead of full X?" |
60
+ | **Computation goal** | what the output must be | "What exactly do you need from this?" |
61
+ | **Definition of a unit** | what one increment is | "What counts as one X?" |
62
+ | **Lifetime / sameness** | shared vs per-instance data | "Is this the same for every instance, or per instance?" |
63
+
64
+ If you can't think of a question, you don't understand the fragment well enough to code it — go smaller. Prefer a question that could get a "no"; one that can only be answered "yes" teaches nothing. After a "no," restate the corrected understanding before moving on.
64
65
  3. **WAIT** for the answer. Do not write code, tests, or the next fragment until the user responds.
65
66
  4. **Fold in** the correction (rename, re-shape, drop, or add), then record the term (Step 6).
66
67
 
67
68
  Example of the loop's rhythm (compressed):
68
69
 
69
- > **You:** Here's my first guess — a `Net` carries a signal to every `Pin` attached to it.
70
- > ```
71
- > Net — connects many Pins
72
- > ```
73
- > Are you saying a `Pin` belongs to exactly one `ComponentInstance` and connects to exactly one `Net`?
70
+ > **You:** Here's my first guess — an `Order` groups the `LineItem`s a customer buys in one go, so `Order` holds many `LineItem`s. Does a `LineItem` belong to exactly one `Order`, or can the same one sit on several?
74
71
  >
75
- > **Expert:** Yes — one pin, one component instance, one net.
72
+ > **Expert:** One line item, one order. Always.
76
73
  >
77
74
  > **You:** Good — I'll make that a hard invariant. *(folds in, records the term, then moves to the next concept)*
78
75
 
79
- ### Step 5 — TDD the confirmed behavior (one test at a time)
80
- Now make the confirmed fragment real — **domain only, no persistence, no UI**. The cardinal rule is
81
- **a genuine RED before any production code**:
82
-
83
- 1. The fragment you sketched in Step 4 is a *drawing in the chat, not a file*. **Do not create the
84
- production type yet.**
85
- 2. **RED** — write ONE test for ONE behavior the fragment must exhibit, run the project's test command,
86
- and show it **fail**. A compile error because the type/method doesn't exist yet counts as red — that
87
- failing run is the point; never skip it by writing the type and its test together.
88
- 3. **GREEN** — write the *minimal* code to pass that one test; run it; show green.
89
- 4. **Repeat one behavior at a time** — test → code, test → code. Never write several tests at once or
90
- write the whole type ahead of its tests. Batching tests ("horizontal slicing") yields tests of
91
- *imagined*, not actual, behavior.
92
- 5. Test **observable behavior through the public surface**, not getters or data shape. The test should
93
- read like the scenario's rule (Evans: "a path of more than 2–3 hops is a long delay; each Net
94
- crossing is one hop") and survive an internal refactor.
95
- 6. **Never refactor while red.** Get to green first; tidy only with the bar green.
96
-
97
- Let the failing-then-passing test — not a written-out class — be the turning point that makes the model
98
- concrete to the expert.
76
+ A loop turn is complete only when **all three** agree: the **expert** has answered the verifying question, the **code** reflects that answer, and **`docs/ubiquitous-language.md`** records the term. If any of the three lags, close the gap before proposing the next concept.
99
77
 
100
- ### Step 6 — Record the language immediately
101
- The ubiquitous language lives in **one** place: the bounded context's `CONTEXT.md`, under
102
- `## Language`. There is no separate glossary file. The moment a term is confirmed, add an entry in the
103
- repo's format — `**Term**:` then a one-line definition in the expert's words (mention the `TypeName`
104
- that embodies it), then `_Avoid_:` rejected synonyms — grouped under a `###` subsection. Keep code and
105
- `CONTEXT.md` identical: rename in code, rename here in the same turn. Distilled-out concepts go under
106
- `## Deferred`, unresolved ones under `## Flagged ambiguities`.
78
+ ### Step 5 — TDD the confirmed behavior (one test at a time)
79
+ Make the confirmed fragment real with the red-green loop in the `tdd` skill (`.claude/skills/tdd/SKILL.md`) — its rules on one-test-at-a-time, minimal green, testing observable behavior through the public surface, and never refactoring while red all apply here unchanged.
107
80
 
108
- Edit the context's existing root-level `CONTEXT.md` directly — it is canonical, and is
109
- also your seed (Step 1). If the module is new and has no `CONTEXT.md` yet, create one from
110
- `assets/context.template.md`.
81
+ Two amendments for a crunching session:
111
82
 
112
- ### Step 7 — Distill, then loop
113
- Drop concepts the current scenario doesn't need and say so ("I'll leave `Topology` out for now; we'll
114
- bring it back when we model routing"). When a new scenario arrives and the model can't express it,
115
- brainstorm a new concept or refactor an existing one toward deeper insight — that is crunching the
116
- knowledge further. Return to Step 3 for the next scenario.
83
+ - **Domain layer only.** No persistence, no application handlers, no UI. Ignore that skill's full-vertical-slice default — here the slice stops at the domain, and what comes next is the expert's next concept, not the next layer.
84
+ - **The Step 4 sketch is a drawing in the chat, not a file.** Do not create the production type ahead of its first failing test. A compile error because the type does not exist yet is a legitimate red, and that failing run is the point.
117
85
 
118
- ## Pace and etiquette
86
+ ### Step 6 — Record the language immediately
87
+ The ubiquitous language lives in **one** place: `docs/ubiquitous-language.md`. There is no separate glossary file. The moment a term is confirmed, add one line — `Term: ` then what it means in the expert's words. Nothing else: no rejected-synonym list, no deferred section, no grouping. The term is already the type's name (stance 3), so don't restate it. Rename in code, rename here in the same turn.
119
88
 
120
- - Move **one concept per turn**. A wall of ten types with no questions is the failure mode — it skips
121
- the verification that makes this skill worth running.
122
- - When the user's explanation makes your "head spin," say so and pull back to one concrete scenario.
123
- - Keep the prototype runnable at every step; a red build is fine *during* a cycle, never at a pause.
89
+ Write a dictionary entry, not prose. One line. No lead-in, no narrative, no "this is the concept that…". Keep implementation and vendor words out — they name the mechanism, not the concept (stance 5). Keep invariants out — rules live in the code and its tests (Step 5).
124
90
 
125
- ## Resources
91
+ If the doc does not exist yet, copy `assets/ubiquitous-language.template.md` from this skill to `docs/ubiquitous-language.md`. If it already exists, edit it — never overwrite it, and never start a second one. It is canonical, and is also your seed (Step 1).
126
92
 
127
- - `references/crunching-dialogue.md` — the verifying-question catalog, the PCB session worked
128
- end-to-end (dialogue → code → `CONTEXT.md`), and drift triggers in existing code. Read at Step 4.
129
- - `assets/context.template.md` — starter `CONTEXT.md` (repo's `## Language` format) for a **new**
130
- module that has none yet; if the context already has a `CONTEXT.md`, edit that one instead.
93
+ ### Step 7 — Distill, then loop
94
+ Drop concepts the current scenario doesn't need and say so ("I'll leave scheduling out for now; we'll bring it back when a feature needs it"). When a new scenario arrives and the model can't express it, brainstorm a new concept or refactor an existing one toward deeper insight — that is crunching the knowledge further. Return to Step 3 for the next scenario.
@@ -0,0 +1,3 @@
1
+ # Ubiquitous Language
2
+
3
+ <Term>: <what it means, in the expert's own words>
@@ -1,83 +0,0 @@
1
- #!/usr/bin/env node
2
- // PreToolUse guard: inject the env vars every `adr new` call needs, so the
3
- // agent can write a bare `adr new -- "Title"` and get a correct command.
4
- //
5
- // VISUAL=true EDITOR=true — without them an ambient EDITOR opens an
6
- // interactive editor and hangs the session (adr-new always shells out to
7
- // $VISUAL/$EDITOR to "edit" the new file).
8
- // ADR_TEMPLATE=...adr-template.md — points adr-new at this skill's template
9
- // instead of adr-tools' own bundled default.
10
- //
11
- // Uses PreToolUse's `updatedInput` (not just allow/deny) to rewrite the
12
- // command before it runs. Only fills in vars that are missing — an assignment
13
- // the agent already wrote (any value, even a deliberately different template)
14
- // is left alone. Silent no-op if all three are already present.
15
- //
16
- // Safety: a title that happens to contain the literal text "adr new" could
17
- // make the matching regex fire inside a quoted string. Guarded by counting
18
- // quote characters before each match — an odd count means "inside an open
19
- // string", and that occurrence is left untouched rather than risk corrupting
20
- // the title.
21
-
22
- const fs = require('fs');
23
- const {
24
- invocationRe,
25
- isInsideQuotes,
26
- isInsideHeredoc,
27
- invokesAdr,
28
- } = require('./lib/adr/command-scan.cjs');
29
-
30
- const DEFAULTS = [
31
- ['ADR_TEMPLATE', '.claude/skills/adr-writer/references/adr-template.md'],
32
- ['VISUAL', 'true'],
33
- ['EDITOR', 'true'],
34
- ];
35
-
36
- function existingVars(prefix) {
37
- const names = new Set();
38
- const re = /([A-Za-z_][A-Za-z0-9_]*)=/g;
39
- let m;
40
- while ((m = re.exec(prefix))) names.add(m[1]);
41
- return names;
42
- }
43
-
44
- function main() {
45
- const payload = JSON.parse(fs.readFileSync(0, 'utf8'));
46
- const command = payload?.tool_input?.command || '';
47
- if (!invokesAdr(command, 'new')) return;
48
-
49
- let changed = false;
50
- const updated = command.replace(invocationRe('new'), (whole, boundary, ws, existingPrefix, adrNew, offset) => {
51
- // Both guards, for the same reason the deny hooks use them: a heredoc body
52
- // is documentation being written, and injecting env vars into it would
53
- // rewrite the file's contents rather than the command.
54
- // `offset` is the boundary character; step past it to the word `adr`.
55
- const at = offset + boundary.length + ws.length + existingPrefix.length;
56
- if (isInsideQuotes(command, at) || isInsideHeredoc(command, at)) return whole;
57
- const present = existingVars(existingPrefix);
58
- const missing = DEFAULTS.filter(([name]) => !present.has(name));
59
- if (missing.length === 0) return whole;
60
- changed = true;
61
- const injected = missing.map(([name, value]) => `${name}=${value}`).join(' ');
62
- return `${boundary}${ws}${injected} ${existingPrefix}${adrNew}`;
63
- });
64
- if (!changed) return;
65
-
66
- process.stdout.write(
67
- JSON.stringify({
68
- systemMessage: 'Added missing VISUAL/EDITOR/ADR_TEMPLATE env vars before `adr new`.',
69
- hookSpecificOutput: {
70
- hookEventName: 'PreToolUse',
71
- permissionDecision: 'allow',
72
- updatedInput: { command: updated },
73
- },
74
- })
75
- );
76
- }
77
-
78
- try {
79
- main();
80
- } catch {
81
- /* fail open: leave the command untouched */
82
- }
83
- process.exit(0);
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
- // SessionStart hook (dotnet company): auto-load the repo-root `CONTEXT.md` — the
3
- // bounded-context model produced by the knowledge-crunching skill — into every
4
- // session, so the domain's ubiquitous language and invariants "lead the code".
5
- //
6
- // If the root CONTEXT.md is missing, ALERT the user (systemMessage) so they
7
- // create one. Any other error fails open (emits nothing, exit 0) so it can never
8
- // block a session.
9
- const fs = require("fs");
10
- const path = require("path");
11
-
12
- const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
13
- const file = path.join(root, "CONTEXT.md");
14
-
15
- try {
16
- let content;
17
- try {
18
- content = fs.readFileSync(file, "utf-8");
19
- } catch {
20
- // Not found — surface a visible warning to the user, inject nothing.
21
- process.stdout.write(
22
- JSON.stringify({
23
- systemMessage:
24
- "musketeer: no CONTEXT.md at the repo root — the bounded-context model is missing. " +
25
- "Run the knowledge-crunching skill (/knowledge-crunching) to create one.",
26
- })
27
- );
28
- process.exit(0);
29
- }
30
-
31
- const additionalContext =
32
- "Bounded-context model (root CONTEXT.md) — injected every session. It is the " +
33
- "ubiquitous language and model rules of this bounded context, kept vendor- and " +
34
- "decision-neutral. Treat it as canonical for domain naming, concepts, and invariants: " +
35
- "name new code after it, and when a concept is renamed update CONTEXT.md and the code in " +
36
- "the same turn. It bounds what the DOMAIN model sees, not what infrastructure may do " +
37
- "(an ACL can legitimately key on more).\n\n" +
38
- "===== CONTEXT.md =====\n" +
39
- content.trimEnd();
40
-
41
- process.stdout.write(
42
- JSON.stringify({
43
- hookSpecificOutput: {
44
- hookEventName: "SessionStart",
45
- additionalContext,
46
- },
47
- })
48
- );
49
- process.exit(0);
50
- } catch {
51
- process.exit(0); // fail open
52
- }