@phuc1403/musketeer 0.7.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.
- package/manifest.json +4 -4
- package/package.json +1 -1
- package/template/.claude/hooks/inject-design-docs.cjs +13 -13
- package/template/.claude/hooks/inject-ubiquitous-language.cjs +52 -0
- package/template/.claude/skills/context-map/SKILL.md +1 -1
- package/template/.claude/skills/knowledge-crunching/SKILL.md +56 -92
- package/template/.claude/skills/knowledge-crunching/assets/ubiquitous-language.template.md +3 -0
- package/template/.claude/hooks/inject-context.cjs +0 -52
- package/template/.claude/skills/knowledge-crunching/assets/context.template.md +0 -59
- package/template/.claude/skills/knowledge-crunching/references/crunching-dialogue.md +0 -113
package/manifest.json
CHANGED
|
@@ -146,14 +146,14 @@
|
|
|
146
146
|
},
|
|
147
147
|
"dotnet": {
|
|
148
148
|
"label": "dotnet",
|
|
149
|
-
"description": ".NET extras: tdd, knowledge-crunching + EF migration-guard &
|
|
149
|
+
"description": ".NET extras: tdd, knowledge-crunching + EF migration-guard & ubiquitous-language auto-load hooks. Muster also always merges 4 quality-gate MSBuild properties into root Directory.Build.props (any dotnet-selected project, set MUSKETEER_SKIP_DOTNET_PROPS=1 to skip) and scaffolds a generic src/tests Clean Architecture skeleton when the project is genuinely blank.",
|
|
150
150
|
"locked": false,
|
|
151
151
|
"deps": [],
|
|
152
152
|
"files": [
|
|
153
153
|
"skills/tdd/**",
|
|
154
154
|
"skills/knowledge-crunching/**",
|
|
155
155
|
"hooks/block-migration-edits.cjs",
|
|
156
|
-
"hooks/inject-
|
|
156
|
+
"hooks/inject-ubiquitous-language.cjs"
|
|
157
157
|
],
|
|
158
158
|
"settings": [
|
|
159
159
|
{
|
|
@@ -166,9 +166,9 @@
|
|
|
166
166
|
{
|
|
167
167
|
"event": "SessionStart",
|
|
168
168
|
"matcher": "startup|resume|clear|compact",
|
|
169
|
-
"command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-
|
|
169
|
+
"command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/inject-ubiquitous-language.cjs\"",
|
|
170
170
|
"order": 1,
|
|
171
|
-
"statusMessage": "Loading
|
|
171
|
+
"statusMessage": "Loading ubiquitous language"
|
|
172
172
|
}
|
|
173
173
|
],
|
|
174
174
|
"prereqs": []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phuc1403/musketeer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Distributable custom Claude Code harness — one declarative command scaffolds a curated company of musketeers (skills/agents/hooks) into any project's .claude/.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// SessionStart hook: inject
|
|
2
|
+
// SessionStart hook: inject the project's always-on design artifacts into every
|
|
3
3
|
// session so the design-first docs reliably "lead the code" (CLAUDE.md), instead
|
|
4
4
|
// of relying on me to remember to read them.
|
|
5
5
|
//
|
|
6
|
-
// Payload (~7K tokens, mostly the
|
|
6
|
+
// Payload (~7K tokens, mostly the vocabularies): the CML context map, the
|
|
7
7
|
// architecture-characteristics worksheet, the ADR index, and every bounded
|
|
8
|
-
// context's
|
|
9
|
-
// config change). tasks.json / event-storm JSON under bounded-contexts are
|
|
8
|
+
// context's ubiquitous-language.md (globbed recursively, so new BCs are picked
|
|
9
|
+
// up with no config change). tasks.json / event-storm JSON under bounded-contexts are
|
|
10
10
|
// deliberately excluded — task-tracking and raw walls, not design context.
|
|
11
11
|
//
|
|
12
12
|
// Fails open: any error emits nothing and exits 0, so it can never block a session.
|
|
@@ -15,8 +15,8 @@ const path = require("path");
|
|
|
15
15
|
|
|
16
16
|
const root = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
17
17
|
|
|
18
|
-
// Recursively collect
|
|
19
|
-
function
|
|
18
|
+
// Recursively collect ubiquitous-language.md under a directory (one per bounded context).
|
|
19
|
+
function findUbiquitousLanguageDocs(dir) {
|
|
20
20
|
const out = [];
|
|
21
21
|
let entries;
|
|
22
22
|
try {
|
|
@@ -27,8 +27,8 @@ function findContextDocs(dir) {
|
|
|
27
27
|
for (const entry of entries) {
|
|
28
28
|
const full = path.join(dir, entry.name);
|
|
29
29
|
if (entry.isDirectory()) {
|
|
30
|
-
out.push(...
|
|
31
|
-
} else if (entry.isFile() && entry.name === "
|
|
30
|
+
out.push(...findUbiquitousLanguageDocs(full));
|
|
31
|
+
} else if (entry.isFile() && entry.name === "ubiquitous-language.md") {
|
|
32
32
|
out.push(full);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -40,7 +40,7 @@ try {
|
|
|
40
40
|
path.join(root, "docs", "context-map.cml"),
|
|
41
41
|
path.join(root, "docs", "architecture-characteristics.md"),
|
|
42
42
|
path.join(root, "docs", "adr", "README.md"),
|
|
43
|
-
...
|
|
43
|
+
...findUbiquitousLanguageDocs(path.join(root, "docs", "bounded-contexts")),
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
const sections = [];
|
|
@@ -60,16 +60,16 @@ try {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
const additionalContext =
|
|
63
|
-
"
|
|
63
|
+
"Design artifacts — injected every session. " +
|
|
64
64
|
"Treat these as the source of truth for the domain model, context boundaries, architecture " +
|
|
65
65
|
"characteristics, and recorded decisions; consult them before any domain, " +
|
|
66
66
|
"architecture, or ADR work, and keep changes consistent with them.\n\n" +
|
|
67
|
-
"Each
|
|
68
|
-
"
|
|
67
|
+
"Each ubiquitous-language.md captures the decisions of the DOMAIN only — the " +
|
|
68
|
+
"vocabulary and model rules, kept vendor/decision-neutral. It bounds what the " +
|
|
69
69
|
"domain model sees, not what infrastructure may do: the ACL/infrastructure can " +
|
|
70
70
|
"legitimately key on more than the model names. (E.g. the domain only ever sees " +
|
|
71
71
|
"a learner's Email, never the auth method; the ACL may also match the OIDC `sub`.) " +
|
|
72
|
-
"That layering is intended, not a contradiction of
|
|
72
|
+
"That layering is intended, not a contradiction of the ubiquitous language.\n\n" +
|
|
73
73
|
sections.join("\n\n");
|
|
74
74
|
|
|
75
75
|
process.stdout.write(
|
|
@@ -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
|
+
}
|
|
@@ -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 `
|
|
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,
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 —
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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 —
|
|
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:**
|
|
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
|
-
|
|
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
|
|
101
|
-
|
|
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
|
-
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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.
|
|
@@ -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
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# {{CONTEXT_TITLE}}
|
|
2
|
-
|
|
3
|
-
<One line: the slice of the domain this context covers — the flow you crunched, in the expert's words.>
|
|
4
|
-
|
|
5
|
-
> Starter for a context that has **no `CONTEXT.md` yet**. It lives at the
|
|
6
|
-
> root folder as `CONTEXT.md`. If the context already has one, edit that — never a second.
|
|
7
|
-
|
|
8
|
-
## Language
|
|
9
|
-
|
|
10
|
-
The vocabulary of *this* context, crunched with the domain expert. Each entry must clear this bar:
|
|
11
|
-
|
|
12
|
-
- **One meaning.** A term denotes exactly one thing here. If it means two things, split it into two.
|
|
13
|
-
- **Defined in the expert's words**, present tense — never with implementation or vendor terms, and
|
|
14
|
-
never using the term to define itself.
|
|
15
|
-
- **Says what it is _not_** whenever it's easily confused with a neighbour — the sharpest
|
|
16
|
-
disambiguator there is.
|
|
17
|
-
- **Carries its governing rule** when one exists ("… finishes when …") — the language should imply the
|
|
18
|
-
behavior, not just label a noun.
|
|
19
|
-
- **Bound to code:** name the `TypeName` that embodies it. The type and the term are the same word.
|
|
20
|
-
- **`_Avoid_:` rejected synonyms** so the wrong word can't creep back (add a half-line *why* if it
|
|
21
|
-
isn't obvious).
|
|
22
|
-
|
|
23
|
-
Group related terms under `###` subsections. Reference other defined terms by their exact name.
|
|
24
|
-
|
|
25
|
-
Worked example of the bar (delete once you have your own):
|
|
26
|
-
|
|
27
|
-
### Connectivity
|
|
28
|
-
|
|
29
|
-
**Net**:
|
|
30
|
-
A conductor that carries one signal to every `Pin` connected to it; a signal crossing a `Net` counts as
|
|
31
|
-
one **hop**. _Not_ a physical wire segment — one `Net` may span many segments.
|
|
32
|
-
_Avoid_: wire, trace, connection
|
|
33
|
-
|
|
34
|
-
**Pin**:
|
|
35
|
-
A single connection point on a `ComponentInstance`. Belongs to exactly one `ComponentInstance` and
|
|
36
|
-
connects to exactly one `Net` — that one-to-one-to-one rule is an invariant.
|
|
37
|
-
_Avoid_: leg, terminal (terminal means the physical metal, not the model concept)
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
### <your first group>
|
|
42
|
-
|
|
43
|
-
**<Term>**:
|
|
44
|
-
<One sentence in the expert's words; fold in the governing rule if any, and what it is _not_ if it's
|
|
45
|
-
confusable; name the `TypeName` that embodies it.>
|
|
46
|
-
_Avoid_: <rejected synonyms>
|
|
47
|
-
|
|
48
|
-
## Deferred
|
|
49
|
-
|
|
50
|
-
Concepts that exist in the domain but this scenario doesn't need yet — distilled out, the way Evans
|
|
51
|
-
dropped `Topology` for the probe simulation. Bring one back only when a feature actually pulls it in.
|
|
52
|
-
|
|
53
|
-
- **<Term>** — <what it is; why it isn't needed yet>
|
|
54
|
-
|
|
55
|
-
## Flagged ambiguities
|
|
56
|
-
|
|
57
|
-
Open questions or contradictions between experts, to resolve in a later loop.
|
|
58
|
-
|
|
59
|
-
- <the question — and who or what would settle it>
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
# The Crunching Dialogue
|
|
2
|
-
|
|
3
|
-
How to run Step 4's per-concept loop well: the kinds of questions that actually move the model, and
|
|
4
|
-
the full PCB session translated from Evans' diagrams into the code/test/glossary this skill produces.
|
|
5
|
-
|
|
6
|
-
> The model fragments below are written in **language-neutral pseudocode** so the modeling moves stay
|
|
7
|
-
> the point. In a real session, write them as actual runnable types and tests in the bounded context's
|
|
8
|
-
> own language and test framework.
|
|
9
|
-
|
|
10
|
-
## Verifying-question catalog
|
|
11
|
-
|
|
12
|
-
Each loop turn asks **one** question whose answer would change the code if your guess is wrong. Pick
|
|
13
|
-
the type that fits the fragment you just proposed. A good question is falsifiable, concrete, and
|
|
14
|
-
answerable in a sentence — not "does this look right?"
|
|
15
|
-
|
|
16
|
-
| Type | What it pins down | Template | PCB example |
|
|
17
|
-
|---|---|---|---|
|
|
18
|
-
| **Cardinality** | how many relate to how many | "Does one X belong to exactly one Y, or many?" | "A `Pin` belongs to one `ComponentInstance` and one `Net`?" |
|
|
19
|
-
| **Synonym** | two words, one concept | "Are X and Y the same thing?" | "Is `ref-des` the same as `component instance`?" |
|
|
20
|
-
| **Ownership of behavior** | which object does the work | "What pushes the signal — X or Y?" | "Does the `Net` carry the signal further, or does the component push it?" |
|
|
21
|
-
| **Exclusion / relevance** | is this concept needed *now* | "Does X matter for this scenario?" | "Does `Topology` come into the probe simulation?" |
|
|
22
|
-
| **Simplification** | how little can we model | "Is a simplified Z enough instead of full X?" | "Can a list of push-throughs stand in for chip internals?" |
|
|
23
|
-
| **Computation goal** | what the output must be | "What exactly do you need from this?" | "What are we looking for — paths longer than 2–3 hops?" |
|
|
24
|
-
| **Definition of a unit** | what one increment is | "What counts as one X?" | "What counts as one hop?" |
|
|
25
|
-
| **Lifetime / sameness** | shared vs per-instance data | "Is this the same for every instance, or per instance?" | "Are the pushes the same for all instances of a component?" |
|
|
26
|
-
|
|
27
|
-
Rules of thumb:
|
|
28
|
-
- If you can't think of a question, you don't understand the fragment well enough to code it — go
|
|
29
|
-
smaller.
|
|
30
|
-
- Prefer a question that could get a "no." A question that can only be answered "yes" teaches nothing.
|
|
31
|
-
- After a "no," restate the corrected understanding before moving on, so the correction is shared.
|
|
32
|
-
|
|
33
|
-
## The PCB session, translated to code
|
|
34
|
-
|
|
35
|
-
Evans drew object-interaction and class diagrams. This skill produces the same model as code + tests +
|
|
36
|
-
glossary. Below, each beat of the original dialogue maps to what you would actually write.
|
|
37
|
-
|
|
38
|
-
### Beat 1 — the glimmer ("nets")
|
|
39
|
-
The experts kept asking for reports about *nets*. That recurring noun, not their "read a file and
|
|
40
|
-
sort it" framing, was the first model element. You name it back and ask a cardinality question rather
|
|
41
|
-
than scaffolding a type immediately.
|
|
42
|
-
|
|
43
|
-
> "A `Net` is a conductor that connects components and carries a signal to everything on it — yes?"
|
|
44
|
-
|
|
45
|
-
### Beat 2 — reconcile terminology, fix cardinality
|
|
46
|
-
"Component" vs "component instance" vs "ref-des" collide. You reconcile them (synonym question), then
|
|
47
|
-
pin the pin↔instance↔net cardinality (cardinality question). Only once confirmed do you write:
|
|
48
|
-
|
|
49
|
-
```
|
|
50
|
-
ComponentInstance // expert's "ref-des" — reconciled to one name
|
|
51
|
-
pins -> read-only list of Pin
|
|
52
|
-
|
|
53
|
-
Pin
|
|
54
|
-
owner -> ComponentInstance // exactly one (confirmed)
|
|
55
|
-
net -> Net (optional) // exactly one (confirmed)
|
|
56
|
-
|
|
57
|
-
Net
|
|
58
|
-
pins -> read-only list of Pin // connects many pins
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Beat 3 — narrow to one scenario (probe simulation)
|
|
62
|
-
You drop everything not needed to simulate a signal. You ask the *ownership* question and learn the
|
|
63
|
-
**component pushes the signal through** — the `Net` does not do it alone.
|
|
64
|
-
|
|
65
|
-
### Beat 4 — simplify what you can't model
|
|
66
|
-
You can't model chip internals; you ask the *simplification* question and the expert offers
|
|
67
|
-
"push-throughs": a list of (fromPin → toPin) for a component **type** (not per instance — that's the
|
|
68
|
-
lifetime question). The behavior, driven by a test:
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
test "signal propagates through pushes and across nets":
|
|
72
|
-
// arrange a tiny board: in-pin → component pushes to out-pin → net to next component
|
|
73
|
-
hops = simulation.probe(startPin)
|
|
74
|
-
assert hops == 2 // each Net crossing counts as one hop
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Beat 5 — pin the computation goal and the unit
|
|
78
|
-
The *computation-goal* question yields the rule: flag any signal path longer than 2–3 hops. The
|
|
79
|
-
*unit* question yields: **one hop = one Net crossing.** So the `Net` increments the hop count as the
|
|
80
|
-
signal passes:
|
|
81
|
-
|
|
82
|
-
```
|
|
83
|
-
Net
|
|
84
|
-
carry(hopsSoFar) -> hopsSoFar + 1 // crossing this Net is one hop
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Beat 6 — distill: drop `Topology`
|
|
88
|
-
`Topology` exists in the domain but isn't used by the probe simulation, so you explicitly leave it
|
|
89
|
-
out ("I'll drop it for now; we'll bring it back for routing"). The model is a distillation, not a
|
|
90
|
-
transcription — it excludes the hundreds of facts the engineers know but this problem doesn't need.
|
|
91
|
-
|
|
92
|
-
## What "on the same page" looks like at the end of a loop
|
|
93
|
-
|
|
94
|
-
A loop turn is complete only when **all three** agree:
|
|
95
|
-
1. the **expert** has answered the verifying question,
|
|
96
|
-
2. the **code** (type/method/test) reflects that answer, and
|
|
97
|
-
3. the **`CONTEXT.md` `## Language`** records the term, its definition in their words, the rejected
|
|
98
|
-
synonyms (`_Avoid_:`), and the `TypeName` that embodies it.
|
|
99
|
-
|
|
100
|
-
If any of the three lags, close the gap before proposing the next concept.
|
|
101
|
-
|
|
102
|
-
## Drift triggers in existing code
|
|
103
|
-
|
|
104
|
-
When the module's domain layer already has a model, read it against the language and let each mismatch
|
|
105
|
-
become a verifying-question loop turn — surface it, verify with the user, then change code +
|
|
106
|
-
`CONTEXT.md` together:
|
|
107
|
-
|
|
108
|
-
- **Synonym drift** — code says `Learner`, experts now say `Student`. Reconcile, pick one, record the
|
|
109
|
-
rejected synonym under `_Avoid_:`.
|
|
110
|
-
- **Conflated concept** — one type doing two jobs the experts name separately → candidate split.
|
|
111
|
-
- **Leaked invariant** — a rule enforced in a service/controller that an aggregate should own.
|
|
112
|
-
- **Dead concept** — a type no scenario exercises anymore → deprecate (move to `CONTEXT.md`'s
|
|
113
|
-
`## Deferred` with the reason).
|